thyme-0.2.0.0: src/Data/Thyme/Format.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Data.Thyme.Format
( FormatTime (..)
, formatTime
, ParseTime (..)
, parseTime
, TimeParse (..)
, timeParser
) where
import Prelude
import Control.Applicative
import Control.Lens
import Control.Monad.Trans.Class
import Control.Monad.Trans.State.Strict
import Data.Attoparsec.ByteString.Char8 (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as P
import Data.Basis
import Data.Bits
import qualified Data.ByteString.Builder as S
import qualified Data.ByteString.Char8 as S
import qualified Data.ByteString.Lazy as SL
import Data.Char
import Data.Micro
import Data.Thyme.Calendar
import Data.Thyme.Calendar.Internal
import Data.Thyme.Calendar.MonthDay
import Data.Thyme.Clock.POSIX
import Data.Thyme.Clock.Scale
import Data.Thyme.Clock.UTC
import Data.Thyme.Format.Internal
import Data.Thyme.LocalTime
import Data.Thyme.TH
import Data.VectorSpace
import System.Locale
type FormatS = Char -> ShowS
class FormatTime t where
showsTime :: TimeLocale -> t -> FormatS -> FormatS
{-# INLINEABLE formatTime #-}
formatTime :: (FormatTime t) => TimeLocale -> String -> t -> String
formatTime l@TimeLocale {..} spec t = go spec "" where
-- leave unrecognised codes as they are
format = showsTime l t (\ c s -> '%' : c : s)
go s = case s of
'%' : c : rest -> case c of
-- aggregate
'c' -> go (dateTimeFmt ++ rest)
'r' -> go (time12Fmt ++ rest)
'X' -> go (timeFmt ++ rest)
'x' -> go (dateFmt ++ rest)
-- modifier (whatever)
'-' -> go ('%' : rest)
'_' -> go ('%' : rest)
'0' -> go ('%' : rest)
'^' -> go ('%' : rest)
'#' -> go ('%' : rest)
-- escape (why would anyone need %t and %n?)
'%' -> (:) '%' . go rest
-- default
_ -> format c . go rest
c : rest -> (:) c . go rest
[] -> id
{-# INLINE showsYear #-}
showsYear :: Year -> ShowS
#if BUG_FOR_BUG
showsYear = shows
#else
-- ISO 8601 says 4 digits, even for first millennium.
showsYear = shows04
#endif
instance FormatTime TimeOfDay where
{-# INLINEABLE showsTime #-}
showsTime TimeLocale {..} (TimeOfDay h m (DiffTime s)) = \ def c -> case c of
-- aggregate
'R' -> shows02 h . (:) ':' . shows02 m
'T' -> shows02 h . (:) ':' . shows02 m . (:) ':' . shows02 si
-- AM/PM
'P' -> (++) $ toLower <$> if h < 12 then fst amPm else snd amPm
'p' -> (++) $ if h < 12 then fst amPm else snd amPm
-- Hour
'H' -> shows02 h
'I' -> shows02 $ 1 + mod (h - 1) 12
'k' -> shows_2 h
'l' -> shows_2 $ 1 + mod (h - 1) 12
-- Minute
'M' -> shows02 m
-- Second
'S' -> shows02 si
'q' -> fills06 su . shows su . (++) "000000"
'Q' -> if su == 0 then id else (:) '.' . fills06 su . drops0 su
-- default
_ -> def c
where (fromIntegral -> si, Micro su) = microQuotRem s (Micro 1000000)
instance FormatTime YearMonthDay where
{-# INLINEABLE showsTime #-}
showsTime TimeLocale {..} (YearMonthDay y m d) = \ def c -> case c of
-- aggregate
'D' -> shows02 m . (:) '/' . shows02 d . (:) '/' . shows02 (mod y 100)
'F' -> showsYear y . (:) '-' . shows02 m . (:) '-' . shows02 d
-- Year
'Y' -> showsYear y
'y' -> shows02 (mod y 100)
'C' -> shows02 (div y 100)
-- Month
'B' -> (++) . fst $ months !! (m - 1)
'b' -> (++) . snd $ months !! (m - 1)
'h' -> (++) . snd $ months !! (m - 1)
'm' -> shows02 m
-- DayOfMonth
'd' -> shows02 d
'e' -> shows_2 d
-- default
_ -> def c
instance FormatTime MonthDay where
{-# INLINEABLE showsTime #-}
showsTime TimeLocale {..} (MonthDay m d) = \ def c -> case c of
-- Month
'B' -> (++) . fst $ months !! (m - 1)
'b' -> (++) . snd $ months !! (m - 1)
'h' -> (++) . snd $ months !! (m - 1)
'm' -> shows02 m
-- DayOfMonth
'd' -> shows02 d
'e' -> shows_2 d
-- default
_ -> def c
instance FormatTime OrdinalDate where
{-# INLINEABLE showsTime #-}
showsTime TimeLocale {..} (OrdinalDate y d) = \ def c -> case c of
-- Year
'Y' -> showsYear y
'y' -> shows02 (mod y 100)
'C' -> shows02 (div y 100)
-- DayOfYear
'j' -> shows03 d
-- default
_ -> def c
instance FormatTime WeekDate where
{-# INLINEABLE showsTime #-}
showsTime TimeLocale {..} (WeekDate y w d) = \ def c -> case c of
-- Year
'G' -> showsYear y
'g' -> shows02 (mod y 100)
'f' -> shows02 (div y 100)
-- WeekOfYear
'V' -> shows02 w
-- DayOfWeek
'u' -> shows $ if d == 0 then 7 else d
'w' -> shows $ if d == 7 then 0 else d
'A' -> (++) . fst $ wDays !! mod d 7
'a' -> (++) . snd $ wDays !! mod d 7
-- default
_ -> def c
instance FormatTime SundayWeek where
{-# INLINEABLE showsTime #-}
showsTime TimeLocale {..} (SundayWeek y w d) = \ def c -> case c of
-- Year
'Y' -> showsYear y
'y' -> shows02 (mod y 100)
'C' -> shows02 (div y 100)
-- WeekOfYear
'U' -> shows02 w
-- DayOfWeek
'u' -> shows $ if d == 0 then 7 else d
'w' -> shows $ if d == 7 then 0 else d
'A' -> (++) . fst $ wDays !! mod d 7
'a' -> (++) . snd $ wDays !! mod d 7
-- default
_ -> def c
instance FormatTime MondayWeek where
{-# INLINEABLE showsTime #-}
showsTime TimeLocale {..} (MondayWeek y w d) = \ def c -> case c of
-- Year
'Y' -> showsYear y
'y' -> shows02 (mod y 100)
'C' -> shows02 (div y 100)
-- WeekOfYear
'W' -> shows02 w
-- DayOfWeek
'u' -> shows $ if d == 0 then 7 else d
'w' -> shows $ if d == 7 then 0 else d
'A' -> (++) . fst $ wDays !! mod d 7
'a' -> (++) . snd $ wDays !! mod d 7
-- default
_ -> def c
instance FormatTime LocalTime where
{-# INLINEABLE showsTime #-}
showsTime l (LocalTime day tod) = showsTime l day . showsTime l tod
instance FormatTime Day where
{-# INLINEABLE showsTime #-}
showsTime l d@(view ordinalDate -> ordinal)
= showsTime l ordinal
. showsTime l (view yearMonthDay ordinal)
. showsTime l (toWeekOrdinal ordinal d)
. showsTime l (toSundayOrdinal ordinal d)
. showsTime l (toMondayOrdinal ordinal d)
instance FormatTime TimeZone where
{-# INLINEABLE showsTime #-}
showsTime _ tz@(TimeZone _ _ name) = \ def c -> case c of
'z' -> (++) (timeZoneOffsetString tz)
'Z' -> (++) (if null name then timeZoneOffsetString tz else name)
_ -> def c
instance FormatTime ZonedTime where
{-# INLINEABLE showsTime #-}
showsTime l (ZonedTime lt tz) = showsTime l lt . showsTime l tz
instance FormatTime UTCTime where
{-# INLINEABLE showsTime #-}
showsTime l t = \ def c -> case c of
's' -> shows . fst $ qr s (Micro 1000000)
_ -> showsTime l (view zonedTime (utc, t)) def c
where
NominalDiffTime s = view posixTime t
#if BUG_FOR_BUG
qr = microDivMod -- rounds down
#else
qr = microQuotRem -- rounds to 0
#endif
------------------------------------------------------------------------
data TimeFlag
= PostMeridiem
| TwelveHour
| HasCentury
| IsPOSIXTime
| IsOrdinalDate
| IsGregorian
| IsWeekDate
| IsSundayWeek
| IsMondayWeek
deriving (Enum, Show)
data TimeParse = TimeParse
{ tpCentury :: {-# UNPACK #-}!Int
, tpCenturyYear :: {-# UNPACK #-}!Int{-YearOfCentury-}
, tpMonth :: {-# UNPACK #-}!Month
, tpWeekOfYear :: {-# UNPACK #-}!WeekOfYear
, tpDayOfMonth :: {-# UNPACK #-}!DayOfMonth
, tpDayOfYear :: {-# UNPACK #-}!DayOfYear
, tpDayOfWeek :: {-# UNPACK #-}!DayOfWeek
, tpFlags :: {-# UNPACK #-}!Int{-BitSet TimeFlag-}
, tpHour :: {-# UNPACK #-}!Hour
, tpMinute :: {-# UNPACK #-}!Minute
, tpSecond :: {-# UNPACK #-}!Int
, tpSecFrac :: {-# UNPACK #-}!DiffTime
, tpPOSIXTime :: {-# UNPACK #-}!POSIXTime
, tpTimeZone :: !TimeZone
} deriving (Show)
thymeLenses ''TimeParse
{-# INLINE flag #-}
flag :: TimeFlag -> Simple Lens TimeParse Bool
flag (fromEnum -> f) = _tpFlags . lens
(`testBit` f) (\ n b -> (if b then setBit else clearBit) n f)
-- | Time 'Parser' for UTF-8 encoded 'ByteString's.
--
-- Attoparsec easily beats any 'String' parser out there, but we do have to
-- be careful to convert the input to UTF-8 'ByteString's.
{-# INLINEABLE timeParser #-}
timeParser :: TimeLocale -> String -> Parser TimeParse
timeParser TimeLocale {..} = flip execStateT unixEpoch . go where
go :: String -> StateT TimeParse Parser ()
go spec = case spec of
'%' : cspec : rspec -> case cspec of
-- aggregate
'c' -> go (dateTimeFmt ++ rspec)
'r' -> go (time12Fmt ++ rspec)
'X' -> go (timeFmt ++ rspec)
'x' -> go (dateFmt ++ rspec)
'R' -> go ("%H:%M" ++ rspec)
'T' -> go ("%H:%M:%S" ++ rspec)
'D' -> go ("%m/%d/%y" ++ rspec)
'F' -> go ("%Y-%m-%d" ++ rspec)
-- AM/PM
'P' -> dayHalf
'p' -> dayHalf
-- Hour
'H' -> lift (dec0 2) >>= setHour24
'I' -> lift (dec0 2) >>= setHour12
'k' -> lift (dec_ 2 <|> dec_ 1) >>= setHour24
'l' -> lift (dec_ 2 <|> dec_ 1) >>= setHour12
-- Minute
'M' -> lift (dec0 2) >>= assign _tpMinute >> go rspec
-- Second
'S' -> lift (dec0 2) >>= assign _tpSecond >> go rspec
'q' -> lift micro >>= assign _tpSecFrac . DiffTime >> go rspec
'Q' -> lift ((P.char '.' >> DiffTime <$> micro) <|> return zeroV)
>>= assign _tpSecFrac >> go rspec
-- Year
-- FIXME: should full years / centuries be fixed width?
'Y' -> lift (dec0 4) >>= setYear
'y' -> lift (dec0 2) >>= setCenturyYear
'C' -> lift (dec0 2) >>= setCentury
-- Month
'B' -> lift (indexOfCI $ fst <$> months) >>= setMonth . succ
'b' -> lift (indexOfCI $ snd <$> months) >>= setMonth . succ
'h' -> lift (indexOfCI $ snd <$> months) >>= setMonth . succ
'm' -> lift (dec0 2) >>= setMonth
-- DayOfMonth
'd' -> lift (dec0 2) >>= setDayOfMonth
'e' -> lift (dec_ 2 <|> dec_ 1) >>= setDayOfMonth
-- DayOfYear
'j' -> lift (dec0 3) >>= assign _tpDayOfYear
>> flag IsOrdinalDate .= True >> go rspec
-- Year (WeekDate)
-- FIXME: problematic if input contains both %Y and %G
'G' -> flag IsWeekDate .= True >> lift (dec0 4) >>= setYear
'g' -> flag IsWeekDate .= True >> lift (dec0 2) >>= setCenturyYear
'f' -> flag IsWeekDate .= True >> lift (dec0 2) >>= setCentury
-- WeekOfYear
-- FIXME: problematic if more than one of the following
'V' -> flag IsWeekDate .= True >> lift (dec0 2) >>= setWeekOfYear
'U' -> flag IsSundayWeek .= True >> lift (dec0 2) >>= setWeekOfYear
'W' -> flag IsMondayWeek .= True >> lift (dec0 2) >>= setWeekOfYear
-- DayOfWeek
'w' -> lift (dec0 1) >>= setDayOfWeek
'u' -> lift (dec0 1) >>= setDayOfWeek
'A' -> lift (indexOfCI $ fst <$> wDays) >>= setDayOfWeek
'a' -> lift (indexOfCI $ snd <$> wDays) >>= setDayOfWeek
-- TimeZone
'z' -> do tzOffset; go rspec
'Z' -> do tzOffset <|> tzName; go rspec
-- UTCTime
's' -> do
s <- lift (negative P.decimal)
_tpPOSIXTime .= fromIntegral s *^ basisValue ()
flag IsPOSIXTime .= True
go rspec
-- modifier (whatever)
'-' -> go ('%' : rspec)
'_' -> go ('%' : rspec)
'0' -> go ('%' : rspec)
-- escape (why would anyone need %t and %n?)
'%' -> lift (P.char '%') >> go rspec
_ -> lift . fail $ "Unknown format character: " ++ show cspec
where
dayHalf = do
pm <- lift $ False <$ stringCI (fst amPm)
<|> True <$ stringCI (snd amPm)
flag PostMeridiem .= pm
flag TwelveHour .= True
go rspec
setHour12 h = do
flag TwelveHour .= True
_tpHour .= h
go rspec
setHour24 h = do
flag TwelveHour .= False
_tpHour .= h
go rspec
setYear ((`divMod` 100) -> (c, y)) = do
flag HasCentury .= True
_tpCentury .= c
_tpCenturyYear .= y
go rspec
setCenturyYear y = do _tpCenturyYear .= y; go rspec
setCentury c = do
_tpCentury .= c
flag HasCentury .= True
go rspec
setMonth m = do
flag IsGregorian .= True
_tpMonth .= m
go rspec
setDayOfMonth d = do
flag IsGregorian .= True
_tpDayOfMonth .= d
go rspec
setWeekOfYear w = do _tpWeekOfYear .= w; go rspec
setDayOfWeek d = do _tpDayOfWeek .= d; go rspec
tzOffset = do
s <- lift (id <$ P.char '+' <|> negate <$ P.char '-')
h <- lift (dec0 2)
() <$ lift (P.char ':') <|> pure ()
m <- lift (dec0 2)
_tpTimeZone . _timeZoneMinutes .= s (h * 60 + m)
tzName = lift timeZoneParser >>= assign _tpTimeZone
c : rspec | P.isSpace c ->
lift (P.takeWhile P.isSpace) >> go (dropWhile P.isSpace rspec)
c : rspec | isAscii c -> lift (P.char c) >> go rspec
c : rspec -> lift (charU8 c) >> go rspec
"" -> lift P.skipSpace
{-# INLINE micro #-}
micro :: Parser Micro
micro = do
us10 <- either fail return . P.parseOnly P.decimal . S.take 7
. (`S.append` S.pack "000000") =<< P.takeWhile1 isDigit
return $ Micro (div (us10 + 5) 10)
{-# INLINE unixEpoch #-}
unixEpoch :: TimeParse
unixEpoch = TimeParse {..} where
tpCentury = 19
tpCenturyYear = 70
tpMonth = 1
tpWeekOfYear = 1
tpDayOfYear = 1
tpDayOfMonth = 1
tpDayOfWeek = 4
tpFlags = 0
tpHour = 0
tpMinute = 0
tpSecond = 0
tpSecFrac = zeroV
tpPOSIXTime = zeroV
tpTimeZone = utc
{-# INLINEABLE parseTime #-}
parseTime :: (ParseTime t) => TimeLocale -> String -> String -> Maybe t
parseTime l spec = either (const Nothing) Just
. P.parseOnly (buildTime <$> timeParser l spec)
. SL.toStrict . S.toLazyByteString . S.stringUtf8
class ParseTime t where
buildTime :: TimeParse -> t
instance ParseTime TimeOfDay where
{-# INLINE buildTime #-}
buildTime tp@TimeParse {..} = TimeOfDay h tpMinute
(fromIntegral tpSecond *^ basisValue () ^+^ tpSecFrac) where
h = case tp ^. flag TwelveHour of
False -> tpHour
True -> case tp ^. flag PostMeridiem of
False -> mod tpHour 12
True -> if tpHour < 12 then tpHour + 12 else tpHour
{-# INLINE tpYear #-}
tpYear :: TimeParse -> Year
tpYear tp@TimeParse {..} = tpCenturyYear + 100 * if tp ^. flag HasCentury
then tpCentury else if tpCenturyYear < 69 then 20 else 19
instance ParseTime YearMonthDay where
{-# INLINE buildTime #-}
buildTime tp@TimeParse {..} = YearMonthDay (tpYear tp) tpMonth tpDayOfMonth
instance ParseTime MonthDay where
{-# INLINE buildTime #-}
buildTime TimeParse {..} = MonthDay tpMonth tpDayOfMonth
instance ParseTime OrdinalDate where
{-# INLINE buildTime #-}
buildTime tp@TimeParse {..} = OrdinalDate (tpYear tp) tpDayOfYear
instance ParseTime WeekDate where
{-# INLINE buildTime #-}
buildTime tp@TimeParse {..} = WeekDate (tpYear tp) tpWeekOfYear
(if tpDayOfWeek == 0 then 7 else tpDayOfWeek)
instance ParseTime SundayWeek where
{-# INLINE buildTime #-}
buildTime tp@TimeParse {..} = SundayWeek (tpYear tp) tpWeekOfYear
(if tpDayOfWeek == 7 then 0 else tpDayOfWeek)
instance ParseTime MondayWeek where
{-# INLINE buildTime #-}
buildTime tp@TimeParse {..} = MondayWeek (tpYear tp) tpWeekOfYear
(if tpDayOfWeek == 0 then 7 else tpDayOfWeek)
instance ParseTime LocalTime where
{-# INLINE buildTime #-}
buildTime = LocalTime <$> buildTime <*> buildTime
instance ParseTime Day where
{-# INLINE buildTime #-}
buildTime tp@TimeParse {..}
| tp ^. flag IsOrdinalDate = review ordinalDate (buildTime tp)
| tp ^. flag IsGregorian = review gregorian (buildTime tp)
| tp ^. flag IsWeekDate = review weekDate (buildTime tp)
| tp ^. flag IsSundayWeek = review sundayWeek (buildTime tp)
| tp ^. flag IsMondayWeek = review mondayWeek (buildTime tp)
| otherwise = review ordinalDate (buildTime tp)
-- TODO: Better conflict handling when multiple flags are set?
instance ParseTime TimeZone where
{-# INLINE buildTime #-}
buildTime = tpTimeZone
instance ParseTime ZonedTime where
{-# INLINE buildTime #-}
buildTime = ZonedTime <$> buildTime <*> buildTime
instance ParseTime UTCTime where
{-# INLINE buildTime #-}
buildTime tp@TimeParse {..} = if tp ^. flag IsPOSIXTime
then review posixTime tpPOSIXTime
else view (from zonedTime . _2) (buildTime tp)
------------------------------------------------------------------------
-- Dubiously pilfered from time-1.4.0.2
-- s/^.*-- \(.*\)\n.*\("[A-Z]\+"\).*"\([+-]\)\([0-9]\{2\}\):\([0-9]\{2\}\)", \(True\|False\).*$/ <|> zone \2 (($\3) \4 \5) \6 -- \1/
-- followed by !sort -r , because some names are prefixes of others.
timeZoneParser :: Parser TimeZone
timeZoneParser
= zone "ZULU" (($+) 00 00) False -- Same as UTC
<|> zone "Z" (($+) 00 00) False -- Same as UTC
<|> zone "YST" (($-) 09 00) False -- Yukon Standard Time
<|> zone "YDT" (($-) 08 00) True -- Yukon Daylight-Saving Time
<|> zone "WST" (($+) 08 00) False -- West Australian Standard Time
<|> zone "WETDST" (($+) 01 00) True -- Western European Daylight-Saving Time
<|> zone "WET" (($+) 00 00) False -- Western European Time
<|> zone "WDT" (($+) 09 00) True -- West Australian Daylight-Saving Time
<|> zone "WAT" (($-) 01 00) False -- West Africa Time
<|> zone "WAST" (($+) 07 00) False -- West Australian Standard Time
<|> zone "WADT" (($+) 08 00) True -- West Australian Daylight-Saving Time
<|> zone "UTC" (($+) 00 00) False -- Universal Coordinated Time
<|> zone "UT" (($+) 00 00) False -- Universal Time
<|> zone "TFT" (($+) 05 00) False -- Kerguelen Time
<|> zone "SWT" (($+) 01 00) False -- Swedish Winter Time
<|> zone "SST" (($+) 02 00) False -- Swedish Summer Time
<|> zone "SET" (($+) 01 00) False -- Seychelles Time
<|> zone "SCT" (($+) 04 00) False -- Mahe Island Time
<|> zone "SAST" (($+) 09 30) False -- South Australia Standard Time
<|> zone "SADT" (($+) 10 30) True -- South Australian Daylight-Saving Time
<|> zone "RET" (($+) 04 00) False -- Reunion Island Time
<|> zone "PST" (($-) 08 00) False -- Pacific Standard Time
<|> zone "PDT" (($-) 07 00) True -- Pacific Daylight-Saving Time
<|> zone "NZT" (($+) 12 00) False -- New Zealand Time
<|> zone "NZST" (($+) 12 00) False -- New Zealand Standard Time
<|> zone "NZDT" (($+) 13 00) True -- New Zealand Daylight-Saving Time
<|> zone "NT" (($-) 11 00) False -- Nome Time
<|> zone "NST" (($-) 03 30) False -- Newfoundland Standard Time
<|> zone "NOR" (($+) 01 00) False -- Norway Standard Time
<|> zone "NFT" (($-) 03 30) False -- Newfoundland Standard Time
<|> zone "NDT" (($-) 02 30) True -- Newfoundland Daylight-Saving Time
<|> zone "MVT" (($+) 05 00) False -- Maldives Island Time
<|> zone "MUT" (($+) 04 00) False -- Mauritius Island Time
<|> zone "MT" (($+) 08 30) False -- Moluccas Time
<|> zone "MST" (($-) 07 00) False -- Mountain Standard Time
<|> zone "MMT" (($+) 06 30) False -- Myanmar Time
<|> zone "MHT" (($+) 09 00) False -- Kwajalein Time
<|> zone "MEZ" (($+) 01 00) False -- Mitteleuropaeische Zeit
<|> zone "MEWT" (($+) 01 00) False -- Middle European Winter Time
<|> zone "METDST" (($+) 02 00) True -- Middle Europe Daylight-Saving Time
<|> zone "MET" (($+) 01 00) False -- Middle European Time
<|> zone "MEST" (($+) 02 00) False -- Middle European Summer Time
<|> zone "MDT" (($-) 06 00) True -- Mountain Daylight-Saving Time
<|> zone "MAWT" (($+) 06 00) False -- Mawson (Antarctica) Time
<|> zone "MART" (($-) 09 30) False -- Marquesas Time
<|> zone "LIGT" (($+) 10 00) False -- Melbourne, Australia
<|> zone "KST" (($+) 09 00) False -- Korea Standard Time
<|> zone "JT" (($+) 07 30) False -- Java Time
<|> zone "JST" (($+) 09 00) False -- Japan Standard Time, Russia zone 8
<|> zone "IT" (($+) 03 30) False -- Iran Time
<|> zone "IST" (($+) 02 00) False -- Israel Standard Time
<|> zone "IRT" (($+) 03 30) False -- Iran Time
<|> zone "IOT" (($+) 05 00) False -- Indian Chagos Time
<|> zone "IDLW" (($-) 12 00) False -- International Date Line, West
<|> zone "IDLE" (($+) 12 00) False -- International Date Line, East
<|> zone "HST" (($-) 10 00) False -- Hawaii Standard Time
<|> zone "HMT" (($+) 03 00) False -- Hellas Mediterranean Time (?)
<|> zone "HDT" (($-) 09 00) True -- Hawaii/Alaska Daylight-Saving Time
<|> zone "GST" (($+) 10 00) False -- Guam Standard Time, Russia zone 9
<|> zone "GMT" (($+) 00 00) False -- Greenwich Mean Time
<|> zone "FWT" (($+) 02 00) False -- French Winter Time
<|> zone "FST" (($+) 01 00) False -- French Summer Time
<|> zone "FNT" (($-) 02 00) False -- Fernando de Noronha Time
<|> zone "FNST" (($-) 01 00) False -- Fernando de Noronha Summer Time
<|> zone "EST" (($-) 05 00) False -- Eastern Standard Time
<|> zone "EETDST" (($+) 03 00) True -- Eastern Europe Daylight-Saving Time
<|> zone "EET" (($+) 02 00) False -- Eastern European Time, Russia zone 1
<|> zone "EDT" (($-) 04 00) True -- Eastern Daylight-Saving Time
<|> zone "EAT" (($+) 03 00) False -- Antananarivo, Comoro Time
<|> zone "EAST" (($+) 10 00) False -- East Australian Standard Time
<|> zone "EAST" (($+) 04 00) False -- Antananarivo Summer Time
<|> zone "DNT" (($+) 01 00) False -- Dansk Normal Tid
<|> zone "CXT" (($+) 07 00) False -- Christmas (Island) Time
<|> zone "CST" (($-) 06 00) False -- Central Standard Time
<|> zone "CETDST" (($+) 02 00) True -- Central European Daylight-Saving Time
<|> zone "CET" (($+) 01 00) False -- Central European Time
<|> zone "CEST" (($+) 02 00) False -- Central European Summer Time
<|> zone "CDT" (($-) 05 00) True -- Central Daylight-Saving Time
<|> zone "CCT" (($+) 08 00) False -- China Coastal Time
<|> zone "CAT" (($-) 10 00) False -- Central Alaska Time
<|> zone "CAST" (($+) 09 30) False -- Central Australia Standard Time
<|> zone "CADT" (($+) 10 30) True -- Central Australia Daylight-Saving Time
<|> zone "BT" (($+) 03 00) False -- Baghdad Time
<|> zone "BST" (($+) 01 00) False -- British Summer Time
<|> zone "BRT" (($-) 03 00) False -- Brasilia Time
<|> zone "BRST" (($-) 02 00) False -- Brasilia Summer Time
<|> zone "BDST" (($+) 02 00) False -- British Double Summer Time
<|> zone "AWT" (($-) 03 00) False -- (unknown)
<|> zone "AWST" (($+) 08 00) False -- Australia Western Standard Time
<|> zone "AWSST" (($+) 09 00) False -- Australia Western Summer Standard Time
<|> zone "AST" (($-) 04 00) False -- Atlantic Standard Time (Canada)
<|> zone "ALMT" (($+) 06 00) False -- Almaty Time
<|> zone "ALMST" (($+) 07 00) False -- Almaty Summer Time
<|> zone "AKST" (($-) 09 00) False -- Alaska Standard Time
<|> zone "AKDT" (($-) 08 00) True -- Alaska Daylight-Saving Time
<|> zone "AHST" (($-) 10 00) False -- Alaska/Hawaii Standard Time
<|> zone "AFT" (($+) 04 30) False -- Afghanistan Time
<|> zone "AEST" (($+) 10 00) False -- Australia Eastern Standard Time
<|> zone "AESST" (($+) 11 00) False -- Australia Eastern Summer Standard Time
<|> zone "ADT" (($-) 03 00) True -- Atlantic Daylight-Saving Time
<|> zone "ACT" (($-) 05 00) False -- Atlantic/Porto Acre Standard Time
<|> zone "ACST" (($-) 04 00) False -- Atlantic/Porto Acre Summer Time
<|> zone "ACSST" (($+) 10 30) False -- Central Australia Summer Standard Time
where
zone name offset dst = TimeZone offset dst name <$ P.string (S.pack name)
($+) h m = h * 60 + m
($-) h m = negate (h * 60 + m)