git.iko.soy/typeable/timezone-olson
on c3bd1fb help sign in
.gitignoredeleted−16
since splitsnapshots
unifiedsplit
@@ -1,16 +0,0 @@
1−dist
2−cabal-dev
3−*.o
4−*.hi
5−*.chi
6−*.chs.h
7−*.dyn_o
8−*.dyn_hi
9−.virtualenv
10−.hpc
11−.hsenv
12−.cabal-sandbox/
13−cabal.sandbox.config
14−*.prof
15−*.aux
16−*.hp
.travis.ymldeleted−107
@@ -1,107 +0,0 @@
1−# This Travis job script has been generated by a script via
2−#
3−# runghc make_travis_yml_2.hs 'timezone-olson.cabal'
4−#
5−# For more information, see https://github.com/hvr/multi-ghc-travis
6−#
7−language: c
8−sudo: false
9−
10−git:
11− submodules: false # whether to recursively clone submodules
12−
13−cache:
14− directories:
15− - $HOME/.cabal/packages
16− - $HOME/.cabal/store
17−
18−before_cache:
19− - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
20− # remove files that are regenerated by 'cabal update'
21− - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*
22− - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json
23− - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache
24− - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar
25− - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx
26−
27− - rm -rfv $HOME/.cabal/packages/head.hackage
28−
29−matrix:
30− include:
31− - compiler: "ghc-8.0.1"
32− # env: TEST=--disable-tests BENCH=--disable-benchmarks
33− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.0.1], sources: [hvr-ghc]}}
34− - compiler: "ghc-8.0.2"
35− # env: TEST=--disable-tests BENCH=--disable-benchmarks
36− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.0.2], sources: [hvr-ghc]}}
37− - compiler: "ghc-8.2.1"
38− # env: TEST=--disable-tests BENCH=--disable-benchmarks
39− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.2.1], sources: [hvr-ghc]}}
40− - compiler: "ghc-8.2.2"
41− # env: TEST=--disable-tests BENCH=--disable-benchmarks
42− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.2.2], sources: [hvr-ghc]}}
43− - compiler: "ghc-8.4.1"
44− # env: TEST=--disable-tests BENCH=--disable-benchmarks
45− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.4.1], sources: [hvr-ghc]}}
46−
47−before_install:
48− - HC=${CC}
49− - HCPKG=${HC/ghc/ghc-pkg}
50− - unset CC
51− - ROOTDIR=$(pwd)
52− - mkdir -p $HOME/.local/bin
53− - "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"
54− - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
55− - echo $HCNUMVER
56−
57−install:
58− - cabal --version
59− - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
60− - BENCH=${BENCH---enable-benchmarks}
61− - TEST=${TEST---enable-tests}
62− - HADDOCK=${HADDOCK-true}
63− - INSTALLED=${INSTALLED-true}
64− - GHCHEAD=${GHCHEAD-false}
65− - travis_retry cabal update -v
66− - "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
67− - rm -fv cabal.project cabal.project.local
68− - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
69− - "printf 'packages: \".\"\\n' > cabal.project"
70− - cat cabal.project
71− - if [ -f "./configure.ac" ]; then
72− (cd "." && autoreconf -i);
73− fi
74− - rm -f cabal.project.freeze
75− - cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dep -j2 all
76− - cabal new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project" --dep -j2 all
77− - rm -rf .ghc.environment.* "."/dist
78− - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
79−
80−# Here starts the actual work to be performed for the package under test;
81−# any command which exits with a non-zero exit code causes the build to fail.
82−script:
83− # test that source-distributions can be generated
84− - (cd "." && cabal sdist)
85− - mv "."/dist/timezone-olson-*.tar.gz ${DISTDIR}/
86− - cd ${DISTDIR} || false
87− - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
88− - "printf 'packages: timezone-olson-*/*.cabal\\n' > cabal.project"
89− - cat cabal.project
90− # this builds all libraries and executables (without tests/benchmarks)
91− - cabal new-build -w ${HC} --disable-tests --disable-benchmarks all
92−
93− # Build with installed constraints for packages in global-db
94− - if $INSTALLED; then echo cabal new-build -w ${HC} --disable-tests --disable-benchmarks $(${HCPKG} list --global --simple-output --names-only | sed 's/\([a-zA-Z0-9-]\{1,\}\) */--constraint="\1 installed" /g') all | sh; else echo "Not building with installed constraints"; fi
95−
96− # build & run tests, build benchmarks
97− - cabal new-build -w ${HC} ${TEST} ${BENCH} all
98−
99− # cabal check
100− - (cd timezone-olson-* && cabal check)
101−
102− # haddock
103− - rm -rf ./dist-newstyle
104− - if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi
105−
106−# REGENDATA ["timezone-olson.cabal"]
107−# EOF
Data/Time/LocalTime/TimeZone/Olson.hs+14−8
@@ -1,7 +1,7 @@
1-----------------------------------------------------------------------------
1-----------------------------------------------------------------------------
2-- |
2-- |
3-- Module : Data.Time.LocalTime.TimeZone.Olson
3-- Module : Data.Time.LocalTime.TimeZone.Olson
4−-- Copyright : Yitzchak Gale 2018
4+-- Copyright : Yitzchak Gale 2010
5--
5--
6-- Maintainer : Yitzchak Gale <gale@sefer.org>
6-- Maintainer : Yitzchak Gale <gale@sefer.org>
7-- Portability : portable
7-- Portability : portable
@@ -9,20 +9,26 @@
9-- A parser and renderer for binary Olson timezone files whose format
9-- A parser and renderer for binary Olson timezone files whose format
10-- are specified by the tzfile(5) man page on Unix-like systems. For
10-- are specified by the tzfile(5) man page on Unix-like systems. For
11-- more information about this format, see
11-- more information about this format, see
12−-- <http://www.twinsun.com/tz/tz-link.htm>. Functions are provided for
12+-- http://www.twinsun.com/tz/tz-link.htm. Functions are provided for
13-- converting the parsed data into @TimeZoneSeries@ and @TimeZone@
13-- converting the parsed data into @TimeZoneSeries@ and @TimeZone@
14-- objects.
14-- objects.
15
15
16+{-
16−{- Copyright (c) 2018 Yitzchak Gale. All rights reserved.
17+Copyright (c) 2010 Yitzchak Gale. All rights reserved.
17−For licensing information, see the BSD3-style license in the file
18+For licensing information, see the file LICENSE that was
18−LICENSE that was originally distributed by the author together with
19+originally distributed by the author together with this file.
19−this file. -}
20+-}
20
21
21module Data.Time.LocalTime.TimeZone.Olson
22module Data.Time.LocalTime.TimeZone.Olson
22(
23(
24+ -- * Olson timezone datatypes
25+ module Data.Time.LocalTime.TimeZone.Olson.Types,
26+
27+ -- * Parsing Olson timezone files
23 module Data.Time.LocalTime.TimeZone.Olson.Parse,
28 module Data.Time.LocalTime.TimeZone.Olson.Parse,
29+
30+ -- * Rendering Olson timezone files
24− module Data.Time.LocalTime.TimeZone.Olson.Render,
31+ module Data.Time.LocalTime.TimeZone.Olson.Render
25− module Data.Time.LocalTime.TimeZone.Olson.Types
26)
32)
27where
33where
28
34
Data/Time/LocalTime/TimeZone/Olson/Parse.hs+48−86
@@ -3,25 +3,26 @@
3-----------------------------------------------------------------------------
3-----------------------------------------------------------------------------
4-- |
4-- |
5-- Module : Data.Time.LocalTime.TimeZone.Olson.Parse
5-- Module : Data.Time.LocalTime.TimeZone.Olson.Parse
6−-- Copyright : Yitzchak Gale 2018
6+-- Copyright : Yitzchak Gale 2010
7--
7--
8-- Maintainer : Yitzchak Gale <gale@sefer.org>
8-- Maintainer : Yitzchak Gale <gale@sefer.org>
9-- Portability : portable
9-- Portability : portable
10--
10--
11−-- A parser for binary Olson timezone files whose format is specified
11+-- A parser for binary Olson timezone files whose format are specified
12-- by the tzfile(5) man page on Unix-like systems. For more
12-- by the tzfile(5) man page on Unix-like systems. For more
13-- information about this format, see
13-- information about this format, see
14−-- <http://www.twinsun.com/tz/tz-link.htm>. Functions are provided for
14+-- http://www.twinsun.com/tz/tz-link.htm. Functions are provided for
15−-- converting the parsed data into 'TimeZoneSeries' objects.
15+-- converting the parsed data into @TimeZoneSeries@ and @TimeZone@
16+-- objects.
16
17
18+{-
17−{- Copyright (c) 2018 Yitzchak Gale. All rights reserved.
19+Copyright (c) 2010 Yitzchak Gale. All rights reserved.
18−For licensing information, see the BSD3-style license in the file
20+For licensing information, see the file LICENSE that was
19−LICENSE that was originally distributed by the author together with
21+originally distributed by the author together with this file.
20−this file. -}
22+-}
21
23
22module Data.Time.LocalTime.TimeZone.Olson.Parse
24module Data.Time.LocalTime.TimeZone.Olson.Parse
23(
25(
24− -- * Parsing Olson timezone files
25 getTimeZoneSeriesFromOlsonFile,
26 getTimeZoneSeriesFromOlsonFile,
26 getOlsonFromFile,
27 getOlsonFromFile,
27 olsonToTimeZoneSeries,
28 olsonToTimeZoneSeries,
@@ -39,14 +40,11 @@ import Data.Binary.Get (Get, runGet, getWord8, getWord32be, getWord64be,
39import qualified Data.ByteString as B
40import qualified Data.ByteString as B
40import qualified Data.ByteString.Lazy as L
41import qualified Data.ByteString.Lazy as L
41import Data.Monoid (mappend)
42import Data.Monoid (mappend)
42−import Data.List (sortBy, groupBy)
43import Data.Maybe (listToMaybe, fromMaybe)
43import Data.Maybe (listToMaybe, fromMaybe)
44import Data.Word (Word8)
44import Data.Word (Word8)
45import Data.Int (Int32, Int64)
45import Data.Int (Int32, Int64)
46−import Data.Ord (comparing)
47−import Data.Function (on)
48import Data.Typeable (Typeable)
46import Data.Typeable (Typeable)
49−import Control.Monad (guard, replicateM, replicateM_, when)
47+import Control.Monad (guard, replicateM, replicateM_)
50import Control.Exception.Extensible (try, throw, Exception, ErrorCall)
48import Control.Exception.Extensible (try, throw, Exception, ErrorCall)
51
49
52-- | An exception indicating that the binary data being parsed was not
50-- | An exception indicating that the binary data being parsed was not
@@ -60,8 +58,8 @@ instance Exception OlsonError
60-- | Convert parsed Olson timezone data into a @TimeZoneSeries@.
58-- | Convert parsed Olson timezone data into a @TimeZoneSeries@.
61olsonToTimeZoneSeries :: OlsonData -> Maybe TimeZoneSeries
59olsonToTimeZoneSeries :: OlsonData -> Maybe TimeZoneSeries
62olsonToTimeZoneSeries (OlsonData ttimes ttinfos@(dflt0:_) _ _) =
60olsonToTimeZoneSeries (OlsonData ttimes ttinfos@(dflt0:_) _ _) =
63− fmap (TimeZoneSeries $ mkTZ dflt) . mapM (lookupTZ ttinfos) .
61+ fmap (TimeZoneSeries $ mkTZ dflt) . mapM (lookupTZ ttinfos) $
64− uniqTimes . sortBy futureToPast $ ttimes
62+ reverse ttimes
65 where
63 where
66 dflt = fromMaybe dflt0 . listToMaybe $ filter isStd ttinfos
64 dflt = fromMaybe dflt0 . listToMaybe $ filter isStd ttinfos
67 isStd (TtInfo _ isdst _ _) = not isdst
65 isStd (TtInfo _ isdst _ _) = not isdst
@@ -70,74 +68,53 @@ olsonToTimeZoneSeries (OlsonData ttimes ttinfos@(dflt0:_) _ _) =
70 lookupTZ ttinfos ttime = fmap (((,) $ toUTC ttime) . mkTZ) . listToMaybe $
68 lookupTZ ttinfos ttime = fmap (((,) $ toUTC ttime) . mkTZ) . listToMaybe $
71 drop (transIndex ttime) ttinfos
69 drop (transIndex ttime) ttinfos
72 toUTC = posixSecondsToUTCTime . fromIntegral . transTime
70 toUTC = posixSecondsToUTCTime . fromIntegral . transTime
73− uniqTimes = map last . groupBy ((==) `on` transTime)
74− futureToPast = comparing $ negate . transTime
75olsonToTimeZoneSeries _ = Nothing
71olsonToTimeZoneSeries _ = Nothing
76
72
77-- | Read timezone data from a binary Olson timezone file and convert
73-- | Read timezone data from a binary Olson timezone file and convert
78−-- it into a @TimeZoneSeries@ for use together with the types and
74+-- it into a @TimeZoneSeries@.
79−-- fucntions of "Data.Time". This is the function from this module
80−-- for which you are most likely to have use.
81−--
82−-- If the values in the Olson timezone file exceed the standard size
83−-- limits (see 'defaultLimits'), this function throws an
84−-- exception. For other behavior, use 'getOlson' and
85−-- 'Data.Binary.Get.runGet' directly.
86getTimeZoneSeriesFromOlsonFile :: FilePath -> IO TimeZoneSeries
75getTimeZoneSeriesFromOlsonFile :: FilePath -> IO TimeZoneSeries
87getTimeZoneSeriesFromOlsonFile fp = getOlsonFromFile fp >>=
76getTimeZoneSeriesFromOlsonFile fp = getOlsonFromFile fp >>=
88 maybe (throwOlson fp "no timezone found in OlsonData") return .
77 maybe (throwOlson fp "no timezone found in OlsonData") return .
89 olsonToTimeZoneSeries
78 olsonToTimeZoneSeries
90
79
91-- | Parse a binary Olson timezone file.
80-- | Parse a binary Olson timezone file.
92−--
93−-- If the values in the Olson timezone file exceed the standard size
94−-- limits (see 'defaultLimits'), this function throws an
95−-- exception. For other behavior, use 'getOlson' and
96−-- 'Data.Binary.Get.runGet' directly.
97getOlsonFromFile :: FilePath -> IO OlsonData
81getOlsonFromFile :: FilePath -> IO OlsonData
98getOlsonFromFile fp = do
82getOlsonFromFile fp = do
99− e <- try . fmap (runGet $ getOlson defaultLimits) $ L.readFile fp
83+ e <- try . fmap (runGet getOlson) $ L.readFile fp
100 either (formatError fp) return e
84 either (formatError fp) return e
101
85
102formatError :: FilePath -> ErrorCall -> IO a
86formatError :: FilePath -> ErrorCall -> IO a
103formatError fp e = throwOlson fp $ show e
87formatError fp e = throwOlson fp $ show e
104
88
105-- | A binary parser for binary Olson timezone files
89-- | A binary parser for binary Olson timezone files
106−getOlson :: SizeLimits -> Get OlsonData
90+getOlson :: Get OlsonData
107−getOlson limits = do
91+getOlson = do
108− (version, part1) <- getOlsonPart True limits get32bitInteger
92+ getByteString 4
109− -- There is one part for Version 1 format data, and two parts and a POSIX
93+ >>= verify ((== "TZif") . toASCII . B.unpack) "missing magic number"
110− -- TZ string for Version 2 or 3 format data
111− case () of
112− _ | version == 0 -> return part1
113− | version == 50 || version == 51 -> do
114− (_, part2) <- getOlsonPart False limits get64bitInteger
115− posixTZ <- getPosixTZ
116− return $ part1 `mappend` part2 `mappend` posixTZ
117− | otherwise -> do
118− let msg = "getOlson: invalid tzfile version " ++ toASCII [version]
119− verify (const False) msg undefined
120−
121−-- Parse the part of an Olson file that contains timezone data
122−getOlsonPart :: Integral a => Bool -> SizeLimits -> Get a ->
123− Get (Word8, OlsonData)
124−getOlsonPart verifyMagic limits getTime = do
125− magic <- fmap (toASCII . B.unpack) $ getByteString 4
126− when verifyMagic $ verify_ (== "TZif") "missing magic number" magic
127 version <- getWord8
94 version <- getWord8
95+ >>= verify (`elem` [0, 50]) "invalid version number"
128 replicateM_ 15 getWord8 -- padding nulls
96 replicateM_ 15 getWord8 -- padding nulls
97+ getOlsonParts version
98+
99+-- There is one part for Version 1 format data, and two parts and a POSIX
100+-- TZ string for Version 2 format data
101+getOlsonParts :: Word8 -> Get OlsonData
102+getOlsonParts 0 = getOlsonPart get32bitInteger
103+getOlsonParts 50 = do part1 <- getOlsonPart get32bitInteger
104+ part2 <- getOlsonPart get64bitInteger
105+ posixTZ <- getPosixTZ
106+ return $ part1 `mappend` part2 `mappend` posixTZ
107+getOlsonParts _ = error "Invalid version number" -- never reached
108+
109+-- Parse the part of an Olson file that contains timezone data
110+getOlsonPart :: Integral a => Get a -> Get OlsonData
111+getOlsonPart getTime = do
129 tzh_ttisgmtcnt <- get32bitInt
112 tzh_ttisgmtcnt <- get32bitInt
130 tzh_ttisstdcnt <- get32bitInt
113 tzh_ttisstdcnt <- get32bitInt
131 tzh_leapcnt <- get32bitInt
114 tzh_leapcnt <- get32bitInt
132− >>= verify (withinLimit maxLeaps) "too many leap second specifications"
133 tzh_timecnt <- get32bitInt
115 tzh_timecnt <- get32bitInt
134− >>= verify (withinLimit maxTimes) "too many timezone transitions"
135 tzh_typecnt <- get32bitInt
116 tzh_typecnt <- get32bitInt
136− >>= verify (withinLimit maxTypes) "too many timezone type specifications"
137− verify (withinLimit maxTypes) "too many isgmt specifiers" tzh_ttisgmtcnt
138− verify (withinLimit maxTypes) "too many isstd specifiers" tzh_ttisstdcnt
139 tzh_charcnt <- get32bitInt
117 tzh_charcnt <- get32bitInt
140− >>= verify (withinLimit maxAbbrChars) "too many tilezone specifiers"
141 times <- fmap (map toInteger) $ replicateM tzh_timecnt getTime
118 times <- fmap (map toInteger) $ replicateM tzh_timecnt getTime
142 indexes <- replicateM tzh_timecnt get8bitInt
119 indexes <- replicateM tzh_timecnt get8bitInt
143 ttinfos <- replicateM tzh_typecnt getTtInfo
120 ttinfos <- replicateM tzh_typecnt getTtInfo
@@ -145,32 +122,22 @@ getOlsonPart verifyMagic limits getTime = do
145 leaps <- replicateM tzh_leapcnt $ getLeapInfo getTime
122 leaps <- replicateM tzh_leapcnt $ getLeapInfo getTime
146 isstds <- replicateM tzh_ttisstdcnt getBool
123 isstds <- replicateM tzh_ttisstdcnt getBool
147 isgmts <- replicateM tzh_ttisgmtcnt getBool
124 isgmts <- replicateM tzh_ttisgmtcnt getBool
148− return
149− (version,
150− OlsonData
125+ return $ OlsonData
151− (zipWith Transition times indexes)
126+ (zipWith Transition times indexes)
152− (map (flip lookupAbbr abbr_chars) . zipWith setTtype ttinfos $
127+ (map (flip lookupAbbr abbr_chars) . zipWith setTtype ttinfos .
128+ (++ repeat UnknownType) $
153− zipWithExtend boolsToTType False False isstds isgmts)
129+ zipWith boolsToTType (map Just isstds ++ repeat Nothing) isgmts)
154− leaps
130+ leaps
155− Nothing
131+ Nothing
156− )
157 where
132 where
158− withinLimit limit value = maybe True (value <=) $ limit limits
159 lookupAbbr (TtInfo gmtoff isdst ttype abbrind) =
133 lookupAbbr (TtInfo gmtoff isdst ttype abbrind) =
160 TtInfo gmtoff isdst ttype . takeWhile (/= '\NUL') . drop abbrind
134 TtInfo gmtoff isdst ttype . takeWhile (/= '\NUL') . drop abbrind
161 setTtype ttinfo ttype = ttinfo {tt_ttype = ttype}
135 setTtype ttinfo ttype = ttinfo {tt_ttype = ttype}
162− boolsToTType _ isgmt | isgmt = UTC
136+ boolsToTType _ isgmt | isgmt = UTC
163− boolsToTType isstd _
137+ boolsToTType isstd _ = maybe UnknownType stdOrWall isstd
138+ stdOrWall isstd
164− | isstd = Std
139+ | isstd = Std
165− | otherwise = Wall
140+ | otherwise = Wall
166−
167−-- A variant of zipWith whose result is the length of the longer
168−-- rather than the shorter list, by extending the shorter list with
169−-- a default value
170−zipWithExtend :: (a -> b -> c) -> a -> b -> [a] -> [b] -> [c]
171−zipWithExtend f x0 y0 (x:xs) (y:ys) = f x y : zipWithExtend f x0 y0 xs ys
172−zipWithExtend f x0 _ [] ys = map (f x0) ys
173−zipWithExtend f _ y0 xs _ = map (flip f y0) xs
174
141
175-- Parse a POSIX-style TZ string.
142-- Parse a POSIX-style TZ string.
176-- We don't try to understand the TZ string, we just pass it along whole.
143-- We don't try to understand the TZ string, we just pass it along whole.
@@ -191,7 +158,7 @@ getTtInfo = do
191 gmtoff <- get32bitInt
158 gmtoff <- get32bitInt
192 isdst <- getBool
159 isdst <- getBool
193 abbrind <- get8bitInt
160 abbrind <- get8bitInt
194− return $ TtInfo gmtoff isdst Wall abbrind
161+ return $ TtInfo gmtoff isdst UnknownType abbrind
195
162
196-- Parse leap second info. (usually not used)
163-- Parse leap second info. (usually not used)
197getLeapInfo :: Integral a => Get a -> Get LeapInfo
164getLeapInfo :: Integral a => Get a -> Get LeapInfo
@@ -234,11 +201,6 @@ verify pred msg val
234 | pred val = return val
201 | pred val = return val
235 | otherwise = error msg
202 | otherwise = error msg
236
203
237−verify_ :: Monad m => (a -> Bool) -> String -> a -> m ()
238−verify_ pred msg val
239− | pred val = return ()
240− | otherwise = error msg
241−
242throwOlson :: FilePath -> String -> IO a
204throwOlson :: FilePath -> String -> IO a
243throwOlson fp msg = throw . OlsonError $
205throwOlson fp msg = throw . OlsonError $
244 fp ++ ": invalid timezone file: " ++ msg
206 fp ++ ": invalid timezone file: " ++ msg
Data/Time/LocalTime/TimeZone/Olson/Render.hs+73−125
@@ -2,42 +2,37 @@
2-----------------------------------------------------------------------------
2-----------------------------------------------------------------------------
3-- |
3-- |
4-- Module : Data.Time.LocalTime.TimeZone.Olson.Render
4-- Module : Data.Time.LocalTime.TimeZone.Olson.Render
5−-- Copyright : Yitzchak Gale 2018
5+-- Copyright : Yitzchak Gale 2010
6--
6--
7-- Maintainer : Yitzchak Gale <gale@sefer.org>
7-- Maintainer : Yitzchak Gale <gale@sefer.org>
8-- Portability : portable
8-- Portability : portable
9--
9--
10−-- Render Olson timezone data in the standard binary format as used in
10+-- Render Olson timezone data in the standard binary format as
11−-- Olson timezone files, and as specified by the tzfile(5) man page on
11+-- used in Olson timezone files, and as specified by the
12−-- Unix-like systems. For more information about this format, see
12+-- tzfile(5) man page on Unix-like systems. For more information
13−-- <http://www.twinsun.com/tz/tz-link.htm>.
13+-- about this format, see http://www.twinsun.com/tz/tz-link.htm.
14
14
15+{-
15−{- Copyright (c) 2018ZZ Yitzchak Gale. All rights reserved.
16+Copyright (c) 2010 Yitzchak Gale. All rights reserved.
16−For licensing information, see the BSD3-style license in the file
17+For licensing information, see the file LICENSE that was
17−LICENSE that was originally distributed by the author together with
18+originally distributed by the author together with this file.
18−this file. -}
19+-}
19
20
20module Data.Time.LocalTime.TimeZone.Olson.Render
21module Data.Time.LocalTime.TimeZone.Olson.Render
21(
22(
22− -- * Rendering Olson timezone files
23+ -- * Rendering Olson timezone data
23 -- | If any of the transition times or leap second times specified
24 -- | If any of the transition times or leap second times specified
24 -- require more than a 32-bit integer to represent as a Unix
25 -- require more than a 32-bit integer to represent as a Unix
25 -- timestamp, or if a POSIX-style TZ string is specified, timezone
26 -- timestamp, or if a POSIX-style TZ string is specified, timezone
26 -- data is rendered using Version 2 format. Otherwise, the timezone data
27 -- data is rendered using Version 2 format. Otherwise, the timezone data
27 -- is rendered using Version 1 format.
28 -- is rendered using Version 1 format.
28− renderTimeZoneSeriesToOlsonFile,
29− timeZoneSeriesToOlson,
30 renderOlsonToFile,
29 renderOlsonToFile,
31− verifyOlsonLimits,
32 putOlson,
30 putOlson,
33− extractOlsonV1
31+ splitOlson
34)
32)
35where
33where
36
34
37import Data.Time.LocalTime.TimeZone.Olson.Types
35import Data.Time.LocalTime.TimeZone.Olson.Types
38−import Data.Time.LocalTime.TimeZone.Series (TimeZoneSeries(TimeZoneSeries))
39−import Data.Time (TimeZone(TimeZone, timeZoneSummerOnly, timeZoneName))
40−import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
41import Data.Binary.Put (Put, runPut, putByteString, putWord8, flush,
36import Data.Binary.Put (Put, runPut, putByteString, putWord8, flush,
42 putWord32be, putWord64be)
37 putWord32be, putWord64be)
43import qualified Data.ByteString as B
38import qualified Data.ByteString as B
@@ -45,117 +40,78 @@ import qualified Data.ByteString.Lazy as L
45import Data.List (partition, sortBy, sort, group)
40import Data.List (partition, sortBy, sort, group)
46import Data.Ord (comparing)
41import Data.Ord (comparing)
47import Data.Word (Word8)
42import Data.Word (Word8)
48−import Data.Maybe (listToMaybe, maybeToList, isNothing, fromMaybe, catMaybes)
43+import Data.Maybe (listToMaybe, maybeToList, isNothing, fromMaybe)
49import Data.Monoid (mempty)
44import Data.Monoid (mempty)
50−import Control.Monad (guard, replicateM_, unless)
45+import Control.Monad (guard, replicateM_)
51
46
52−-- | Render a @TimeZoneSeries@ as a binary Olson timezone file.
53−--
54−-- If the values in the Olson timezone data exceed the standard size
55−-- limits (see 'defaultLimits'), this function throws an
56−-- exception. For other behavior, use 'timeZoneSeriesToOlson',
57−-- 'verifyOlsonLimits', 'putOlson' and 'Data.Binary.Put.runPut'
58−-- directly.
59−renderTimeZoneSeriesToOlsonFile :: FilePath -> TimeZoneSeries -> IO ()
60−renderTimeZoneSeriesToOlsonFile fp = renderOlsonToFile fp .
61− fromMaybe (error "Cannot render TimeZoneSeries: default is summer time") .
62− timeZoneSeriesToOlson
63−
64−-- | Convert a @TimeZoneSeries@ to @OlsonData@ for rendering.
65−timeZoneSeriesToOlson :: TimeZoneSeries -> Maybe OlsonData
66−timeZoneSeriesToOlson (TimeZoneSeries dflt pairs)
67− | timeZoneSummerOnly dflt && not (all timeZoneSummerOnly $ map snd pairs)
68− = Nothing
69− | otherwise = Just $
70− OlsonData
71− [Transition secs ttinfo |
72− (t, tz) <- reverse pairs,
73− let secs = round $ utcTimeToPOSIXSeconds t,
74− ttinfo <- maybeToList $ lookup (mkTT tz) ttAssocs]
75− ttinfos
76− []
77− Nothing
78− where
79− mkTT (TimeZone offset isdst abbr) =
80− TtInfo (offset*60) isdst Wall abbr
81− dfltTT = mkTT dflt
82− ttAssocs = (dfltTT, 0) :
83− zip (uniq . sort . filter (/= dfltTT) $ map (mkTT . snd) pairs) [1..]
84− ttinfos = map fst ttAssocs
85−
86−-- | Check whether @OlsonData@ is within size limits.
87−verifyOlsonLimits :: SizeLimits -> OlsonData -> Bool
88−verifyOlsonLimits limits (OlsonData transs ttinfos leaps _) =
89− withinLimit maxTimes transs &&
90− withinLimit maxTypes ttinfos &&
91− withinLimit maxLeaps leaps &&
92− withinLimit maxAbbrChars abbrChars
93− where
94− withinLimit limit items = maybe True (null . flip drop items) $
95− limit limits
96− abbrChars = concat abbrStrs ++ map (const '\NUL') abbrStrs
97− abbrStrs = map tt_abbr ttinfos
98
47
99-- | Render Olson timezone data as a binary Olson timezone file
48-- | Render Olson timezone data as a binary Olson timezone file
100−--
101−-- If the values in the Olson timezone data exceed the standard size
102−-- limits (see 'defaultLimits'), this function throws an
103−-- exception. For other behavior, use 'verifyOlsonLimits', 'putOlson'
104−-- and 'Data.Binary.Put.runPut' directly.
105renderOlsonToFile :: FilePath -> OlsonData -> IO ()
49renderOlsonToFile :: FilePath -> OlsonData -> IO ()
106−renderOlsonToFile fp olson = do
107− unless (verifyOlsonLimits defaultLimits olson) $
108− error "Olson timezone data exceeds size limits"
109− L.writeFile fp . runPut . putOlson $ olson
50+renderOlsonToFile fp = L.writeFile fp . runPut . putOlson
110
51
111-- | Render Olson timezone data in binary Olson timezone file format
52-- | Render Olson timezone data in binary Olson timezone file format
112-- as a lazy @ByteString@
53-- as a lazy @ByteString@
113−putOlson olson@(OlsonData _ _ _ posix)
54+putOlson :: OlsonData -> Put
114− | fitsInVersion1 = putOlsonPart 0 put32bitIntegral olson
55+putOlson olson = do
115− | otherwise = do putOlsonPart 50 put32bitIntegral olson1
56+ putASCII "TZif"
116− putOlsonPart 50 put64bitIntegral olson
57+ putWord8 version
117− putPosixTZ posix
58+ putByteString . B.pack $ replicate 15 0 -- padding nulls
59+ putOlsonParts version olson1 olson2 posix
60+ flush
118 where
61 where
119− olson1 = extractOlsonV1 olson
62+ (olson1, olson2, posix) = splitOlson olson
120− fitsInVersion1 = fmap null posix /= Just False &&
63+ version
121− olson1 == olson {olsonPosixTZ = Nothing}
64+ | olson2 /= mempty = 50
122−
65+ | otherwise = maybe 0 (const 50) $ posix >>= guard . not . null
66+
67+-- | Split Olson timezone data into three parts: timezone data that can
123−-- | Extract Olson timezone data that can be rendered using Version 1 format
68+-- be rendered using Version 1 format, timezone data that can only be
69+-- rendered using Version 2 format, and the POSIX-style TZ string
70+-- if specified
124−extractOlsonV1 :: OlsonData -> OlsonData
71+splitOlson :: OlsonData -> (OlsonData, OlsonData, Maybe String)
125−extractOlsonV1 (OlsonData transs ttinfos leaps _)
126− | allV1 = OlsonData transs ttinfos leaps Nothing
72+splitOlson (OlsonData transs ttinfos leaps posix) =
127− | otherwise = OlsonData transs1 ttinfos1 leaps1 Nothing
73+ (OlsonData transs1 ttinfos1 leaps1 Nothing,
74+ OlsonData transs2 ttinfos2 leaps2 Nothing,
75+ posix)
128 where
76 where
129 cutoff = 0x80000000 -- 2^31
77 cutoff = 0x80000000 -- 2^31
130− fitsIn32bits x = x < cutoff && x >= negate cutoff
78+ ( leaps1 , leaps2 ) = partition ((>= cutoff) . leapTime) leaps
131− leaps1 = filter (fitsIn32bits . leapTime) leaps
132− transs1' = filter (fitsIn32bits . transTime) transs
79+ (transs1', transs2') = partition ((>= cutoff) . transTime) transs
133− allV1 = length leaps1 == length leaps && length transs1' == length transs
80+ assoc1 = mkAssoc [0] transs1'
134− assoc1 = zip
81+ assoc2 = mkAssoc [] transs2'
82+ transs1 = mkTranss transs1' assoc1
83+ transs2 = mkTranss transs2' assoc2
84+ ttinfos1 = mkTtinfos assoc1
85+ ttinfos2 = mkTtinfos assoc2
86+ mkAssoc prepend transs' = zip
135 (sortBy (comparing $ fmap tt_ttype . listToMaybe . flip drop ttinfos) .
87 (sortBy (comparing $ fmap tt_ttype . listToMaybe . flip drop ttinfos) .
136− uniq . sort . (0 :) $ map transIndex transs1')
88+ uniq . sort . (prepend ++) $ map transIndex transs')
137 [0..]
89 [0..]
138− transs1 = [t {transIndex = i} |
90+ mkTranss transs' assoc = [t {transIndex = i} |
139− t <- transs1', i <- maybeToList $ lookup (transIndex t) assoc1]
91+ t <- transs', i <- maybeToList $ lookup (transIndex t) assoc]
140− ttinfos1 = map snd . dropWhile (isNothing . fst) .
92+ mkTtinfos assoc = map snd . dropWhile (isNothing . fst) .
141− sortBy (comparing fst) $ zip (map (flip lookup assoc1) [0..]) ttinfos
93+ sortBy (comparing fst) $ zip (map (flip lookup assoc) [0..]) ttinfos
142−
94+
95+putOlsonParts :: Word8 -> OlsonData -> OlsonData -> Maybe String -> Put
96+putOlsonParts 0 olson1 _ _ = putOlsonPart put32bitIntegral olson1
97+putOlsonParts _ olson1 olson2 posix = do
98+ putOlsonPart put32bitIntegral olson1
99+ putOlsonPart put64bitIntegral olson2
100+ putPosixTZ posix
101+
143−putOlsonPart :: Word8 -> (Integer -> Put) -> OlsonData -> Put
102+putOlsonPart :: (Integer -> Put) -> OlsonData -> Put
144−putOlsonPart version putTime (OlsonData transs ttinfos leaps _) = do
103+putOlsonPart putTime (OlsonData transs ttinfos leaps _) = do
145− putASCII "magic number" "TZif"
146− putWord8 version
147− putByteString . B.pack $ replicate 15 0 -- padding nulls
148 replicateM_ 2 $ putCount ttinfosWithTtype
104 replicateM_ 2 $ putCount ttinfosWithTtype
149− -- tzh_ttisgmtcnt
105+ -- tzh_ttisgmtcnt
150− -- tzh_ttisstdcnt
106+ -- tzh_ttisstdcnt
151− putCount leaps -- tzh_leapcnt
107+ putCount leaps -- tzh_leapcnt
152− putCount transs -- tzh_timecnt
108+ putCount transs -- tzh_timecnt
153− putCount ttinfos -- tzh_typecnt
109+ putCount ttinfos -- tzh_typecnt
154− put32bitIntegral abbrChars -- tzh_charcnt
110+ putCount abbrChars -- tzh_charcnt
155 mapM_ (putTime . transTime ) transs
111 mapM_ (putTime . transTime ) transs
156 mapM_ (put8bitIntegral . transIndex) transs
112 mapM_ (put8bitIntegral . transIndex) transs
157 mapM_ putTtInfo ttinfosIndexed
113 mapM_ putTtInfo ttinfosIndexed
158− mapM_ putAbbr abbrStrings
114+ putASCII abbrChars
159 mapM_ (putLeapInfo putTime) leaps
115 mapM_ (putLeapInfo putTime) leaps
160 mapM_ (putBool . (== Std) . tt_ttype) ttinfosWithTtype -- isstd
116 mapM_ (putBool . (== Std) . tt_ttype) ttinfosWithTtype -- isstd
161 mapM_ (putBool . (== UTC) . tt_ttype) ttinfosWithTtype -- isgmt
117 mapM_ (putBool . (== UTC) . tt_ttype) ttinfosWithTtype -- isgmt
@@ -163,18 +119,16 @@ putOlsonPart version putTime (OlsonData transs ttinfos leaps _) = do
163 putCount = put32bitIntegral . length
119 putCount = put32bitIntegral . length
164 ttinfosWithTtype = takeWhile ((<= UTC) . tt_ttype) ttinfosIndexed
120 ttinfosWithTtype = takeWhile ((<= UTC) . tt_ttype) ttinfosIndexed
165 abbrStrings = uniq . sort $ map tt_abbr ttinfos
121 abbrStrings = uniq . sort $ map tt_abbr ttinfos
166− abbrChars = sum (map length abbrStrings) + length abbrStrings
167− putAbbr abbr = putASCII "time zone abbreviation" abbr >> putWord8 0
168− abbrAssocs = zip abbrStrings . scanl (+) 0 $
169− map ((+ 1) . length) abbrStrings
122+ abbrChars = concatMap (++ "\NUL") abbrStrings
170 ttinfosIndexed = [TtInfo gmtoff isdst ttype i |
123 ttinfosIndexed = [TtInfo gmtoff isdst ttype i |
171 TtInfo gmtoff isdst ttype abbr <- ttinfos,
124 TtInfo gmtoff isdst ttype abbr <- ttinfos,
172− i <- maybeToList $ lookup abbr abbrAssocs]
125+ i <- maybeToList . lookup abbr . zip abbrStrings .
126+ scanl (+) 0 $ map ((+ 1) . length) abbrStrings]
173
127
174putPosixTZ :: Maybe String -> Put
128putPosixTZ :: Maybe String -> Put
175putPosixTZ posix = do
129putPosixTZ posix = do
176 putWord8 10
130 putWord8 10
177− putASCII "POSIX TZ string"$ fromMaybe "" posix
131+ putASCII $ fromMaybe "" posix
178 putWord8 10
132 putWord8 10
179
133
180putTtInfo :: TtInfo Int -> Put
134putTtInfo :: TtInfo Int -> Put
@@ -207,11 +161,5 @@ putBool True = putWord8 1
207uniq :: Eq a => [a] -> [a]
161uniq :: Eq a => [a] -> [a]
208uniq = map head . group
162uniq = map head . group
209
163
210−putASCII :: String -> String -> Put
164+putASCII :: String -> Put
211−putASCII what =
212− putByteString . B.pack . map (fromIntegral . verify . fromEnum)
165+putASCII = putByteString . B.pack . map (fromIntegral . fromEnum)
213− where
214− verify c
215− | c >= 32 && c <= 126 = c
216− | otherwise = error $ "Cannot render TimeZoneSeries: " ++
217− what ++ " contains non-ASCII characters"
Data/Time/LocalTime/TimeZone/Olson/Types.hs+31−101
@@ -1,9 +1,8 @@
1{-# OPTIONS_HADDOCK hide #-}
1{-# OPTIONS_HADDOCK hide #-}
2−{-# LANGUAGE CPP #-}
3-----------------------------------------------------------------------------
2-----------------------------------------------------------------------------
4-- |
3-- |
5-- Module : Data.Time.LocalTime.TimeZone.Olson.Types
4-- Module : Data.Time.LocalTime.TimeZone.Olson.Types
6−-- Copyright : Yitzchak Gale 2018
5+-- Copyright : Yitzchak Gale 2010
7--
6--
8-- Maintainer : Yitzchak Gale <gale@sefer.org>
7-- Maintainer : Yitzchak Gale <gale@sefer.org>
9-- Portability : portable
8-- Portability : portable
@@ -11,89 +10,54 @@
11-- Data types to represent timezone data as used in Olson timezone
10-- Data types to represent timezone data as used in Olson timezone
12-- files, and as specified by the tzfile(5) man page on Unix-like
11-- files, and as specified by the tzfile(5) man page on Unix-like
13-- systems. For more information about this format, see
12-- systems. For more information about this format, see
14−-- <http://www.twinsun.com/tz/tz-link.htm>.
13+-- http://www.twinsun.com/tz/tz-link.htm.
15--
14--
16-- Both Version 1 and Version 2 timezone data can be represented.
15-- Both Version 1 and Version 2 timezone data can be represented.
17
16
17+{-
18−{- Copyright (c) 2018 Yitzchak Gale. All rights reserved.
18+Copyright (c) 2010 Yitzchak Gale. All rights reserved.
19−For licensing information, see the BSD3-style license in the file
19+For licensing information, see the file LICENSE that was
20−LICENSE that was originally distributed by the author together with
20+originally distributed by the author together with this file.
21−this file. -}
21+-}
22
22
23module Data.Time.LocalTime.TimeZone.Olson.Types
23module Data.Time.LocalTime.TimeZone.Olson.Types
24(
24(
25− -- * Olson timezone datatypes
26 OlsonData(..),
25 OlsonData(..),
27 Transition(..),
26 Transition(..),
28 TransitionType(..),
27 TransitionType(..),
29 TtInfo(..),
28 TtInfo(..),
30− LeapInfo(..),
29+ LeapInfo(..)
31−
32− -- ** Size limits for Olson timezone data
33− SizeLimits(..),
34− defaultLimits,
35− limitsNoSolar,
36− noLimits
37)
30)
38where
31where
39
32
40−#if MIN_VERSION_base(4,11,0)
41−import Data.Semigroup (Semigroup(..))
42−#endif
43import Data.Monoid (Monoid(..))
33import Data.Monoid (Monoid(..))
44import Control.Monad (mplus)
34import Control.Monad (mplus)
45
35
46-- | @OlsonData@ represents a full set of timezone data for a location.
36-- | @OlsonData@ represents a full set of timezone data for a location.
47--
37--
48−-- @OlsonData@ can represent timezone data from files in Version 1, 2,
38+-- @OlsonData@ can represent timezone data from files in Version 1 format
49−-- or 3 format.
50−--
51−-- Version 1 format files can only contain timestamp values that can
39+-- or Version 2 format. Version 1 format files can only contain timestamp
52−-- be represented in less than 32 bits, and cannot contain a POSIX TZ
40+-- values that can be represented in less than 32 bits, and cannot contain
53−-- string.
41+-- a POSIX TZ string.
54−--
55−-- In a Version 2 format file, the timezone data is split into two
56−-- parts. The first part contains timezone data for which all
57−-- timestamp values can be represented in less than 32 bits, and the
58−-- second part contains timezone data for which 32 bits or more are
59−-- required to represent timestamp values. The POSIX TZ string, if
60−-- present, can only be rendered in a Version 2 file, and appears
61−-- after both parts of the timezone data.
62--
42--
63−-- Version 3 format files relax certain syntax requirements for the
43+-- In a Version 2 format file, the timezone data is split into two parts.
64−-- POSIX TZ string. Since we represent the POSIX TZ string as an
44+-- The first part contains timezone data for which all timestamp values
65−-- unparsed 'String', Version 3 is identical to Version 2 for our
45+-- can be represented in less 32 bits, and the second part contains
66−-- purposes.
46+-- timezone data for which 32 bits or more are required to represent
47+-- timestamp values. The POSIX TZ string, if present, can only be rendered
48+-- in a Version 2 file, and appears after both parts of the timezone data.
67data OlsonData =
49data OlsonData =
68− OlsonData {
50+ OlsonData
69− olsonTransitions :: [Transition],
51+ [Transition]
70− olsonTypes :: [TtInfo String],
52+ [TtInfo String]
71− olsonLeaps :: [LeapInfo],
53+ [LeapInfo]
72− olsonPosixTZ :: (Maybe String)
73− -- ^ Optional POSIX TZ string for
54+ (Maybe String) -- ^ Optional POSIX TZ string for the last @Transition@
74− -- times after the last @Transition@
75− }
76 deriving (Eq, Show)
55 deriving (Eq, Show)
77
56
78−#if MIN_VERSION_base(4,11,0)
79−instance Semigroup OlsonData where
80− OlsonData a b c d <> OlsonData a' b' c' d' =
81− OlsonData (a ++ map (shiftBy $ length b) a')
82− (b ++ b') (c ++ c') (d `mplus` d')
83− where
84− shiftBy n trans = trans {transIndex = n + transIndex trans}
85−
86−instance Monoid OlsonData where
87− mempty = OlsonData [] [] [] Nothing
88−#else
89instance Monoid OlsonData where
57instance Monoid OlsonData where
90 mempty = OlsonData [] [] [] Nothing
58 mempty = OlsonData [] [] [] Nothing
91− mappend (OlsonData a b c d ) (OlsonData a' b' c' d') =
59+ mappend (OlsonData a b c d ) (OlsonData a' b' c' d')
92− OlsonData (a ++ map (shiftBy $ length b) a')
93− (b ++ b') (c ++ c') (d `mplus` d')
60+ = OlsonData (a++a') (b++b') (c++c') (d `mplus` d')
94− where
95− shiftBy n trans = trans {transIndex = n + transIndex trans}
96−#endif
97
61
98-- | A @Transition@ represents a moment when the clocks change in a
62-- | A @Transition@ represents a moment when the clocks change in a
99-- timezone. It consists of a Unix timestamp value that indicates the
63-- timezone. It consists of a Unix timestamp value that indicates the
@@ -104,9 +68,8 @@ data Transition =
104 Transition
68 Transition
105 {transTime :: Integer, -- ^ Unix timestamp indicating the time
69 {transTime :: Integer, -- ^ Unix timestamp indicating the time
106 -- when the clocks change
70 -- when the clocks change
107− transIndex :: Int -- ^ 0-based index in the list of @TtInfo@
71+ transIndex :: Int} -- ^ 0-based index in the list of @TtInfo@
108 -- that describes the new time
72 -- that describes the new time
109− }
110 deriving (Eq, Show)
73 deriving (Eq, Show)
111
74
112-- | A @TransitionType@ is historical information about whether the
75-- | A @TransitionType@ is historical information about whether the
@@ -116,7 +79,7 @@ data Transition =
116-- zone. This historical trivia may seem rather boring, but
79-- zone. This historical trivia may seem rather boring, but
117-- unfortunately it is needed to interpret a POSIX-style TZ string
80-- unfortunately it is needed to interpret a POSIX-style TZ string
118-- timezone specification correctly.
81-- timezone specification correctly.
119−data TransitionType = Std | Wall | UTC
82+data TransitionType = Std | Wall | UTC | UnknownType
120 deriving (Eq, Ord, Show)
83 deriving (Eq, Ord, Show)
121
84
122-- | A @TtInfo@ is a specification of local time in a timezone for
85-- | A @TtInfo@ is a specification of local time in a timezone for
@@ -131,48 +94,15 @@ data TtInfo abbr =
131 -- in seconds
94 -- in seconds
132 tt_isdst :: Bool, -- ^ True if local clocks are summer time
95 tt_isdst :: Bool, -- ^ True if local clocks are summer time
133 tt_ttype :: TransitionType,
96 tt_ttype :: TransitionType,
134− tt_abbr :: abbr -- ^ The timezone abbreviation string.
97+ tt_abbr :: abbr} -- ^ The timezone abbreviation string.
135− }
136− deriving (Eq, Ord, Show)
98+ deriving (Eq, Show)
137
99
138-- | Olson timezone files can contain leap second specifications, though
100-- | Olson timezone files can contain leap second specifications, though
139−-- most do not.
101+-- the official files have not done so since the late 1980s.
140data LeapInfo =
102data LeapInfo =
141 LeapInfo
103 LeapInfo
142 {leapTime :: Integer, -- ^ A Unix timestamp indicating the time
104 {leapTime :: Integer, -- ^ A Unix timestamp indicating the time
143 -- that the leap second occurred
105 -- that the leap second occurred
144− leapOffset :: Int -- ^ The new total offset of UTC from UT1
106+ leapOffset :: Int} -- ^ The new total offset of UTC from UT1
145 -- after this leap second
107 -- after this leap second
146− }
147 deriving (Eq, Show)
108 deriving (Eq, Show)
148−
149−-- | The reference C implentation imposes size limits on the data
150−-- structures in a timezone file.
151−data SizeLimits = SizeLimits
152− {maxTimes :: Maybe Int, -- ^ The maximum number of transition times
153− maxTypes :: Maybe Int, -- ^ The maximum number of TtInfo
154− -- clock settings
155− maxAbbrChars :: Maybe Int, -- ^ The maximum total number of bytes in
156− -- all timezone abbreviations.
157− maxLeaps :: Maybe Int -- ^ The maximum number of leap second
158− -- specifications.
159− }
160−
161−-- | The size limits in @defaultLimits@ are taken from the file
162−-- tzfile.h from tzcode version 2010f. These are the limits for the C
163−-- implementation on many platforms.
164−defaultLimits :: SizeLimits
165−defaultLimits = SizeLimits { maxTimes = Just 1200, maxTypes = Just 256,
166− maxAbbrChars = Just 50, maxLeaps = Just 50 }
167−
168−-- | @limitsNoSolar@ contains the tighter size limits imposed on some
169−-- platforms that do not allow timezones that are based on solar time.
170−limitsNoSolar :: SizeLimits
171−limitsNoSolar = defaultLimits { maxTypes = Just 20 }
172−
173−-- | @noLimits@ imposes no size limits. If you use @noLimits@ when
174−-- parsing, you may exhaust all available memory when reading a faulty
175−-- or malicious timezone file. If you use @noLimits@ when rendering,
176−-- the rendered timezone file might not be readable on some systems.
177−noLimits :: SizeLimits
178−noLimits = SizeLimits Nothing Nothing Nothing Nothing
LICENSE+1−1
@@ -1,4 +1,4 @@
1−Copyright (c) 2010-2018 Yitzchak Gale. All rights reserved.
1+Copyright (c) 2010 Yitzchak Gale. All rights reserved.
2
2
3Redistribution and use in source and binary forms, with or without
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are
4modification, are permitted provided that the following conditions are
README.mddeleted−43
@@ -1,43 +0,0 @@
1−# timezone-olson
2−
3−On Hackage: [timezone-olson](http://hackage.haskell.org/package/timezone-olson)
4−
5−This package provides a parser and renderer for binary Olson timezone
6−files whose format is specified by the tzfile(5) man page on Unix-like
7−systems. For more information about this format, see
8−[the IANA timezone database site](https://www.iana.org/time-zones).
9−
10−Functions are provided for
11−converting the parsed data into `TimeZoneSeries` objects from the
12−[timezone-series](http://hackage.haskell.org/package/timezone-series)
13−package. On many platforms, binary Olson timezone
14−files suitable for use with this package are available in the
15−directory `/usr/share/zoneinfo` and its subdirectories on your computer.
16−
17−See also the
18−[timezone-olson-th](http://hackage.haskell.org/package/timezone-olson-th)
19−package for a way to include timezone informaton from a binary Olson
20−timezone file at compile time.
21−
22−Copyright (c) 2010-2018 Yitzchak Gale. All rights reserved.
23−
24−For licensing information, see the BSD3-style license in the file
25−LICENSE that was originally distributed by the author together with
26−this file.
27−
28−This package is part of the [time-ng project](http://projects.haskell.org/time-ng/).
29−
30−## Testing utilities
31−
32−This package also provides two Haskell files, each of which can be
33−compiled into a command-line utility that might be helpful for testing
34−purposes.
35−
36−zhdump.hs: A clone of zdump(8), including most of its bugs, that
37− is usually present on systems that have an Olson timezone
38− database, except hzdump takes paths to timezone files
39− instead of timezone identifiers on the command line.
40−
41−catTZ: Read and parse a timezone file, then render it. With the
42− -i flag, interprets the timezone data as a TimeZoneSeries
43− object before rendering.
Setup.hsdeleted−2
@@ -1,2 +0,0 @@
1−import Distribution.Simple
2−main = defaultMain
catTZ.hsdeleted−14
@@ -1,14 +0,0 @@
1−-- | Parse an Olson timezone file, then render it.
2−-- If -i is specified, interpret it as a TimeZoneSeries before rendering.
3−module Main where
4−
5−import Data.Time.LocalTime.TimeZone.Olson
6−import Data.Time.LocalTime.TimeZone.Series
7−import System.Environment
8−
9−main = do
10− args <- getArgs
11− if head args == "-i"
12− then getTimeZoneSeriesFromOlsonFile (args !! 1) >>=
13− renderTimeZoneSeriesToOlsonFile (args !! 2)
14− else getOlsonFromFile (args !! 0) >>= renderOlsonToFile (args !! 1)
hzdump.hs+14−29
@@ -1,6 +1,5 @@
1-- | A clone in Haskell of the zdump(8) command, including most of
1-- | A clone in Haskell of the zdump(8) command, including most of
2−-- the bugs, that takes paths to Olson files instead of timezone
3−-- names. This is useful for testing the Haskell implementation
2+-- the bugs. This is useful for testing the Haskell implementation
4-- of the Olson timezone parser and renderer against the reference
3-- of the Olson timezone parser and renderer against the reference
5-- implementation in C. But less useful than it might seem at first,
4-- implementation in C. But less useful than it might seem at first,
6-- because Haskell rounds historical solar mean timezones to the
5-- because Haskell rounds historical solar mean timezones to the
@@ -9,24 +8,22 @@
9module Main where
8module Main where
10
9
11import Data.Time.LocalTime.TimeZone.Series
10import Data.Time.LocalTime.TimeZone.Series
12−import Data.Time.LocalTime.TimeZone.Olson
11+import Data.Time.LocalTime.TimeZone.Olson.Types
12+import Data.Time.LocalTime.TimeZone.Olson.Parse
13import Data.Time
13import Data.Time
14import Data.Maybe (listToMaybe)
14import Data.Maybe (listToMaybe)
15import System.Environment (getArgs)
15import System.Environment (getArgs)
16import System.Exit (exitWith, exitSuccess, ExitCode(ExitFailure))
16import System.Exit (exitWith, exitSuccess, ExitCode(ExitFailure))
17import System.Locale (defaultTimeLocale)
17import System.Locale (defaultTimeLocale)
18−import Control.Monad (guard)
19
18
20version :: IO a
19version :: IO a
21version = do
20version = do
22− putStrLn "hzdump version 0.1, clone of zdump 1.7 -c option fixed,"
21+ putStrLn "hzdump version 0.1, clone of zdump 1.7 with -c option fixed"
23− putStrLn "takes zone file paths instead of zone specifications"
24 exitSuccess
22 exitSuccess
25
23
26usage :: IO a
24usage :: IO a
27usage = do
25usage = do
28− putStrLn "usage: hzdump [--version] [-v] [-c cutoff] zone-file-path ..."
26+ putStrLn "usage: hzdump [--version] [-v] [-c cutoff] zonename ..."
29− putStrLn " where cutoff is lo-year,hi-year or hi-year"
30 exitWith $ ExitFailure 1
27 exitWith $ ExitFailure 1
31
28
32illegalOpt :: Char -> IO a -- sic
29illegalOpt :: Char -> IO a -- sic
@@ -34,8 +31,7 @@ illegalOpt opt = do
34 putStrLn $ "hzdump: illegal option -- " ++ [opt]
31 putStrLn $ "hzdump: illegal option -- " ++ [opt]
35 usage
32 usage
36
33
37−data Option = Version | Illegal Char |
34+data Option = Version | Illegal Char | Verbose (Maybe Integer) | Now
38− Verbose (Maybe Integer, Maybe Integer) | Now
39 deriving (Eq, Ord, Show)
35 deriving (Eq, Ord, Show)
40
36
41main = do
37main = do
@@ -43,7 +39,7 @@ main = do
43 (getTimes, displayTime) <- case opts of
39 (getTimes, displayTime) <- case opts of
44 Version -> version
40 Version -> version
45 Illegal opt -> illegalOpt opt
41 Illegal opt -> illegalOpt opt
46− Verbose rng -> return (transitionTimes rng, displayVerbose)
42+ Verbose yr -> return (transitionTimesUntil yr, displayVerbose)
47 _ -> do now <- getCurrentTime
43 _ -> do now <- getCurrentTime
48 return (const [now], displayConcise)
44 return (const [now], displayConcise)
49 tzss <- mapM getZone zones
45 tzss <- mapM getZone zones
@@ -58,26 +54,17 @@ main = do
58
54
59parseArgs :: [String] -> (Option, [FilePath])
55parseArgs :: [String] -> (Option, [FilePath])
60parseArgs args | "--version" `elem` args = (Version, [])
56parseArgs args | "--version" `elem` args = (Version, [])
61−parseArgs args = getOpts False (Nothing, Nothing) args
57+parseArgs args = getOpts False Nothing args
62 where
58 where
63 getOpts _ cutoff ("-v":args) = getOpts True cutoff args
59 getOpts _ cutoff ("-v":args) = getOpts True cutoff args
64− getOpts v _ ("-c":c:args) = maybe (Illegal 'c', [])
60+ getOpts v Nothing ("-c":c:args) = maybe (Illegal 'c', [])
65− (\y -> getOpts v y args) $
61+ (\y -> getOpts v (Just y) args) $
66− parseCutoff c
62+ maybeRead c
67 getOpts v cutoff ("--":zones ) = (opts v cutoff, zones)
63 getOpts v cutoff ("--":zones ) = (opts v cutoff, zones)
68 getOpts _ _ (('-':x:_):_) = (Illegal x, [])
64 getOpts _ _ (('-':x:_):_) = (Illegal x, [])
69 getOpts v cutoff zones = (opts v cutoff, zones)
65 getOpts v cutoff zones = (opts v cutoff, zones)
70−
71 opts v cutoff = if v then Verbose cutoff else Now
66 opts v cutoff = if v then Verbose cutoff else Now
72
67
73− parseCutoff c = let p0 = listToMaybe $ reads c
74− los = fmap fst p0
75− his = do str <- fmap snd p0
76− let (comma, str1) = splitAt 1 str
77− guard $ comma == ","
78− maybeRead str1
79− in los >> Just (maybe (Nothing, los) ((,) los . Just) his)
80−
81displayConcise :: TimeZoneSeries -> UTCTime -> String
68displayConcise :: TimeZoneSeries -> UTCTime -> String
82displayConcise tzs t = formatTime defaultTimeLocale format $
69displayConcise tzs t = formatTime defaultTimeLocale format $
83 ZoneSeriesTime t tzs
70 ZoneSeriesTime t tzs
@@ -90,11 +77,9 @@ displayVerbose tzs t = concat [displayConcise utcTZ t, " = ",
90 where
77 where
91 isdst = timeZoneSummerOnly $ timeZoneFromSeries tzs t
78 isdst = timeZoneSummerOnly $ timeZoneFromSeries tzs t
92
79
93−transitionTimes :: (Maybe Integer, Maybe Integer) -> TimeZoneSeries ->
80+transitionTimesUntil :: Maybe Integer -> TimeZoneSeries -> [UTCTime]
94− [UTCTime]
81+transitionTimesUntil yr =
95−transitionTimes (lo, hi) =
96− maybe id (takeWhile . (>)) (fmap happyNewYear hi) .
97− maybe id (dropWhile . (>)) (fmap happyNewYear lo) .
82+ maybe id (dropWhile . (>=)) (fmap happyNewYear yr) .
98 addBugs . addPrevSecond . actualTransitions
83 addBugs . addPrevSecond . actualTransitions
99 where
84 where
100 happyNewYear y = UTCTime (fromGregorian y 1 1) 0
85 happyNewYear y = UTCTime (fromGregorian y 1 1) 0
timezone-olson.cabaldeleted−45
@@ -1,45 +0,0 @@
1−Name: timezone-olson
2−Version: 0.1.9
3−Synopsis: A pure Haskell parser and renderer for binary Olson timezone files
4−Description: A parser and renderer for binary Olson timezone
5− files whose format is specified by the tzfile(5)
6− man page on Unix-like systems. For more
7− information about this format, see
8− <http://www.iana.org/time-zones/repository/tz-link.html>.
9− Functions are provided for converting the parsed data into
10− 'TimeZoneSeries' objects from the timezone-series
11− package. On many platforms, binary Olson timezone
12− files suitable for use with this package are
13− available in the directory /usr/share/zoneinfo
14− and its subdirectories on your computer.
15− For a way to read binary Olson timezone files at compile
16− time, see the timezone-olson-th package
17− (<http://hackage.haskell.org/package/timezone-olson-th>).
18−Homepage: http://projects.haskell.org/time-ng/
19−License: BSD3
20−License-file: LICENSE
21−Author: Yitzchak Gale
22−Maintainer: yitz@sefer.org
23−Copyright: Copyright (c) 2010-2018 Yitzchak Gale. All rights reserved.
24−Category: Data
25−Build-type: Simple
26−Extra-source-files: README.md, catTZ.hs, hzdump.hs
27−Cabal-version: >=1.10
28−Tested-with: GHC > 8.0 && < 8.5
29−
30−Source-repository HEAD
31− type: git
32− location: https://github.com/ygale/timezone-olson.git
33−
34−Library
35− Default-language: Haskell2010
36− Exposed-modules: Data.Time.LocalTime.TimeZone.Olson
37− , Data.Time.LocalTime.TimeZone.Olson.Parse
38− , Data.Time.LocalTime.TimeZone.Olson.Render
39− , Data.Time.LocalTime.TimeZone.Olson.Types
40− Build-depends: base >= 3.0 && < 5.0,
41− timezone-series >= 0.1.0 && < 0.2,
42− time >= 1.6 && < 1.10,
43− binary >= 0.4.1 && < 0.9,
44− bytestring >= 0.9 && < 1.0,
45− extensible-exceptions >= 0.1.0 && < 0.2