-
Notifications
You must be signed in to change notification settings - Fork 8
/
hzdump.hs
118 lines (103 loc) · 4.55 KB
/
hzdump.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
-- | A clone in Haskell of the zdump(8) command, including most of
-- the bugs, that takes paths to Olson files instead of timezone
-- names. This is useful for testing the Haskell implementation
-- of the Olson timezone parser and renderer against the reference
-- implementation in C. But less useful than it might seem at first,
-- because Haskell rounds historical solar mean timezones to the
-- nearest minute, whereas the C implementation rounds to the nearest
-- second.
module Main where
import Data.Time.LocalTime.TimeZone.Series
import Data.Time.LocalTime.TimeZone.Olson
import Data.Time
import Data.Maybe (listToMaybe)
import System.Environment (getArgs)
import System.Exit (exitWith, exitSuccess, ExitCode(ExitFailure))
import System.Locale (defaultTimeLocale)
import Control.Monad (guard)
version :: IO a
version = do
putStrLn "hzdump version 0.1, clone of zdump 1.7 -c option fixed,"
putStrLn "takes zone file paths instead of zone specifications"
exitSuccess
usage :: IO a
usage = do
putStrLn "usage: hzdump [--version] [-v] [-c cutoff] zone-file-path ..."
putStrLn " where cutoff is lo-year,hi-year or hi-year"
exitWith $ ExitFailure 1
illegalOpt :: Char -> IO a -- sic
illegalOpt opt = do
putStrLn $ "hzdump: illegal option -- " ++ [opt]
usage
data Option = Version | Illegal Char |
Verbose (Maybe Integer, Maybe Integer) | Now
deriving (Eq, Ord, Show)
main = do
(opts, zones) <- fmap parseArgs getArgs
(getTimes, displayTime) <- case opts of
Version -> version
Illegal opt -> illegalOpt opt
Verbose rng -> return (transitionTimes rng, displayVerbose)
_ -> do now <- getCurrentTime
return (const [now], displayConcise)
tzss <- mapM getZone zones
putStr . unlines . concat $
zipWith (displayZone getTimes displayTime) zones tzss
where
getZone "-" = return utcTZ -- sic
getZone z = getTimeZoneSeriesFromOlsonFile z
displayZone getTimes displayTime zone tzs =
map ((zone ++) . (" " ++) . displayTime tzs) $ getTimes tzs
parseArgs :: [String] -> (Option, [FilePath])
parseArgs args | "--version" `elem` args = (Version, [])
parseArgs args = getOpts False (Nothing, Nothing) args
where
getOpts _ cutoff ("-v":args) = getOpts True cutoff args
getOpts v _ ("-c":c:args) = maybe (Illegal 'c', [])
(\y -> getOpts v y args) $
parseCutoff c
getOpts v cutoff ("--":zones ) = (opts v cutoff, zones)
getOpts _ _ (('-':x:_):_) = (Illegal x, [])
getOpts v cutoff zones = (opts v cutoff, zones)
opts v cutoff = if v then Verbose cutoff else Now
parseCutoff c = let p0 = listToMaybe $ reads c
los = fmap fst p0
his = do str <- fmap snd p0
let (comma, str1) = splitAt 1 str
guard $ comma == ","
maybeRead str1
in los >> Just (maybe (Nothing, los) ((,) los . Just) his)
displayConcise :: TimeZoneSeries -> UTCTime -> String
displayConcise tzs t = formatTime defaultTimeLocale format $
ZoneSeriesTime t tzs
where
format = "%a %b %e %T %Y %Z"
displayVerbose :: TimeZoneSeries -> UTCTime -> String
displayVerbose tzs t = concat [displayConcise utcTZ t, " = ",
displayConcise tzs t, " isdst=", if isdst then "1" else "0"]
where
isdst = timeZoneSummerOnly $ timeZoneFromSeries tzs t
transitionTimes :: (Maybe Integer, Maybe Integer) -> TimeZoneSeries ->
[UTCTime]
transitionTimes (lo, hi) =
maybe id (takeWhile . (>)) (fmap happyNewYear hi) .
maybe id (dropWhile . (>)) (fmap happyNewYear lo) .
addBugs . addPrevSecond . actualTransitions
where
happyNewYear y = UTCTime (fromGregorian y 1 1) 0
actualTransitions (TimeZoneSeries d cs) =
let rs = reverse cs
in map snd . filter fst $
zipWith (\(t, tz) prevTz -> (tz /= prevTz, t))
rs
(d : map snd rs)
addPrevSecond = concatMap (\t -> [(-1) `addUTCTime` t, t])
addBugs = (bug0 ++) . (++ bug1) -- sic
bug0 = [UTCTime (fromGregorian 1901 12 13) (20*3600+45*60+52),
UTCTime (fromGregorian 1901 12 14) (20*3600+45*60+52)]
bug1 = [UTCTime (fromGregorian 2038 1 18) ( 3*3600+14*60+ 7),
UTCTime (fromGregorian 2038 1 19) ( 3*3600+14*60+ 7)]
utcTZ :: TimeZoneSeries
utcTZ = TimeZoneSeries utc []
maybeRead :: Read a => String -> Maybe a
maybeRead = fmap fst . listToMaybe . filter (null . snd) . reads