git.iko.soy/typeable/timezone-olson
on master help sign in
Added Types.hs, Parse.hs, Render.hs
commit1d68f6448177cf443a2226a5409eff13ec961d33 authorYitzchak Gale <gale@sefer.org> dateThu 13 May 2010, 20:49 (16 years ago)
3 files changed+551
Data/Time/LocalTime/TimeZone/Olson/Parse.hs+228 Data/Time/LocalTime/TimeZone/Olson/Render.hs+190 Data/Time/LocalTime/TimeZone/Olson/Types.hs+133
Data/Time/LocalTime/TimeZone/Olson/Parse.hsnew file+228
@@ -0,0 +1,228 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+-----------------------------------------------------------------------------
3+-- |
4+-- Module : Data.Time.LocalTime.TimeZone.Olson.Parse
5+-- Copyright : Yitzchak Gale 2010
6+--
7+-- Maintainer : Yitzchak Gale <gale@sefer.org>
8+-- Portability : portable
9+--
10+-- A parser for binary Olson timezone files whose format are specified
11+-- by the tzfile(5) man page on Unix-like systems. For more
12+-- information about this format, see
13+-- http://www.twinsun.com/tz/tz-link.htm. Functions are provided for
14+-- converting the parsed data into @TimeZoneSeries@ and @TimeZone@
15+-- objects.
16+
17+{-
18+Copyright (c) 2010 Yitzchak Gale. All rights reserved.
19+
20+Redistribution and use in source and binary forms, with or without
21+modification, are permitted provided that the following conditions are
22+met:
23+
24+ * Redistributions of source code must retain the above copyright
25+ notice, this list of conditions and the following disclaimer.
26+
27+ * Redistributions in binary form must reproduce the above
28+ copyright notice, this list of conditions and the following
29+ disclaimer in the documentation and/or other materials provided
30+ with the distribution.
31+
32+ * Neither the name of Yitzchak Gale nor the names of other
33+ contributors may be used to endorse or promote products derived
34+ from this software without specific prior written permission.
35+
36+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
37+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
38+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
39+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
40+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
43+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
45+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
46+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47+----------------------------------------------------------------------------}
48+
49+module Data.Time.LocalTime.TimeZone.Olson.Parse
50+(
51+ getTimeZoneSeriesFromOlsonFile,
52+ getOlsonFromFile,
53+ olsonToTimeZoneSeries,
54+ getOlson,
55+ OlsonError
56+)
57+where
58+
59+import Data.Time.LocalTime.TimeZone.Olson.Types
60+import Data.Time.LocalTime.TimeZone.Series (TimeZoneSeries(..))
61+import Data.Time (TimeZone(TimeZone))
62+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
63+import Data.Binary.Get (Get, runGet, getWord8, getWord32be, getWord64be,
64+ getByteString, getRemainingLazyByteString)
65+import qualified Data.ByteString as B
66+import qualified Data.ByteString.Lazy as L
67+import Data.Monoid (mappend)
68+import Data.Maybe (listToMaybe)
69+import Data.Word (Word8)
70+import Data.Int (Int32, Int64)
71+import Data.Typeable (Typeable)
72+import Control.Monad (guard, replicateM, replicateM_)
73+import Control.Exception.Extensible (try, throw, Exception, ErrorCall)
74+
75+-- | An exception indicating that the binary data being parsed was not
76+-- valid Olson timezone data
77+data OlsonError = OlsonError String
78+ deriving Typeable
79+instance Show OlsonError where
80+ show (OlsonError msg) = msg
81+instance Exception OlsonError
82+
83+-- | Convert parsed Olson timezone data into a @TimeZoneSeries@.
84+olsonToTimeZoneSeries :: OlsonData -> Maybe TimeZoneSeries
85+olsonToTimeZoneSeries (OlsonData ttimes ttinfos@(dflt:_) _ _) =
86+ fmap (TimeZoneSeries $ mkTZ dflt) . mapM (lookupTZ ttinfos) $
87+ reverse ttimes
88+ where
89+ mkTZ (TtInfo gmtoff isdst _ abbr) = TimeZone (gmtoff `div` 60) isdst abbr
90+ lookupTZ ttinfos ttime = fmap (((,) $ toUTC ttime) . mkTZ) . listToMaybe $
91+ drop (transIndex ttime) ttinfos
92+ toUTC = posixSecondsToUTCTime . fromIntegral . transTime
93+olsonToTimeZoneSeries _ = Nothing
94+
95+-- | Read timezone data from a binary Olson timezone file and convert
96+-- it into a @TimeZoneSeries@.
97+getTimeZoneSeriesFromOlsonFile :: FilePath -> IO TimeZoneSeries
98+getTimeZoneSeriesFromOlsonFile fp = getOlsonFromFile fp >>=
99+ maybe (throwOlson fp "no timezone found in OlsonData") return .
100+ olsonToTimeZoneSeries
101+
102+-- | Parse a binary Olson timezone file.
103+getOlsonFromFile :: FilePath -> IO OlsonData
104+getOlsonFromFile fp = do
105+ e <- try . fmap (runGet getOlson) $ L.readFile fp
106+ either (formatError fp) return e
107+
108+formatError :: FilePath -> ErrorCall -> IO a
109+formatError fp e = throwOlson fp $ show e
110+
111+-- | A binary parser for binary Olson timezone files
112+getOlson :: Get OlsonData
113+getOlson = do
114+ getByteString 4
115+ >>= verify ((== "TZif") . toASCII . B.unpack) "missing magic number"
116+ version <- getWord8
117+ >>= verify (`elem` [0, 50]) "invalid version number"
118+ replicateM_ 15 getWord8 -- padding nulls
119+ getOlsonParts version
120+
121+-- There is one part for Version 1 format data, and two parts and a POSIX
122+-- TZ string for Version 2 format data
123+getOlsonParts :: Word8 -> Get OlsonData
124+getOlsonParts 0 = getOlsonPart get32bitInteger
125+getOlsonParts 50 = do part1 <- getOlsonPart get32bitInteger
126+ part2 <- getOlsonPart get64bitInteger
127+ posixTZ <- getPosixTZ
128+ return $ part1 `mappend` part2 `mappend` posixTZ
129+getOlsonParts _ = error "Invalid version number" -- never reached
130+
131+-- Parse the part of an Olson file that contains timezone data
132+getOlsonPart :: Integral a => Get a -> Get OlsonData
133+getOlsonPart getTime = do
134+ tzh_ttisgmtcnt <- get32bitInt
135+ tzh_ttisstdcnt <- get32bitInt
136+ tzh_leapcnt <- get32bitInt
137+ tzh_timecnt <- get32bitInt
138+ tzh_typecnt <- get32bitInt
139+ tzh_charcnt <- get32bitInt
140+ times <- fmap (map toInteger) $ replicateM tzh_timecnt getTime
141+ indexes <- replicateM tzh_timecnt get8bitInt
142+ ttinfos <- replicateM tzh_typecnt getTtInfo
143+ abbr_chars <- fmap (toASCII . B.unpack) $ getByteString tzh_charcnt
144+ leaps <- replicateM tzh_leapcnt $ getLeapInfo getTime
145+ isstds <- replicateM tzh_ttisstdcnt getBool
146+ isgmts <- replicateM tzh_ttisgmtcnt getBool
147+ return $ OlsonData
148+ (zipWith Transition times indexes)
149+ (map (flip lookupAbbr abbr_chars) . zipWith setTtype ttinfos .
150+ (++ repeat UnknownType) $
151+ zipWith boolsToTType (map Just isstds ++ repeat Nothing) isgmts)
152+ leaps
153+ Nothing
154+ where
155+ lookupAbbr (TtInfo gmtoff isdst ttype abbrind) =
156+ TtInfo gmtoff isdst ttype . takeWhile (/= '\NUL') . drop abbrind
157+ setTtype ttinfo ttype = ttinfo {tt_ttype = ttype}
158+ boolsToTType _ isgmt | isgmt = UTC
159+ booslToTType isstd _ = maybe UnknownType stdOrWall isstd
160+ stdOrWall isstd
161+ | isstd = Std
162+ | otherwise = Wall
163+
164+-- Parse a POSIX-style TZ string.
165+-- We don't try to understand the TZ string, we just pass it along whole.
166+getPosixTZ :: Get OlsonData
167+getPosixTZ = do
168+ getWord8 >>= verify (== 10)
169+ "POSIX TZ string not preceded by newline"
170+ posixTZ <- fmap (L.takeWhile (/= 10)) getRemainingLazyByteString
171+ -- We don't check for the trailing newline, in order to keep it lazy
172+ return . OlsonData [] [] [] $ do
173+ guard (not $ L.null posixTZ)
174+ Just . toASCII $ L.unpack posixTZ
175+
176+-- Parse a ttinfo struct. Each ttinfo struct corresponds to a single
177+-- Data.Time.TimeZone object.
178+getTtInfo :: Get (TtInfo Int)
179+getTtInfo = do
180+ gmtoff <- get32bitInt
181+ isdst <- getBool
182+ abbrind <- get8bitInt
183+ return $ TtInfo gmtoff isdst UnknownType abbrind
184+
185+-- Parse leap second info. (usually not used)
186+getLeapInfo :: Integral a => Get a -> Get LeapInfo
187+getLeapInfo getTime = do
188+ lTime <- fmap toInteger getTime
189+ lOffset <- get32bitInt
190+ return $ LeapInfo lTime lOffset
191+
192+-- Our 8-bit ints are unsigned, so we can convert them directly
193+get8bitInt :: Get Int
194+get8bitInt = fmap fromIntegral getWord8
195+
196+getInt32 :: Get Int32
197+getInt32 = fmap fromIntegral getWord32be
198+
199+get32bitInt :: Get Int
200+get32bitInt = fmap fromIntegral getInt32
201+ -- via Int32 to get the sign right
202+ -- in case we are on a 64-bit platform
203+
204+get32bitInteger :: Get Integer
205+get32bitInteger = fmap fromIntegral getInt32
206+ -- via Int32 to get the sign right
207+
208+getInt64 :: Get Int64
209+getInt64 = fmap fromIntegral getWord64be
210+
211+get64bitInteger :: Get Integer
212+get64bitInteger = fmap fromIntegral getInt64
213+ -- via Int64 to get the sign right
214+
215+getBool :: Get Bool
216+getBool = fmap (/= 0) getWord8
217+
218+toASCII :: [Word8] -> String
219+toASCII = map (toEnum . fromIntegral)
220+
221+verify :: Monad m => (a -> Bool) -> String -> a -> m a
222+verify pred msg val
223+ | pred val = return val
224+ | otherwise = error msg
225+
226+throwOlson :: FilePath -> String -> IO a
227+throwOlson fp msg = throw . OlsonError $
228+ fp ++ ": invalid timezone file: " ++ msg
Data/Time/LocalTime/TimeZone/Olson/Render.hsnew file+190
@@ -0,0 +1,190 @@
1+-----------------------------------------------------------------------------
2+-- |
3+-- Module : Data.Time.LocalTime.TimeZone.Olson.Render
4+-- Copyright : Yitzchak Gale 2010
5+--
6+-- Maintainer : Yitzchak Gale <gale@sefer.org>
7+-- Portability : portable
8+--
9+-- Render Olson timezone data in the standard binary format as
10+-- used in Olson timezone files, and as specified by the
11+-- tzfile(5) man page on Unix-like systems. For more information
12+-- about this format, see http://www.twinsun.com/tz/tz-link.htm.
13+
14+{-
15+Copyright (c) 2010 Yitzchak Gale. All rights reserved.
16+
17+Redistribution and use in source and binary forms, with or without
18+modification, are permitted provided that the following conditions are
19+met:
20+
21+ * Redistributions of source code must retain the above copyright
22+ notice, this list of conditions and the following disclaimer.
23+
24+ * Redistributions in binary form must reproduce the above
25+ copyright notice, this list of conditions and the following
26+ disclaimer in the documentation and/or other materials provided
27+ with the distribution.
28+
29+ * Neither the name of Yitzchak Gale nor the names of other
30+ contributors may be used to endorse or promote products derived
31+ from this software without specific prior written permission.
32+
33+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
34+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
35+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
36+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
37+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
40+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
41+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
42+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
43+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44+----------------------------------------------------------------------------}
45+
46+module Data.Time.LocalTime.TimeZone.Olson.Render
47+(
48+ -- * Rendering Olson timezone data
49+ -- | If any of the transition times or leap second times specified
50+ -- require more than a 32-bit integer to represent as a Unix
51+ -- timestamp, or if a POSIX-style TZ string is specified, timezone
52+ -- data is rendered using Version 2 format. Otherwise, the timezone data
53+ -- is rendered using Version 1 format.
54+ renderOlsonToFile,
55+ putOlson,
56+ splitOlson
57+)
58+where
59+
60+import Data.Time.LocalTime.TimeZone.Olson.Types
61+import Data.Binary.Put (Put, runPut, putByteString, putWord8, flush,
62+ putWord32be, putWord64be)
63+import qualified Data.ByteString as B
64+import qualified Data.ByteString.Lazy as L
65+import Data.List (partition, sortBy, sort, group)
66+import Data.Ord (comparing)
67+import Data.Word (Word8)
68+import Data.Maybe (listToMaybe, maybeToList, isNothing, fromMaybe)
69+import Data.Monoid (mempty)
70+import Control.Monad (guard, replicateM_)
71+
72+
73+-- | Render Olson timezone data as a binary Olson timezone file
74+renderOlsonToFile :: FilePath -> OlsonData -> IO ()
75+renderOlsonToFile fp = L.writeFile fp . runPut . putOlson
76+
77+-- | Render Olson timezone data in binary Olson timezone file format
78+-- as a lazy @ByteString@
79+putOlson :: OlsonData -> Put
80+putOlson olson = do
81+ putASCII "TZif"
82+ putWord8 version
83+ putByteString . B.pack $ replicate 15 0 -- padding nulls
84+ putOlsonParts version olson1 olson2 posix
85+ flush
86+ where
87+ (olson1, olson2, posix) = splitOlson olson
88+ version
89+ | olson2 /= mempty = 50
90+ | otherwise = maybe 0 (const 50) $ posix >>= guard . not . null
91+
92+-- | Split Olson timezone data into three parts: timezone data that can
93+-- be rendered using Version 1 format, timezone data that can only be
94+-- rendered using Version 2 format, and the POSIX-style TZ string
95+-- if specified
96+splitOlson :: OlsonData -> (OlsonData, OlsonData, Maybe String)
97+splitOlson (OlsonData transs ttinfos leaps posix) =
98+ (OlsonData transs1 ttinfos1 leaps1 Nothing,
99+ OlsonData transs2 ttinfos2 leaps2 Nothing,
100+ posix)
101+ where
102+ cutoff = 0x80000000 -- 2^31
103+ ( leaps1 , leaps2 ) = partition ((>= cutoff) . leapTime) leaps
104+ (transs1', transs2') = partition ((>= cutoff) . transTime) transs
105+ assoc1 = mkAssoc [0] transs1'
106+ assoc2 = mkAssoc [] transs2'
107+ transs1 = mkTranss transs1' assoc1
108+ transs2 = mkTranss transs2' assoc2
109+ ttinfos1 = mkTtinfos assoc1
110+ ttinfos2 = mkTtinfos assoc2
111+ mkAssoc prepend transs' = zip
112+ (sortBy (comparing $ fmap tt_ttype . listToMaybe . flip drop ttinfos) .
113+ uniq . sort . (prepend ++) $ map transIndex transs')
114+ [0..]
115+ mkTranss transs' assoc = [t {transIndex = i} |
116+ t <- transs', i <- maybeToList $ lookup (transIndex t) assoc]
117+ mkTtinfos assoc = map snd . dropWhile (isNothing . fst) .
118+ sortBy (comparing fst) $ zip (map (flip lookup assoc) [0..]) ttinfos
119+
120+putOlsonParts :: Word8 -> OlsonData -> OlsonData -> Maybe String -> Put
121+putOlsonParts 0 olson1 _ _ = putOlsonPart put32bitIntegral olson1
122+putOlsonParts _ olson1 olson2 posix = do
123+ putOlsonPart put32bitIntegral olson1
124+ putOlsonPart put64bitIntegral olson2
125+ putPosixTZ posix
126+
127+putOlsonPart :: (Integer -> Put) -> OlsonData -> Put
128+putOlsonPart putTime (OlsonData transs ttinfos leaps _) = do
129+ replicateM_ 2 $ putCount ttinfosWithTtype
130+ -- tzh_ttisgmtcnt
131+ -- tzh_ttisstdcnt
132+ putCount leaps -- tzh_leapcnt
133+ putCount transs -- tzh_timecnt
134+ putCount ttinfos -- tzh_typecnt
135+ putCount abbrChars -- tzh_charcnt
136+ mapM_ (putTime . transTime ) transs
137+ mapM_ (put8bitIntegral . transIndex) transs
138+ mapM_ putTtInfo ttinfosIndexed
139+ putASCII abbrChars
140+ mapM_ (putLeapInfo putTime) leaps
141+ mapM_ (putBool . (== Std) . tt_ttype) ttinfosWithTtype -- isstd
142+ mapM_ (putBool . (== UTC) . tt_ttype) ttinfosWithTtype -- isgmt
143+ where
144+ putCount = put32bitIntegral . length
145+ ttinfosWithTtype = takeWhile ((<= UTC) . tt_ttype) ttinfosIndexed
146+ abbrStrings = uniq . sort $ map tt_abbr ttinfos
147+ abbrChars = concatMap (++ "\NUL") abbrStrings
148+ ttinfosIndexed = [TtInfo gmtoff isdst ttype i |
149+ TtInfo gmtoff isdst ttype abbr <- ttinfos,
150+ i <- maybeToList . lookup abbr . zip abbrStrings .
151+ scanl (+) 0 $ map ((+ 1) . length) abbrStrings]
152+
153+putPosixTZ :: Maybe String -> Put
154+putPosixTZ posix = do
155+ putWord8 10
156+ putASCII $ fromMaybe "" posix
157+ putWord8 10
158+
159+putTtInfo :: TtInfo Int -> Put
160+putTtInfo tt = do
161+ put32bitIntegral $ tt_gmtoff tt
162+ putBool $ tt_isdst tt
163+ put8bitIntegral $ tt_abbr tt
164+
165+putLeapInfo :: Integral a => (a -> Put) -> LeapInfo -> Put
166+putLeapInfo putTime leap = do
167+ putTime . fromIntegral $ leapTime leap
168+ put32bitIntegral $ leapOffset leap
169+
170+-- Converting signed integrals to unsigned can be done directly,
171+-- without the care needed for the opposite direction when parsing.
172+
173+put8bitIntegral :: Integral a => a -> Put
174+put8bitIntegral = putWord8 . fromIntegral
175+
176+put32bitIntegral :: Integral a => a -> Put
177+put32bitIntegral = putWord32be . fromIntegral
178+
179+put64bitIntegral :: Integral a => a -> Put
180+put64bitIntegral = putWord64be . fromIntegral
181+
182+putBool :: Bool -> Put
183+putBool False = putWord8 0
184+putBool True = putWord8 1
185+
186+uniq :: Eq a => [a] -> [a]
187+uniq = map head . group
188+
189+putASCII :: String -> Put
190+putASCII = putByteString . B.pack . map (fromIntegral . fromEnum)
Data/Time/LocalTime/TimeZone/Olson/Types.hsnew file+133
@@ -0,0 +1,133 @@
1+-----------------------------------------------------------------------------
2+-- |
3+-- Module : Data.Time.LocalTime.TimeZone.Olson.Types
4+-- Copyright : Yitzchak Gale 2010
5+--
6+-- Maintainer : Yitzchak Gale <gale@sefer.org>
7+-- Portability : portable
8+--
9+-- Data types to represent timezone data as used in Olson timezone
10+-- files, and as specified by the tzfile(5) man page on Unix-like
11+-- systems. For more information about this format, see
12+-- http://www.twinsun.com/tz/tz-link.htm.
13+--
14+-- Both Version 1 and Version 2 timezone data can be represented.
15+
16+{-
17+Copyright (c) 2010 Yitzchak Gale. All rights reserved.
18+
19+Redistribution and use in source and binary forms, with or without
20+modification, are permitted provided that the following conditions are
21+met:
22+
23+ * Redistributions of source code must retain the above copyright
24+ notice, this list of conditions and the following disclaimer.
25+
26+ * Redistributions in binary form must reproduce the above
27+ copyright notice, this list of conditions and the following
28+ disclaimer in the documentation and/or other materials provided
29+ with the distribution.
30+
31+ * Neither the name of Yitzchak Gale nor the names of other
32+ contributors may be used to endorse or promote products derived
33+ from this software without specific prior written permission.
34+
35+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46+-}
47+
48+module Data.Time.LocalTime.TimeZone.Olson.Types
49+(
50+ OlsonData(..),
51+ Transition(..),
52+ TransitionType(..),
53+ TtInfo(..),
54+ LeapInfo(..)
55+)
56+where
57+
58+import Data.Monoid (Monoid(..))
59+import Control.Monad (mplus)
60+
61+-- | @OlsonData@ represents a full set of timezone data for a location.
62+--
63+-- @OlsonData@ can represent timezone data from files in Version 1 format
64+-- or Version 2 format. Version 1 format files can only contain timestamp
65+-- values that can be represented in less than 32 bits, and cannot contain
66+-- a POSIX TZ string.
67+--
68+-- In a Version 2 format file, the timezone data is split into two parts.
69+-- The first part contains timezone data for which all timestamp values
70+-- can be represented in less 32 bits, and the second part contains
71+-- timezone data for which 32 bits or more are required to represent
72+-- timestamp values. The POSIX TZ string, if present, can only be rendered
73+-- in a Version 2 file, and appears after both parts of the timezone data.
74+data OlsonData =
75+ OlsonData
76+ [Transition]
77+ [TtInfo String]
78+ [LeapInfo]
79+ (Maybe String) -- ^ Optional POSIX TZ string for the last @Transition@
80+ deriving (Eq, Show)
81+
82+instance Monoid OlsonData where
83+ mempty = OlsonData [] [] [] Nothing
84+ mappend (OlsonData a b c d ) (OlsonData a' b' c' d')
85+ = OlsonData (a++a') (b++b') (c++c') (d `mplus` d')
86+
87+-- | A @Transition@ represents a moment when the clocks change in a
88+-- timezone. It consists of a Unix timestamp value that indicates the
89+-- exact moment in UTC when the clocks change, and the 0-based index
90+-- in the list of @TtInfo@ specifications for the description of the
91+-- new time after the clocks change.
92+data Transition =
93+ Transition
94+ {transTime :: Integer, -- ^ Unix timestamp indicating the time
95+ -- when the clocks change
96+ transIndex :: Int} -- ^ 0-based index in the list of @TtInfo@
97+ -- that describes the new time
98+ deriving (Eq, Show)
99+
100+-- | A @TransitionType@ is historical information about whether the
101+-- official body that announced a time change specified the time of
102+-- the change in terms of UTC, standard time (i.e., non-summer time)
103+-- for the time zone, or the current wall clock time in the time
104+-- zone. This historical trivia may seem rather boring, but
105+-- unfortunately it is needed to interpret a POSIX-style TZ string
106+-- timezone specification correctly.
107+data TransitionType = Std | Wall | UTC | UnknownType
108+ deriving (Eq, Ord, Show)
109+
110+-- | A @TtInfo@ is a specification of local time in a timezone for
111+-- some period during which the clocks did not change. `abbr` is
112+-- @String@ if the timezone abbreviation is represented as a @String@,
113+-- or @Int@ if it is represented as an index into a long string of
114+-- null-terminated abbreviation strings (as in an Olson binary
115+-- timezone file).
116+data TtInfo abbr =
117+ TtInfo
118+ {tt_gmtoff :: Int, -- ^ The offset of local clocks from UTC,
119+ -- in seconds
120+ tt_isdst :: Bool, -- ^ True if local clocks are summer time
121+ tt_ttype :: TransitionType,
122+ tt_abbr :: abbr} -- ^ The timezone abbreviation string.
123+ deriving (Eq, Show)
124+
125+-- | Olson timezone files can contain leap second specifications, though
126+-- the official files have not done so since the late 1980s.
127+data LeapInfo =
128+ LeapInfo
129+ {leapTime :: Integer, -- ^ A Unix timestamp indicating the time
130+ -- that the leap second occurred
131+ leapOffset :: Int} -- ^ The new total offset of UTC from UT1
132+ -- after this leap second
133+ deriving (Eq, Show)