time 1.8.0.4 → 1.9
raw patch · 39 files changed
+2890/−959 lines, 39 filesdep ~base
Dependency ranges changed: base
Files
- changelog.md +13/−0
- configure.ac +1/−1
- lib/Data/Format.hs +256/−0
- lib/Data/Time.hs +4/−1
- lib/Data/Time/Calendar.hs +5/−1
- lib/Data/Time/Calendar/CalendarDiffDays.hs +52/−0
- lib/Data/Time/Calendar/Days.hs +0/−1
- lib/Data/Time/Calendar/Gregorian.hs +42/−2
- lib/Data/Time/Calendar/Julian.hs +43/−1
- lib/Data/Time/Calendar/JulianYearDay.hs +0/−1
- lib/Data/Time/Calendar/Private.hs +12/−1
- lib/Data/Time/Calendar/Week.hs +47/−0
- lib/Data/Time/Clock/Internal/DiffTime.hs +0/−1
- lib/Data/Time/Clock/Internal/NominalDiffTime.hs +0/−1
- lib/Data/Time/Clock/Internal/UTCDiff.hs +0/−1
- lib/Data/Time/Format.hs +3/−338
- lib/Data/Time/Format/Format/Class.hs +349/−0
- lib/Data/Time/Format/Format/Instances.hs +188/−0
- lib/Data/Time/Format/ISO8601.hs +388/−0
- lib/Data/Time/Format/Locale.hs +1/−1
- lib/Data/Time/Format/Parse.hs +27/−486
- lib/Data/Time/Format/Parse/Class.hs +220/−0
- lib/Data/Time/Format/Parse/Instances.hs +392/−0
- lib/Data/Time/LocalTime.hs +2/−0
- lib/Data/Time/LocalTime/Internal/CalendarDiffTime.hs +46/−0
- lib/Data/Time/LocalTime/Internal/LocalTime.hs +12/−2
- lib/Data/Time/LocalTime/Internal/TimeOfDay.hs +16/−1
- lib/Data/Time/LocalTime/Internal/TimeZone.hs +13/−10
- lib/Data/Time/LocalTime/Internal/ZonedTime.hs +0/−2
- test/main/Main.hs +13/−3
- test/main/Test/Arbitrary.hs +107/−0
- test/main/Test/Calendar/Duration.hs +46/−0
- test/main/Test/Calendar/Week.hs +93/−0
- test/main/Test/Format/Format.hs +117/−32
- test/main/Test/Format/ISO8601.hs +290/−0
- test/main/Test/Format/ParseTime.hs +34/−69
- test/main/Test/LocalTime/CalendarDiffTime.hs +18/−0
- test/main/Test/LocalTime/TimeOfDay.hs +22/−0
- time.cabal +18/−3
changelog.md view
@@ -1,5 +1,18 @@ # Change Log +## [1.9]+- new conversion functions timeToDaysAndTimeOfDay & daysAndTimeOfDayToTime+- new DayOfWeek type+- new CalendarDiffDays and CalendarDiffTime types+- new ISO8601 module for ISO 8601 formatting & parsing+- new addLocalTime, diffLocalTime+- hide members of FormatTime and ParseTime classes+- formatting & parsing for diff types (NominalDiffTime, DiffTime, CalendarDiffDays, CalendarDiffTime)+- formatting: %Ez and %EZ for ±HH:MM format+- parseTimeM: use MonadFail constraint when supported+- parsing: reject invalid (and empty) time-zones with %z and %Z+- parsing: reject invalid hour/minute/second specifiers+ ## [1.8.0.4] - Fix "show minBound" bug - haddock: example for parseTimeM
configure.ac view
@@ -1,4 +1,4 @@-AC_INIT([Haskell time package], [1.8.0.4], [ashley@semantic.org], [time])+AC_INIT([Haskell time package], [1.9], [ashley@semantic.org], [time]) # Safety check: Ensure that we are in the correct source directory. AC_CONFIG_SRCDIR([lib/include/HsTime.h])
+ lib/Data/Format.hs view
@@ -0,0 +1,256 @@+module Data.Format+ ( Productish(..)+ , Summish(..)+ , parseReader+ , Format(..)+ , formatShow+ , formatParseM+ , isoMap+ , mapMFormat+ , filterFormat+ , clipFormat+ , enumMap+ , literalFormat+ , specialCaseShowFormat+ , specialCaseFormat+ , optionalFormat+ , casesFormat+ , optionalSignFormat+ , mandatorySignFormat+ , SignOption(..)+ , integerFormat+ , decimalFormat+ ) where++#if MIN_VERSION_base(4,9,0)+import Control.Monad.Fail+import Prelude hiding (fail)+#endif+#if MIN_VERSION_base(4,8,0)+import Data.Void+#endif+import Data.Char+import Text.ParserCombinators.ReadP+++#if MIN_VERSION_base(4,8,0)+#else+data Void+absurd :: Void -> a+absurd v = seq v $ error "absurd"+#endif++class IsoVariant f where+ isoMap :: (a -> b) -> (b -> a) -> f a -> f b++enumMap :: (IsoVariant f,Enum a) => f Int -> f a+enumMap = isoMap toEnum fromEnum++infixr 3 <**>, **>, <**+class IsoVariant f => Productish f where+ pUnit :: f ()+ (<**>) :: f a -> f b -> f (a,b)+ (**>) :: f () -> f a -> f a+ fu **> fa = isoMap (\((),a) -> a) (\a -> ((),a)) $ fu <**> fa+ (<**) :: f a -> f () -> f a+ fa <** fu = isoMap (\(a,()) -> a) (\a -> (a,())) $ fa <**> fu++infixr 2 <++>+class IsoVariant f => Summish f where+ pVoid :: f Void+ (<++>) :: f a -> f b -> f (Either a b)+++parseReader :: (+#if MIN_VERSION_base(4,9,0)+ MonadFail m+#else+ Monad m+#endif+ ) => ReadP t -> String -> m t+parseReader readp s = case [ t | (t,"") <- readP_to_S readp s] of+ [t] -> return t+ [] -> fail $ "no parse of " ++ show s+ _ -> fail $ "multiple parses of " ++ show s++-- | A text format for a type+data Format t = MkFormat+ { formatShowM :: t -> Maybe String+ -- ^ Show a value in the format, if representable+ , formatReadP :: ReadP t+ -- ^ Read a value in the format+ }++-- | Show a value in the format, or error if unrepresentable+formatShow :: Format t -> t -> String+formatShow fmt t = case formatShowM fmt t of+ Just str -> str+ Nothing -> error "formatShow: bad value"++-- | Parse a value in the format+formatParseM :: (+#if MIN_VERSION_base(4,9,0)+ MonadFail m+#else+ Monad m+#endif+ ) => Format t -> String -> m t+formatParseM format = parseReader $ formatReadP format++instance IsoVariant Format where+ isoMap ab ba (MkFormat sa ra) = MkFormat (\b -> sa $ ba b) (fmap ab ra)++mapMFormat :: (a -> Maybe b) -> (b -> Maybe a) -> Format a -> Format b+mapMFormat amb bma (MkFormat sa ra) = MkFormat (\b -> bma b >>= sa) $ do+ a <- ra+ case amb a of+ Just b -> return b+ Nothing -> pfail++filterFormat :: (a -> Bool) -> Format a -> Format a+filterFormat test = mapMFormat (\a -> if test a then Just a else Nothing) (\a -> if test a then Just a else Nothing)++-- | Limits are inclusive+clipFormat :: Ord a => (a,a) -> Format a -> Format a+clipFormat (lo,hi) = filterFormat (\a -> a >= lo && a <= hi)++instance Productish Format where+ pUnit = MkFormat {formatShowM = \_ -> Just "", formatReadP = return ()}+ (<**>) (MkFormat sa ra) (MkFormat sb rb) = let+ sab (a, b) = do+ astr <- sa a+ bstr <- sb b+ return $ astr ++ bstr+ rab = do+ a <- ra+ b <- rb+ return (a, b)+ in MkFormat sab rab+ (MkFormat sa ra) **> (MkFormat sb rb) = let+ s b = do+ astr <- sa ()+ bstr <- sb b+ return $ astr ++ bstr+ r = do+ ra+ rb+ in MkFormat s r+ (MkFormat sa ra) <** (MkFormat sb rb) = let+ s a = do+ astr <- sa a+ bstr <- sb ()+ return $ astr ++ bstr+ r = do+ a <- ra+ rb+ return a+ in MkFormat s r++instance Summish Format where+ pVoid = MkFormat absurd pfail+ (MkFormat sa ra) <++> (MkFormat sb rb) = let+ sab (Left a) = sa a+ sab (Right b) = sb b+ rab = (fmap Left ra) +++ (fmap Right rb)+ in MkFormat sab rab++literalFormat :: String -> Format ()+literalFormat s = MkFormat {formatShowM = \_ -> Just s, formatReadP = string s >> return ()}++specialCaseShowFormat :: Eq a => (a,String) -> Format a -> Format a+specialCaseShowFormat (val,str) (MkFormat s r) = let+ s' t | t == val = Just str+ s' t = s t+ in MkFormat s' r++specialCaseFormat :: Eq a => (a,String) -> Format a -> Format a+specialCaseFormat (val,str) (MkFormat s r) = let+ s' t | t == val = Just str+ s' t = s t+ r' = (string str >> return val) +++ r+ in MkFormat s' r'++optionalFormat :: Eq a => a -> Format a -> Format a+optionalFormat val = specialCaseFormat (val,"")++casesFormat :: Eq a => [(a,String)] -> Format a+casesFormat pairs = let+ s t = lookup t pairs+ r [] = pfail+ r ((v,str):pp) = (string str >> return v) <++ r pp+ in MkFormat s $ r pairs++optionalSignFormat :: (Eq t,Num t) => Format t+optionalSignFormat = casesFormat+ [+ (1,""),+ (1,"+"),+ (0,""),+ (-1,"-")+ ]++mandatorySignFormat :: (Eq t,Num t) => Format t+mandatorySignFormat = casesFormat+ [+ (1,"+"),+ (0,"+"),+ (-1,"-")+ ]++data SignOption+ = NoSign+ | NegSign+ | PosNegSign++readSign :: Num t => SignOption -> ReadP (t -> t)+readSign NoSign = return id+readSign NegSign = option id $ char '-' >> return negate+readSign PosNegSign = (char '+' >> return id) +++ (char '-' >> return negate)++readNumber :: (Num t, Read t) => SignOption -> Maybe Int -> Bool -> ReadP t+readNumber signOpt mdigitcount allowDecimal = do+ sign <- readSign signOpt+ digits <-+ case mdigitcount of+ Just digitcount -> count digitcount $ satisfy isDigit+ Nothing -> many1 $ satisfy isDigit+ moredigits <-+ case allowDecimal of+ False -> return ""+ True ->+ option "" $ do+ _ <- char '.' +++ char ','+ dd <- many1 (satisfy isDigit)+ return $ '.' : dd+ return $ sign $ read $ digits ++ moredigits++zeroPad :: Maybe Int -> String -> String+zeroPad Nothing s = s+zeroPad (Just i) s = replicate (i - length s) '0' ++ s++trimTrailing :: String -> String+trimTrailing "" = ""+trimTrailing "." = ""+trimTrailing s | last s == '0' = trimTrailing $ init s+trimTrailing s = s++showNumber :: Show t => SignOption -> Maybe Int -> t -> Maybe String+showNumber signOpt mdigitcount t = let+ showIt str = let+ (intPart, decPart) = break ((==) '.') str+ in (zeroPad mdigitcount intPart) ++ trimTrailing decPart+ in case show t of+ ('-':str) ->+ case signOpt of+ NoSign -> Nothing+ _ -> Just $ '-' : showIt str+ str ->+ Just $ case signOpt of+ PosNegSign -> '+' : showIt str+ _ -> showIt str++integerFormat :: (Show t,Read t,Num t) => SignOption -> Maybe Int -> Format t+integerFormat signOpt mdigitcount = MkFormat (showNumber signOpt mdigitcount) (readNumber signOpt mdigitcount False)++decimalFormat :: (Show t,Read t,Num t) => SignOption -> Maybe Int -> Format t+decimalFormat signOpt mdigitcount = MkFormat (showNumber signOpt mdigitcount) (readNumber signOpt mdigitcount True)
lib/Data/Time.hs view
@@ -7,13 +7,16 @@ * 'UTCTime' for actual times * 'NominalDiffTime' for differences between times, i.e. durations -Use these types for the ways people refer to time:+Use these types for the ways people refer to time and time differences: * 'Day' for something like June 27th 2017+* 'DayOfWeek' for something like Tuesday * 'TimeOfDay' for something like 5pm * 'LocalTime' for a 'Day' with a 'TimeOfDay' * 'TimeZone' for a time zone offset (not actually the time zone itself) like -0700 * 'ZonedTime' for a 'LocalTime' with a 'TimeZone'+* 'CalendarDiffDays' for something like 6 years, 1 month and 5 days+* 'CalendarDiffTime' for something like 6 years, 1 month, 5 days, 3 hours, 7 minutes and 25.784 seconds Use this for low-latency timing:
lib/Data/Time/Calendar.hs view
@@ -1,9 +1,13 @@ module Data.Time.Calendar ( module Data.Time.Calendar.Days,- module Data.Time.Calendar.Gregorian+ module Data.Time.Calendar.CalendarDiffDays,+ module Data.Time.Calendar.Gregorian,+ module Data.Time.Calendar.Week ) where import Data.Time.Calendar.Days+import Data.Time.Calendar.CalendarDiffDays import Data.Time.Calendar.Gregorian+import Data.Time.Calendar.Week import Data.Time.Format()
+ lib/Data/Time/Calendar/CalendarDiffDays.hs view
@@ -0,0 +1,52 @@+module Data.Time.Calendar.CalendarDiffDays+ (+ -- * Calendar Duration+ module Data.Time.Calendar.CalendarDiffDays+ ) where++#if MIN_VERSION_base(4,8,0)+#else+import Data.Monoid+#endif+#if MIN_VERSION_base(4,9,0)+import Data.Semigroup hiding (option)+#endif++data CalendarDiffDays = CalendarDiffDays+ { cdMonths :: Integer+ , cdDays :: Integer+ } deriving Eq++#if MIN_VERSION_base(4,9,0)+-- | Additive+instance Semigroup CalendarDiffDays where+ CalendarDiffDays m1 d1 <> CalendarDiffDays m2 d2 = CalendarDiffDays (m1 + m2) (d1 + d2)+#endif++-- | Additive+instance Monoid CalendarDiffDays where+ mempty = CalendarDiffDays 0 0+#if MIN_VERSION_base(4,9,0)+ mappend = (<>)+#else+ mappend (CalendarDiffDays m1 d1) (CalendarDiffDays m2 d2) = CalendarDiffDays (m1 + m2) (d1 + d2)+#endif++instance Show CalendarDiffDays where+ show (CalendarDiffDays m d) = "P" ++ show m ++ "M" ++ show d ++ "D"++calendarDay :: CalendarDiffDays+calendarDay = CalendarDiffDays 0 1++calendarWeek :: CalendarDiffDays+calendarWeek = CalendarDiffDays 0 7++calendarMonth :: CalendarDiffDays+calendarMonth = CalendarDiffDays 1 0++calendarYear :: CalendarDiffDays+calendarYear = CalendarDiffDays 12 0++-- | Scale by a factor. Note that @scaleCalendarDiffDays (-1)@ will not perfectly invert a duration, due to variable month lengths.+scaleCalendarDiffDays :: Integer -> CalendarDiffDays -> CalendarDiffDays+scaleCalendarDiffDays k (CalendarDiffDays m d) = CalendarDiffDays (k * m) (k * d)
lib/Data/Time/Calendar/Days.hs view
@@ -1,6 +1,5 @@ {-# OPTIONS -fno-warn-unused-imports #-} #include "HsConfigure.h"--- #hide module Data.Time.Calendar.Days ( -- * Days
lib/Data/Time/Calendar/Gregorian.hs view
@@ -1,6 +1,4 @@ {-# OPTIONS -fno-warn-orphans #-}---- #hide module Data.Time.Calendar.Gregorian ( -- * Gregorian calendar@@ -10,6 +8,8 @@ -- e.g. "one month after March 31st" addGregorianMonthsClip,addGregorianMonthsRollOver, addGregorianYearsClip,addGregorianYearsRollOver,+ addGregorianDurationClip,addGregorianDurationRollOver,+ diffGregorianDurationClip,diffGregorianDurationRollOver, -- re-exported from OrdinalDate isLeapYear@@ -18,6 +18,7 @@ import Data.Time.Calendar.MonthDay import Data.Time.Calendar.OrdinalDate import Data.Time.Calendar.Days+import Data.Time.Calendar.CalendarDiffDays import Data.Time.Calendar.Private -- | Convert to proleptic Gregorian calendar. First element of result is year, second month number (1-12), third day (1-31).@@ -76,6 +77,45 @@ -- For instance, 2004-02-29 + 2 years = 2006-03-01. addGregorianYearsRollOver :: Integer -> Day -> Day addGregorianYearsRollOver n = addGregorianMonthsRollOver (n * 12)++-- | Add months (clipped to last day), then add days+addGregorianDurationClip :: CalendarDiffDays -> Day -> Day+addGregorianDurationClip (CalendarDiffDays m d) day = addDays d $ addGregorianMonthsClip m day++-- | Add months (rolling over to next month), then add days+addGregorianDurationRollOver :: CalendarDiffDays -> Day -> Day+addGregorianDurationRollOver (CalendarDiffDays m d) day = addDays d $ addGregorianMonthsRollOver m day++-- | Calendrical difference, with as many whole months as possible+diffGregorianDurationClip :: Day -> Day -> CalendarDiffDays+diffGregorianDurationClip day2 day1 = let+ (y1,m1,d1) = toGregorian day1+ (y2,m2,d2) = toGregorian day2+ ym1 = y1 * 12 + toInteger m1+ ym2 = y2 * 12 + toInteger m2+ ymdiff = ym2 - ym1+ ymAllowed =+ if day2 >= day1 then+ if d2 >= d1 then ymdiff else ymdiff - 1+ else if d2 <= d1 then ymdiff else ymdiff + 1+ dayAllowed = addGregorianDurationClip (CalendarDiffDays ymAllowed 0) day1+ in CalendarDiffDays ymAllowed $ diffDays day2 dayAllowed++-- | Calendrical difference, with as many whole months as possible.+-- Same as 'diffGregorianDurationClip' for positive durations.+diffGregorianDurationRollOver :: Day -> Day -> CalendarDiffDays+diffGregorianDurationRollOver day2 day1 = let+ (y1,m1,d1) = toGregorian day1+ (y2,m2,d2) = toGregorian day2+ ym1 = y1 * 12 + toInteger m1+ ym2 = y2 * 12 + toInteger m2+ ymdiff = ym2 - ym1+ ymAllowed =+ if day2 >= day1 then+ if d2 >= d1 then ymdiff else ymdiff - 1+ else if d2 <= d1 then ymdiff else ymdiff + 1+ dayAllowed = addGregorianDurationRollOver (CalendarDiffDays ymAllowed 0) day1+ in CalendarDiffDays ymAllowed $ diffDays day2 dayAllowed -- orphan instance instance Show Day where
lib/Data/Time/Calendar/Julian.hs view
@@ -7,12 +7,15 @@ -- calendrical arithmetic -- e.g. "one month after March 31st" addJulianMonthsClip,addJulianMonthsRollOver,- addJulianYearsClip,addJulianYearsRollOver+ addJulianYearsClip,addJulianYearsRollOver,+ addJulianDurationClip,addJulianDurationRollOver,+ diffJulianDurationClip,diffJulianDurationRollOver, ) where import Data.Time.Calendar.MonthDay import Data.Time.Calendar.JulianYearDay import Data.Time.Calendar.Days+import Data.Time.Calendar.CalendarDiffDays import Data.Time.Calendar.Private -- | Convert to proleptic Julian calendar. First element of result is year, second month number (1-12), third day (1-31).@@ -71,3 +74,42 @@ -- For instance, 2004-02-29 + 2 years = 2006-03-01. addJulianYearsRollOver :: Integer -> Day -> Day addJulianYearsRollOver n = addJulianMonthsRollOver (n * 12)++-- | Add months (clipped to last day), then add days+addJulianDurationClip :: CalendarDiffDays -> Day -> Day+addJulianDurationClip (CalendarDiffDays m d) day = addDays d $ addJulianMonthsClip m day++-- | Add months (rolling over to next month), then add days+addJulianDurationRollOver :: CalendarDiffDays -> Day -> Day+addJulianDurationRollOver (CalendarDiffDays m d) day = addDays d $ addJulianMonthsRollOver m day++-- | Calendrical difference, with as many whole months as possible+diffJulianDurationClip :: Day -> Day -> CalendarDiffDays+diffJulianDurationClip day2 day1 = let+ (y1,m1,d1) = toJulian day1+ (y2,m2,d2) = toJulian day2+ ym1 = y1 * 12 + toInteger m1+ ym2 = y2 * 12 + toInteger m2+ ymdiff = ym2 - ym1+ ymAllowed =+ if day2 >= day1 then+ if d2 >= d1 then ymdiff else ymdiff - 1+ else if d2 <= d1 then ymdiff else ymdiff + 1+ dayAllowed = addJulianDurationClip (CalendarDiffDays ymAllowed 0) day1+ in CalendarDiffDays ymAllowed $ diffDays day2 dayAllowed++-- | Calendrical difference, with as many whole months as possible.+-- Same as 'diffJulianDurationClip' for positive durations.+diffJulianDurationRollOver :: Day -> Day -> CalendarDiffDays+diffJulianDurationRollOver day2 day1 = let+ (y1,m1,d1) = toJulian day1+ (y2,m2,d2) = toJulian day2+ ym1 = y1 * 12 + toInteger m1+ ym2 = y2 * 12 + toInteger m2+ ymdiff = ym2 - ym1+ ymAllowed =+ if day2 >= day1 then+ if d2 >= d1 then ymdiff else ymdiff - 1+ else if d2 <= d1 then ymdiff else ymdiff + 1+ dayAllowed = addJulianDurationRollOver (CalendarDiffDays ymAllowed 0) day1+ in CalendarDiffDays ymAllowed $ diffDays day2 dayAllowed
lib/Data/Time/Calendar/JulianYearDay.hs view
@@ -1,4 +1,3 @@--- #hide module Data.Time.Calendar.JulianYearDay ( -- * Year and day format
lib/Data/Time/Calendar/Private.hs view
@@ -1,4 +1,3 @@--- #hide module Data.Time.Calendar.Private where import Data.Fixed@@ -51,3 +50,15 @@ clipValid a _ x | x < a = Nothing clipValid _ b x | x > b = Nothing clipValid _ _ x = Just x++quotBy :: (Real a,Integral b) => a -> a -> b+quotBy d n = truncate ((toRational n) / (toRational d))++remBy :: Real a => a -> a -> a+remBy d n = n - (fromInteger f) * d where+ f = quotBy d n++quotRemBy :: (Real a,Integral b) => a -> a -> (b,a)+quotRemBy d n = let+ f = quotBy d n+ in (f,n - (fromIntegral f) * d)
+ lib/Data/Time/Calendar/Week.hs view
@@ -0,0 +1,47 @@+module Data.Time.Calendar.Week+ (+ -- * Week+ DayOfWeek(..)+ , dayOfWeek+ ) where++import Data.Time.Calendar.Days++data DayOfWeek+ = Monday+ | Tuesday+ | Wednesday+ | Thursday+ | Friday+ | Saturday+ | Sunday+ deriving (Eq, Show, Read)++-- | \"Circular\", so for example @[Tuesday ..]@ gives an endless sequence.+-- Also: 'fromEnum' gives [1 .. 7] for [Monday .. Sunday], and 'toEnum' performs mod 7 to give a cycle of days.+instance Enum DayOfWeek where+ toEnum i =+ case mod i 7 of+ 0 -> Sunday+ 1 -> Monday+ 2 -> Tuesday+ 3 -> Wednesday+ 4 -> Thursday+ 5 -> Friday+ _ -> Saturday+ fromEnum Monday = 1+ fromEnum Tuesday = 2+ fromEnum Wednesday = 3+ fromEnum Thursday = 4+ fromEnum Friday = 5+ fromEnum Saturday = 6+ fromEnum Sunday = 7+ enumFromTo wd1 wd2+ | wd1 == wd2 = [wd1]+ enumFromTo wd1 wd2 = wd1 : enumFromTo (succ wd1) wd2+ enumFromThenTo wd1 wd2 wd3+ | wd2 == wd3 = [wd1, wd2]+ enumFromThenTo wd1 wd2 wd3 = wd1 : enumFromThenTo wd2 (toEnum $ (2 * fromEnum wd2) - (fromEnum wd1)) wd3++dayOfWeek :: Day -> DayOfWeek+dayOfWeek (ModifiedJulianDay d) = toEnum $ fromInteger $ d + 3
lib/Data/Time/Clock/Internal/DiffTime.hs view
@@ -3,7 +3,6 @@ #endif {-# OPTIONS -fno-warn-unused-imports #-} #include "HsConfigure.h"--- #hide module Data.Time.Clock.Internal.DiffTime ( -- * Absolute intervals
lib/Data/Time/Clock/Internal/NominalDiffTime.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE Trustworthy #-} #endif #include "HsConfigure.h"--- #hide module Data.Time.Clock.Internal.NominalDiffTime ( NominalDiffTime,
lib/Data/Time/Clock/Internal/UTCDiff.hs view
@@ -1,4 +1,3 @@--- #hide module Data.Time.Clock.Internal.UTCDiff where import Data.Time.Clock.Internal.NominalDiffTime
lib/Data/Time/Format.hs view
@@ -1,344 +1,9 @@ module Data.Time.Format ( -- * UNIX-style formatting- NumericPadOption,FormatTime(..),formatTime,+ FormatTime(),formatTime, module Data.Time.Format.Parse ) where--import Data.Maybe-import Data.Char-import Data.Fixed--import Data.Time.Clock.Internal.UniversalTime-import Data.Time.Clock.Internal.UTCTime-import Data.Time.Clock.POSIX-import Data.Time.Calendar.Days-import Data.Time.Calendar.Gregorian-import Data.Time.Calendar.WeekDate-import Data.Time.Calendar.OrdinalDate-import Data.Time.Calendar.Private-import Data.Time.LocalTime.Internal.TimeZone-import Data.Time.LocalTime.Internal.TimeOfDay-import Data.Time.LocalTime.Internal.LocalTime-import Data.Time.LocalTime.Internal.ZonedTime+import Data.Time.Format.Format.Class+import Data.Time.Format.Format.Instances() import Data.Time.Format.Parse---type NumericPadOption = Maybe Char---- the weird UNIX logic is here-getPadOption :: Bool -> Bool -> Int -> Char -> Maybe NumericPadOption -> Maybe Int -> PadOption-getPadOption trunc fdef idef cdef mnpad mi = let- c = case mnpad of- Just (Just c') -> c'- Just Nothing -> ' '- _ -> cdef- i = case mi of- Just i' -> case mnpad of- Just Nothing -> i'- _ -> if trunc then i' else max i' idef- Nothing -> idef- f = case mi of- Just _ -> True- Nothing -> case mnpad of- Nothing -> fdef- Just Nothing -> False- Just (Just _) -> True- in if f then Pad i c else NoPad--padGeneral :: Bool -> Bool -> Int -> Char -> (TimeLocale -> PadOption -> t -> String) -> (TimeLocale -> Maybe NumericPadOption -> Maybe Int -> t -> String)-padGeneral trunc fdef idef cdef ff locale mnpad mi = ff locale $ getPadOption trunc fdef idef cdef mnpad mi--padString :: (TimeLocale -> t -> String) -> (TimeLocale -> Maybe NumericPadOption -> Maybe Int -> t -> String)-padString ff = padGeneral False False 1 ' ' $ \locale pado -> showPadded pado . ff locale--padNum :: (ShowPadded i) => Bool -> Int -> Char -> (t -> i) -> (TimeLocale -> Maybe NumericPadOption -> Maybe Int -> t -> String)-padNum fdef idef cdef ff = padGeneral False fdef idef cdef $ \_ pado -> showPaddedNum pado . ff---- <http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html>-class FormatTime t where- formatCharacter :: Char -> Maybe (TimeLocale -> Maybe NumericPadOption -> Maybe Int -> t -> String)--formatChar :: (FormatTime t) => Char -> TimeLocale -> Maybe NumericPadOption -> Maybe Int -> t -> String-formatChar '%' = padString $ \_ _ -> "%"-formatChar 't' = padString $ \_ _ -> "\t"-formatChar 'n' = padString $ \_ _ -> "\n"-formatChar c = case formatCharacter c of- Just f -> f- _ -> \_ _ _ _ -> ""---- | Substitute various time-related information for each %-code in the string, as per 'formatCharacter'.------ The general form is @%\<modifier\>\<width\>\<specifier\>@, where @\<modifier\>@ and @\<width\>@ are optional.------ == @\<modifier\>@--- glibc-style modifiers can be used before the specifier (here marked as @z@):------ [@%-z@] no padding------ [@%_z@] pad with spaces------ [@%0z@] pad with zeros------ [@%^z@] convert to upper case------ [@%#z@] convert to lower case (consistently, unlike glibc)------ == @\<width\>@--- Width digits can also be used after any modifiers and before the specifier (here marked as @z@), for example:------ [@%4z@] pad to 4 characters (with default padding character)------ [@%_12z@] pad with spaces to 12 characters------ == @\<specifier\>@------ For all types (note these three are done by 'formatTime', not by 'formatCharacter'):------ [@%%@] @%@------ [@%t@] tab------ [@%n@] newline------ === 'TimeZone'--- For 'TimeZone' (and 'ZonedTime' and 'UTCTime'):------ [@%z@] timezone offset in the format @-HHMM@.------ [@%Z@] timezone name------ === 'LocalTime'--- For 'LocalTime' (and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):------ [@%c@] as 'dateTimeFmt' @locale@ (e.g. @%a %b %e %H:%M:%S %Z %Y@)------ === 'TimeOfDay'--- For 'TimeOfDay' (and 'LocalTime' and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):------ [@%R@] same as @%H:%M@------ [@%T@] same as @%H:%M:%S@------ [@%X@] as 'timeFmt' @locale@ (e.g. @%H:%M:%S@)------ [@%r@] as 'time12Fmt' @locale@ (e.g. @%I:%M:%S %p@)------ [@%P@] day-half of day from ('amPm' @locale@), converted to lowercase, @am@, @pm@------ [@%p@] day-half of day from ('amPm' @locale@), @AM@, @PM@------ [@%H@] hour of day (24-hour), 0-padded to two chars, @00@ - @23@------ [@%k@] hour of day (24-hour), space-padded to two chars, @ 0@ - @23@------ [@%I@] hour of day-half (12-hour), 0-padded to two chars, @01@ - @12@------ [@%l@] hour of day-half (12-hour), space-padded to two chars, @ 1@ - @12@------ [@%M@] minute of hour, 0-padded to two chars, @00@ - @59@------ [@%S@] second of minute (without decimal part), 0-padded to two chars, @00@ - @60@------ [@%q@] picosecond of second, 0-padded to twelve chars, @000000000000@ - @999999999999@.------ [@%Q@] decimal point and fraction of second, up to 12 second decimals, without trailing zeros.--- For a whole number of seconds, @%Q@ omits the decimal point unless padding is specified.------ === 'UTCTime' and 'ZonedTime'--- For 'UTCTime' and 'ZonedTime':------ [@%s@] number of whole seconds since the Unix epoch. For times before--- the Unix epoch, this is a negative number. Note that in @%s.%q@ and @%s%Q@--- the decimals are positive, not negative. For example, 0.9 seconds--- before the Unix epoch is formatted as @-1.1@ with @%s%Q@.------ === 'Day'--- For 'Day' (and 'LocalTime' and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):------ [@%D@] same as @%m\/%d\/%y@------ [@%F@] same as @%Y-%m-%d@------ [@%x@] as 'dateFmt' @locale@ (e.g. @%m\/%d\/%y@)------ [@%Y@] year, no padding. Note @%0Y@ and @%_Y@ pad to four chars------ [@%y@] year of century, 0-padded to two chars, @00@ - @99@------ [@%C@] century, no padding. Note @%0C@ and @%_C@ pad to two chars------ [@%B@] month name, long form ('fst' from 'months' @locale@), @January@ - @December@------ [@%b@, @%h@] month name, short form ('snd' from 'months' @locale@), @Jan@ - @Dec@------ [@%m@] month of year, 0-padded to two chars, @01@ - @12@------ [@%d@] day of month, 0-padded to two chars, @01@ - @31@------ [@%e@] day of month, space-padded to two chars, @ 1@ - @31@------ [@%j@] day of year, 0-padded to three chars, @001@ - @366@------ [@%f@] century for Week Date format, no padding. Note @%0f@ and @%_f@ pad to two chars------ [@%V@] week of year for Week Date format, 0-padded to two chars, @01@ - @53@------ [@%u@] day of week for Week Date format, @1@ - @7@------ [@%a@] day of week, short form ('snd' from 'wDays' @locale@), @Sun@ - @Sat@------ [@%A@] day of week, long form ('fst' from 'wDays' @locale@), @Sunday@ - @Saturday@------ [@%U@] week of year where weeks start on Sunday (as 'sundayStartWeek'), 0-padded to two chars, @00@ - @53@------ [@%w@] day of week number, @0@ (= Sunday) - @6@ (= Saturday)------ [@%W@] week of year where weeks start on Monday (as 'mondayStartWeek'), 0-padded to two chars, @00@ - @53@-formatTime :: (FormatTime t) => TimeLocale -> String -> t -> String-formatTime _ [] _ = ""-formatTime locale ('%':cs) t = case formatTime1 locale cs t of- Just result -> result- Nothing -> '%':(formatTime locale cs t)-formatTime locale (c:cs) t = c:(formatTime locale cs t)--formatTime1 :: (FormatTime t) => TimeLocale -> String -> t -> Maybe String-formatTime1 locale ('_':cs) t = formatTime2 locale id (Just (Just ' ')) cs t-formatTime1 locale ('-':cs) t = formatTime2 locale id (Just Nothing) cs t-formatTime1 locale ('0':cs) t = formatTime2 locale id (Just (Just '0')) cs t-formatTime1 locale ('^':cs) t = formatTime2 locale (fmap toUpper) Nothing cs t-formatTime1 locale ('#':cs) t = formatTime2 locale (fmap toLower) Nothing cs t-formatTime1 locale cs t = formatTime2 locale id Nothing cs t--getDigit :: Char -> Maybe Int-getDigit c | c < '0' = Nothing-getDigit c | c > '9' = Nothing-getDigit c = Just $ (ord c) - (ord '0')--pullNumber :: Maybe Int -> String -> (Maybe Int,String)-pullNumber mx [] = (mx,[])-pullNumber mx s@(c:cs) = case getDigit c of- Just i -> pullNumber (Just $ (fromMaybe 0 mx)*10+i) cs- Nothing -> (mx,s)--formatTime2 :: (FormatTime t) => TimeLocale -> (String -> String) -> Maybe NumericPadOption -> String -> t -> Maybe String-formatTime2 locale recase mpad cs t = let- (mwidth,rest) = pullNumber Nothing cs- in formatTime3 locale recase mpad mwidth rest t--formatTime3 :: (FormatTime t) => TimeLocale -> (String -> String) -> Maybe NumericPadOption -> Maybe Int -> String -> t -> Maybe String-formatTime3 locale recase mpad mwidth (c:cs) t = Just $ (recase (formatChar c locale mpad mwidth t)) ++ (formatTime locale cs t)-formatTime3 _locale _recase _mpad _mwidth [] _t = Nothing--instance FormatTime LocalTime where- formatCharacter 'c' = Just $ \locale _ _ -> formatTime locale (dateTimeFmt locale)- formatCharacter c = case formatCharacter c of- Just f -> Just $ \locale mpado mwidth dt -> f locale mpado mwidth (localDay dt)- Nothing -> case formatCharacter c of- Just f -> Just $ \locale mpado mwidth dt -> f locale mpado mwidth (localTimeOfDay dt)- Nothing -> Nothing--todAMPM :: TimeLocale -> TimeOfDay -> String-todAMPM locale day = let- (am,pm) = amPm locale- in if (todHour day) < 12 then am else pm--tod12Hour :: TimeOfDay -> Int-tod12Hour day = (mod (todHour day - 1) 12) + 1--showPaddedFixedFraction :: HasResolution a => PadOption -> Fixed a -> String-showPaddedFixedFraction pado x = let- digits = dropWhile (=='.') $ dropWhile (/='.') $ showFixed True x- n = length digits- in case pado of- NoPad -> digits- Pad i c -> if i < n- then take i digits- else digits ++ replicate (i - n) c--instance FormatTime TimeOfDay where- -- Aggregate- formatCharacter 'R' = Just $ padString $ \locale -> formatTime locale "%H:%M"- formatCharacter 'T' = Just $ padString $ \locale -> formatTime locale "%H:%M:%S"- formatCharacter 'X' = Just $ padString $ \locale -> formatTime locale (timeFmt locale)- formatCharacter 'r' = Just $ padString $ \locale -> formatTime locale (time12Fmt locale)- -- AM/PM- formatCharacter 'P' = Just $ padString $ \locale -> map toLower . todAMPM locale- formatCharacter 'p' = Just $ padString $ \locale -> todAMPM locale- -- Hour- formatCharacter 'H' = Just $ padNum True 2 '0' todHour- formatCharacter 'I' = Just $ padNum True 2 '0' tod12Hour- formatCharacter 'k' = Just $ padNum True 2 ' ' todHour- formatCharacter 'l' = Just $ padNum True 2 ' ' tod12Hour- -- Minute- formatCharacter 'M' = Just $ padNum True 2 '0' todMin- -- Second- formatCharacter 'S' = Just $ padNum True 2 '0' $ (floor . todSec :: TimeOfDay -> Int)- formatCharacter 'q' = Just $ padGeneral True True 12 '0' $ \_ pado -> showPaddedFixedFraction pado . todSec- formatCharacter 'Q' = Just $ padGeneral True False 12 '0' $ \_ pado -> dotNonEmpty . showPaddedFixedFraction pado . todSec where- dotNonEmpty "" = ""- dotNonEmpty s = '.':s-- -- Default- formatCharacter _ = Nothing--instance FormatTime ZonedTime where- formatCharacter 'c' = Just $ padString $ \locale -> formatTime locale (dateTimeFmt locale)- formatCharacter 's' = Just $ padNum True 1 '0' $ (floor . utcTimeToPOSIXSeconds . zonedTimeToUTC :: ZonedTime -> Integer)- formatCharacter c = case formatCharacter c of- Just f -> Just $ \locale mpado mwidth dt -> f locale mpado mwidth (zonedTimeToLocalTime dt)- Nothing -> case formatCharacter c of- Just f -> Just $ \locale mpado mwidth dt -> f locale mpado mwidth (zonedTimeZone dt)- Nothing -> Nothing--instance FormatTime TimeZone where- formatCharacter 'z' = Just $ padGeneral False True 4 '0' $ \_ pado -> showPadded pado . timeZoneOffsetString'' pado- formatCharacter 'Z' = Just $ \locale mnpo mi z -> let- n = timeZoneName z- in if null n then timeZoneOffsetString'' (getPadOption False True 4 '0' mnpo mi) z else padString (\_ -> timeZoneName) locale mnpo mi z- formatCharacter _ = Nothing--instance FormatTime Day where- -- Aggregate- formatCharacter 'D' = Just $ padString $ \locale -> formatTime locale "%m/%d/%y"- formatCharacter 'F' = Just $ padString $ \locale -> formatTime locale "%Y-%m-%d"- formatCharacter 'x' = Just $ padString $ \locale -> formatTime locale (dateFmt locale)-- -- Year Count- formatCharacter 'Y' = Just $ padNum False 4 '0' $ fst . toOrdinalDate- formatCharacter 'y' = Just $ padNum True 2 '0' $ mod100 . fst . toOrdinalDate- formatCharacter 'C' = Just $ padNum False 2 '0' $ div100 . fst . toOrdinalDate- -- Month of Year- formatCharacter 'B' = Just $ padString $ \locale -> fst . (\(_,m,_) -> (months locale) !! (m - 1)) . toGregorian- formatCharacter 'b' = Just $ padString $ \locale -> snd . (\(_,m,_) -> (months locale) !! (m - 1)) . toGregorian- formatCharacter 'h' = Just $ padString $ \locale -> snd . (\(_,m,_) -> (months locale) !! (m - 1)) . toGregorian- formatCharacter 'm' = Just $ padNum True 2 '0' $ (\(_,m,_) -> m) . toGregorian- -- Day of Month- formatCharacter 'd' = Just $ padNum True 2 '0' $ (\(_,_,d) -> d) . toGregorian- formatCharacter 'e' = Just $ padNum True 2 ' ' $ (\(_,_,d) -> d) . toGregorian- -- Day of Year- formatCharacter 'j' = Just $ padNum True 3 '0' $ snd . toOrdinalDate-- -- ISO 8601 Week Date- formatCharacter 'G' = Just $ padNum False 4 '0' $ (\(y,_,_) -> y) . toWeekDate- formatCharacter 'g' = Just $ padNum True 2 '0' $ mod100 . (\(y,_,_) -> y) . toWeekDate- formatCharacter 'f' = Just $ padNum False 2 '0' $ div100 . (\(y,_,_) -> y) . toWeekDate-- formatCharacter 'V' = Just $ padNum True 2 '0' $ (\(_,w,_) -> w) . toWeekDate- formatCharacter 'u' = Just $ padNum True 1 '0' $ (\(_,_,d) -> d) . toWeekDate-- -- Day of week- formatCharacter 'a' = Just $ padString $ \locale -> snd . ((wDays locale) !!) . snd . sundayStartWeek- formatCharacter 'A' = Just $ padString $ \locale -> fst . ((wDays locale) !!) . snd . sundayStartWeek- formatCharacter 'U' = Just $ padNum True 2 '0' $ fst . sundayStartWeek- formatCharacter 'w' = Just $ padNum True 1 '0' $ snd . sundayStartWeek- formatCharacter 'W' = Just $ padNum True 2 '0' $ fst . mondayStartWeek-- -- Default- formatCharacter _ = Nothing--instance FormatTime UTCTime where- formatCharacter c = fmap (\f locale mpado mwidth t -> f locale mpado mwidth (utcToZonedTime utc t)) (formatCharacter c)--instance FormatTime UniversalTime where- formatCharacter c = fmap (\f locale mpado mwidth t -> f locale mpado mwidth (ut1ToLocalTime 0 t)) (formatCharacter c)
+ lib/Data/Time/Format/Format/Class.hs view
@@ -0,0 +1,349 @@+module Data.Time.Format.Format.Class+ (+ -- * Formatting+ formatTime,+ FormatNumericPadding,+ FormatOptions(..),+ FormatTime(..),+ ShowPadded,PadOption,+ formatGeneral,formatString,formatNumber,formatNumberStd,+ showPaddedFixed,showPaddedFixedFraction,+ quotBy,remBy,+ )+ where++import Data.Char+import Data.Maybe+import Data.Fixed+import Data.Time.Calendar.Private+import Data.Time.Format.Locale++type FormatNumericPadding = Maybe Char++data FormatOptions = MkFormatOptions {+ foLocale :: TimeLocale,+ foPadding :: Maybe FormatNumericPadding,+ foWidth :: Maybe Int+}++-- <http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html>+class FormatTime t where+ formatCharacter :: Bool -> Char -> Maybe (FormatOptions -> t -> String)+++-- the weird UNIX logic is here+getPadOption :: Bool -> Bool -> Int -> Char -> Maybe FormatNumericPadding -> Maybe Int -> PadOption+getPadOption trunc fdef idef cdef mnpad mi = let+ c = case mnpad of+ Just (Just c') -> c'+ Just Nothing -> ' '+ _ -> cdef+ i = case mi of+ Just i' -> case mnpad of+ Just Nothing -> i'+ _ -> if trunc then i' else max i' idef+ Nothing -> idef+ f = case mi of+ Just _ -> True+ Nothing -> case mnpad of+ Nothing -> fdef+ Just Nothing -> False+ Just (Just _) -> True+ in if f then Pad i c else NoPad++formatGeneral :: Bool -> Bool -> Int -> Char -> (TimeLocale -> PadOption -> t -> String) -> (FormatOptions -> t -> String)+formatGeneral trunc fdef idef cdef ff fo = ff (foLocale fo) $ getPadOption trunc fdef idef cdef (foPadding fo) (foWidth fo)++formatString :: (TimeLocale -> t -> String) -> (FormatOptions -> t -> String)+formatString ff = formatGeneral False False 1 ' ' $ \locale pado -> showPadded pado . ff locale++formatNumber :: (ShowPadded i) => Bool -> Int -> Char -> (t -> i) -> (FormatOptions -> t -> String)+formatNumber fdef idef cdef ff = formatGeneral False fdef idef cdef $ \_ pado -> showPaddedNum pado . ff++formatNumberStd :: Int -> (t -> Integer) -> (FormatOptions -> t -> String)+formatNumberStd n = formatNumber False n '0'++showPaddedFixed :: HasResolution a => PadOption -> PadOption -> Fixed a -> String+showPaddedFixed padn padf x | x < 0 = '-' : showPaddedFixed padn padf (negate x)+showPaddedFixed padn padf x = let+ ns = showPaddedNum padn $ (floor x :: Integer)+ fs = showPaddedFixedFraction padf x+ ds = if null fs then "" else "."+ in ns ++ ds ++ fs++showPaddedFixedFraction :: HasResolution a => PadOption -> Fixed a -> String+showPaddedFixedFraction pado x = let+ digits = dropWhile (=='.') $ dropWhile (/='.') $ showFixed True x+ n = length digits+ in case pado of+ NoPad -> digits+ Pad i c -> if i < n+ then take i digits+ else digits ++ replicate (i - n) c+++-- | Substitute various time-related information for each %-code in the string, as per 'formatCharacter'.+--+-- The general form is @%\<modifier\>\<width\>\<alternate\>\<specifier\>@, where @\<modifier\>@, @\<width\>@, and @\<alternate\>@ are optional.+--+-- == @\<modifier\>@+-- glibc-style modifiers can be used before the specifier (here marked as @z@):+--+-- [@%-z@] no padding+--+-- [@%_z@] pad with spaces+--+-- [@%0z@] pad with zeros+--+-- [@%^z@] convert to upper case+--+-- [@%#z@] convert to lower case (consistently, unlike glibc)+--+-- == @\<width\>@+-- Width digits can also be used after any modifiers and before the specifier (here marked as @z@), for example:+--+-- [@%4z@] pad to 4 characters (with default padding character)+--+-- [@%_12z@] pad with spaces to 12 characters+--+-- == @\<alternate\>@+-- An optional @E@ character indicates an alternate formatting. Currently this only affects @%Z@ and @%z@.+--+-- [@%Ez@] alternate formatting+--+-- == @\<specifier\>@+--+-- For all types (note these three are done by 'formatTime', not by 'formatCharacter'):+--+-- [@%%@] @%@+--+-- [@%t@] tab+--+-- [@%n@] newline+--+-- === 'TimeZone'+-- For 'TimeZone' (and 'ZonedTime' and 'UTCTime'):+--+-- [@%z@] timezone offset in the format @±HHMM@+--+-- [@%Ez@] timezone offset in the format @±HH:MM@+--+-- [@%Z@] timezone name (or else offset in the format @±HHMM@)+--+-- [@%EZ@] timezone name (or else offset in the format @±HH:MM@)+--+-- === 'LocalTime'+-- For 'LocalTime' (and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):+--+-- [@%c@] as 'dateTimeFmt' @locale@ (e.g. @%a %b %e %H:%M:%S %Z %Y@)+--+-- === 'TimeOfDay'+-- For 'TimeOfDay' (and 'LocalTime' and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):+--+-- [@%R@] same as @%H:%M@+--+-- [@%T@] same as @%H:%M:%S@+--+-- [@%X@] as 'timeFmt' @locale@ (e.g. @%H:%M:%S@)+--+-- [@%r@] as 'time12Fmt' @locale@ (e.g. @%I:%M:%S %p@)+--+-- [@%P@] day-half of day from ('amPm' @locale@), converted to lowercase, @am@, @pm@+--+-- [@%p@] day-half of day from ('amPm' @locale@), @AM@, @PM@+--+-- [@%H@] hour of day (24-hour), 0-padded to two chars, @00@ - @23@+--+-- [@%k@] hour of day (24-hour), space-padded to two chars, @ 0@ - @23@+--+-- [@%I@] hour of day-half (12-hour), 0-padded to two chars, @01@ - @12@+--+-- [@%l@] hour of day-half (12-hour), space-padded to two chars, @ 1@ - @12@+--+-- [@%M@] minute of hour, 0-padded to two chars, @00@ - @59@+--+-- [@%S@] second of minute (without decimal part), 0-padded to two chars, @00@ - @60@+--+-- [@%q@] picosecond of second, 0-padded to twelve chars, @000000000000@ - @999999999999@.+--+-- [@%Q@] decimal point and fraction of second, up to 12 second decimals, without trailing zeros.+-- For a whole number of seconds, @%Q@ omits the decimal point unless padding is specified.+--+-- === 'UTCTime' and 'ZonedTime'+-- For 'UTCTime' and 'ZonedTime':+--+-- [@%s@] number of whole seconds since the Unix epoch. For times before+-- the Unix epoch, this is a negative number. Note that in @%s.%q@ and @%s%Q@+-- the decimals are positive, not negative. For example, 0.9 seconds+-- before the Unix epoch is formatted as @-1.1@ with @%s%Q@.+--+-- === 'DayOfWeek'+-- For 'DayOfWeek' (and 'Day' and 'LocalTime' and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):+--+-- [@%u@] day of week number for Week Date format, @1@ (= Monday) - @7@ (= Sunday)+--+-- [@%w@] day of week number, @0@ (= Sunday) - @6@ (= Saturday)+--+-- [@%a@] day of week, short form ('snd' from 'wDays' @locale@), @Sun@ - @Sat@+--+-- [@%A@] day of week, long form ('fst' from 'wDays' @locale@), @Sunday@ - @Saturday@+--+-- === 'Day'+-- For 'Day' (and 'LocalTime' and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):+--+-- [@%D@] same as @%m\/%d\/%y@+--+-- [@%F@] same as @%Y-%m-%d@+--+-- [@%x@] as 'dateFmt' @locale@ (e.g. @%m\/%d\/%y@)+--+-- [@%Y@] year, no padding. Note @%0Y@ and @%_Y@ pad to four chars+--+-- [@%y@] year of century, 0-padded to two chars, @00@ - @99@+--+-- [@%C@] century, no padding. Note @%0C@ and @%_C@ pad to two chars+--+-- [@%B@] month name, long form ('fst' from 'months' @locale@), @January@ - @December@+--+-- [@%b@, @%h@] month name, short form ('snd' from 'months' @locale@), @Jan@ - @Dec@+--+-- [@%m@] month of year, 0-padded to two chars, @01@ - @12@+--+-- [@%d@] day of month, 0-padded to two chars, @01@ - @31@+--+-- [@%e@] day of month, space-padded to two chars, @ 1@ - @31@+--+-- [@%j@] day of year, 0-padded to three chars, @001@ - @366@+--+-- [@%f@] century for Week Date format, no padding. Note @%0f@ and @%_f@ pad to two chars+--+-- [@%V@] week of year for Week Date format, 0-padded to two chars, @01@ - @53@+--+-- [@%U@] week of year where weeks start on Sunday (as 'sundayStartWeek'), 0-padded to two chars, @00@ - @53@+--+-- [@%W@] week of year where weeks start on Monday (as 'mondayStartWeek'), 0-padded to two chars, @00@ - @53@+--+-- == Duration types+-- The specifiers for 'DiffTime', 'NominalDiffTime', 'CalendarDiffDays', and 'CalendarDiffTime' are semantically+-- separate from the other types.+-- Specifiers on negative time differences will generally be negative (think 'rem' rather than 'mod').+--+-- === 'NominalDiffTime' and 'DiffTime'+-- Note that a "minute" of 'DiffTime' is simply 60 SI seconds, rather than a minute of civil time.+-- Use 'NominalDiffTime' to work with civil time, ignoring any leap seconds.+--+-- For 'NominalDiffTime' and 'DiffTime':+--+-- [@%w@] total whole weeks+--+-- [@%d@] total whole days+--+-- [@%D@] whole days of week+--+-- [@%h@] total whole hours+--+-- [@%H@] whole hours of day+--+-- [@%m@] total whole minutes+--+-- [@%M@] whole minutes of hour+--+-- [@%s@] total whole seconds+--+-- [@%Es@] total seconds, with decimal point and up to \<width\> (default 12) decimal places, without trailing zeros.+-- For a whole number of seconds, @%Es@ omits the decimal point unless padding is specified.+--+-- [@%0Es@] total seconds, with decimal point and \<width\> (default 12) decimal places.+--+-- [@%S@] whole seconds of minute+--+-- [@%ES@] seconds of minute, with decimal point and up to \<width\> (default 12) decimal places, without trailing zeros.+-- For a whole number of seconds, @%ES@ omits the decimal point unless padding is specified.+--+-- [@%0ES@] seconds of minute as two digits, with decimal point and \<width\> (default 12) decimal places.+--+-- === 'CalendarDiffDays'+-- For 'CalendarDiffDays' (and 'CalendarDiffTime'):+--+-- [@%y@] total years+--+-- [@%b@] total months+--+-- [@%B@] months of year+--+-- [@%w@] total weeks, not including months+--+-- [@%d@] total days, not including months+--+-- [@%D@] days of week+--+-- === 'CalendarDiffTime'+-- For 'CalendarDiffTime':+--+-- [@%h@] total hours, not including months+--+-- [@%H@] hours of day+--+-- [@%m@] total minutes, not including months+--+-- [@%M@] minutes of hour+--+-- [@%s@] total whole seconds, not including months+--+-- [@%Es@] total seconds, not including months, with decimal point and up to \<width\> (default 12) decimal places, without trailing zeros.+-- For a whole number of seconds, @%Es@ omits the decimal point unless padding is specified.+--+-- [@%0Es@] total seconds, not including months, with decimal point and \<width\> (default 12) decimal places.+--+-- [@%S@] whole seconds of minute+--+-- [@%ES@] seconds of minute, with decimal point and up to \<width\> (default 12) decimal places, without trailing zeros.+-- For a whole number of seconds, @%ES@ omits the decimal point unless padding is specified.+--+-- [@%0ES@] seconds of minute as two digits, with decimal point and \<width\> (default 12) decimal places.+formatTime :: (FormatTime t) => TimeLocale -> String -> t -> String+formatTime _ [] _ = ""+formatTime locale ('%':cs) t = case formatTime1 locale cs t of+ Just result -> result+ Nothing -> '%':(formatTime locale cs t)+formatTime locale (c:cs) t = c:(formatTime locale cs t)++formatTime1 :: (FormatTime t) => TimeLocale -> String -> t -> Maybe String+formatTime1 locale ('_':cs) t = formatTime2 locale id (Just (Just ' ')) cs t+formatTime1 locale ('-':cs) t = formatTime2 locale id (Just Nothing) cs t+formatTime1 locale ('0':cs) t = formatTime2 locale id (Just (Just '0')) cs t+formatTime1 locale ('^':cs) t = formatTime2 locale (fmap toUpper) Nothing cs t+formatTime1 locale ('#':cs) t = formatTime2 locale (fmap toLower) Nothing cs t+formatTime1 locale cs t = formatTime2 locale id Nothing cs t++getDigit :: Char -> Maybe Int+getDigit c | c < '0' = Nothing+getDigit c | c > '9' = Nothing+getDigit c = Just $ (ord c) - (ord '0')++pullNumber :: Maybe Int -> String -> (Maybe Int,String)+pullNumber mx [] = (mx,[])+pullNumber mx s@(c:cs) = case getDigit c of+ Just i -> pullNumber (Just $ (fromMaybe 0 mx)*10+i) cs+ Nothing -> (mx,s)++formatTime2 :: (FormatTime t) => TimeLocale -> (String -> String) -> Maybe FormatNumericPadding -> String -> t -> Maybe String+formatTime2 locale recase mpad cs t = let+ (mwidth,rest) = pullNumber Nothing cs+ in formatTime3 locale recase mpad mwidth rest t++formatTime3 :: (FormatTime t) => TimeLocale -> (String -> String) -> Maybe FormatNumericPadding -> Maybe Int -> String -> t -> Maybe String+formatTime3 locale recase mpad mwidth ('E':cs) = formatTime4 True recase (MkFormatOptions locale mpad mwidth) cs+formatTime3 locale recase mpad mwidth cs = formatTime4 False recase (MkFormatOptions locale mpad mwidth) cs++formatTime4 :: (FormatTime t) => Bool -> (String -> String) -> FormatOptions -> String -> t -> Maybe String+formatTime4 alt recase fo (c:cs) t = Just $ (recase (formatChar alt c fo t)) ++ (formatTime (foLocale fo) cs t)+formatTime4 _alt _recase _fo [] _t = Nothing++formatChar :: (FormatTime t) => Bool -> Char -> FormatOptions -> t -> String+formatChar _ '%' = formatString $ \_ _ -> "%"+formatChar _ 't' = formatString $ \_ _ -> "\t"+formatChar _ 'n' = formatString $ \_ _ -> "\n"+formatChar alt c = case formatCharacter alt c of+ Just f -> f+ _ -> \_ _ -> ""
+ lib/Data/Time/Format/Format/Instances.hs view
@@ -0,0 +1,188 @@+{-# OPTIONS -fno-warn-orphans #-}+module Data.Time.Format.Format.Instances () where++import Data.Char+import Data.Fixed+import Data.Time.Clock.Internal.DiffTime+import Data.Time.Clock.Internal.NominalDiffTime+import Data.Time.Clock.Internal.UniversalTime+import Data.Time.Clock.Internal.UTCTime+import Data.Time.Clock.POSIX+import Data.Time.Calendar.Days+import Data.Time.Calendar.CalendarDiffDays+import Data.Time.Calendar.Gregorian+import Data.Time.Calendar.Week+import Data.Time.Calendar.WeekDate+import Data.Time.Calendar.OrdinalDate+import Data.Time.Calendar.Private+import Data.Time.LocalTime.Internal.CalendarDiffTime+import Data.Time.LocalTime.Internal.TimeZone+import Data.Time.LocalTime.Internal.TimeOfDay+import Data.Time.LocalTime.Internal.LocalTime+import Data.Time.LocalTime.Internal.ZonedTime+import Data.Time.Format.Locale+import Data.Time.Format.Format.Class+++instance FormatTime LocalTime where+ formatCharacter _ 'c' = Just $ \fo -> formatTime (foLocale fo) $ dateTimeFmt $ foLocale fo+ formatCharacter alt c = case formatCharacter alt c of+ Just f -> Just $ \fo dt -> f fo (localDay dt)+ Nothing -> case formatCharacter alt c of+ Just f -> Just $ \fo dt -> f fo (localTimeOfDay dt)+ Nothing -> Nothing++todAMPM :: TimeLocale -> TimeOfDay -> String+todAMPM locale day = let+ (am,pm) = amPm locale+ in if (todHour day) < 12 then am else pm++tod12Hour :: TimeOfDay -> Int+tod12Hour day = (mod (todHour day - 1) 12) + 1++instance FormatTime TimeOfDay where+ -- Aggregate+ formatCharacter _ 'R' = Just $ formatString $ \locale -> formatTime locale "%H:%M"+ formatCharacter _ 'T' = Just $ formatString $ \locale -> formatTime locale "%H:%M:%S"+ formatCharacter _ 'X' = Just $ formatString $ \locale -> formatTime locale (timeFmt locale)+ formatCharacter _ 'r' = Just $ formatString $ \locale -> formatTime locale (time12Fmt locale)+ -- AM/PM+ formatCharacter _ 'P' = Just $ formatString $ \locale -> map toLower . todAMPM locale+ formatCharacter _ 'p' = Just $ formatString $ \locale -> todAMPM locale+ -- Hour+ formatCharacter _ 'H' = Just $ formatNumber True 2 '0' todHour+ formatCharacter _ 'I' = Just $ formatNumber True 2 '0' tod12Hour+ formatCharacter _ 'k' = Just $ formatNumber True 2 ' ' todHour+ formatCharacter _ 'l' = Just $ formatNumber True 2 ' ' tod12Hour+ -- Minute+ formatCharacter _ 'M' = Just $ formatNumber True 2 '0' todMin+ -- Second+ formatCharacter _ 'S' = Just $ formatNumber True 2 '0' $ (floor . todSec :: TimeOfDay -> Int)+ formatCharacter _ 'q' = Just $ formatGeneral True True 12 '0' $ \_ pado -> showPaddedFixedFraction pado . todSec+ formatCharacter _ 'Q' = Just $ formatGeneral True False 12 '0' $ \_ pado -> dotNonEmpty . showPaddedFixedFraction pado . todSec where+ dotNonEmpty "" = ""+ dotNonEmpty s = '.':s++ -- Default+ formatCharacter _ _ = Nothing++instance FormatTime ZonedTime where+ formatCharacter _ 'c' = Just $ formatString $ \locale -> formatTime locale (dateTimeFmt locale)+ formatCharacter _ 's' = Just $ formatNumber True 1 '0' $ (floor . utcTimeToPOSIXSeconds . zonedTimeToUTC :: ZonedTime -> Integer)+ formatCharacter alt c = case formatCharacter alt c of+ Just f -> Just $ \fo dt -> f fo (zonedTimeToLocalTime dt)+ Nothing -> case formatCharacter alt c of+ Just f -> Just $ \fo dt -> f fo (zonedTimeZone dt)+ Nothing -> Nothing++instance FormatTime TimeZone where+ formatCharacter False 'z' = Just $ formatGeneral False True 4 '0' $ \_ -> timeZoneOffsetString'' False+ formatCharacter True 'z' = Just $ formatGeneral False True 5 '0' $ \_ -> timeZoneOffsetString'' True+ formatCharacter alt 'Z' = Just $ \fo z -> let+ n = timeZoneName z+ idef = if alt then 5 else 4+ in if null n then formatGeneral False True idef '0' (\_ -> timeZoneOffsetString'' alt) fo z else formatString (\_ -> timeZoneName) fo z+ formatCharacter _ _ = Nothing++instance FormatTime DayOfWeek where+ formatCharacter _ 'u' = Just $ formatNumber True 1 '0' $ fromEnum+ formatCharacter _ 'w' = Just $ formatNumber True 1 '0' $ \wd -> (mod (fromEnum wd) 7)+ formatCharacter _ 'a' = Just $ formatString $ \locale wd -> snd $ (wDays locale) !! (mod (fromEnum wd) 7)+ formatCharacter _ 'A' = Just $ formatString $ \locale wd -> fst $ (wDays locale) !! (mod (fromEnum wd) 7)+ formatCharacter _ _ = Nothing++instance FormatTime Day where+ -- Aggregate+ formatCharacter _ 'D' = Just $ formatString $ \locale -> formatTime locale "%m/%d/%y"+ formatCharacter _ 'F' = Just $ formatString $ \locale -> formatTime locale "%Y-%m-%d"+ formatCharacter _ 'x' = Just $ formatString $ \locale -> formatTime locale (dateFmt locale)++ -- Year Count+ formatCharacter _ 'Y' = Just $ formatNumber False 4 '0' $ fst . toOrdinalDate+ formatCharacter _ 'y' = Just $ formatNumber True 2 '0' $ mod100 . fst . toOrdinalDate+ formatCharacter _ 'C' = Just $ formatNumber False 2 '0' $ div100 . fst . toOrdinalDate+ -- Month of Year+ formatCharacter _ 'B' = Just $ formatString $ \locale -> fst . (\(_,m,_) -> (months locale) !! (m - 1)) . toGregorian+ formatCharacter _ 'b' = Just $ formatString $ \locale -> snd . (\(_,m,_) -> (months locale) !! (m - 1)) . toGregorian+ formatCharacter _ 'h' = Just $ formatString $ \locale -> snd . (\(_,m,_) -> (months locale) !! (m - 1)) . toGregorian+ formatCharacter _ 'm' = Just $ formatNumber True 2 '0' $ (\(_,m,_) -> m) . toGregorian+ -- Day of Month+ formatCharacter _ 'd' = Just $ formatNumber True 2 '0' $ (\(_,_,d) -> d) . toGregorian+ formatCharacter _ 'e' = Just $ formatNumber True 2 ' ' $ (\(_,_,d) -> d) . toGregorian+ -- Day of Year+ formatCharacter _ 'j' = Just $ formatNumber True 3 '0' $ snd . toOrdinalDate++ -- ISO 8601 Week Date+ formatCharacter _ 'G' = Just $ formatNumber False 4 '0' $ (\(y,_,_) -> y) . toWeekDate+ formatCharacter _ 'g' = Just $ formatNumber True 2 '0' $ mod100 . (\(y,_,_) -> y) . toWeekDate+ formatCharacter _ 'f' = Just $ formatNumber False 2 '0' $ div100 . (\(y,_,_) -> y) . toWeekDate++ formatCharacter _ 'V' = Just $ formatNumber True 2 '0' $ (\(_,w,_) -> w) . toWeekDate+ formatCharacter _ 'u' = Just $ formatNumber True 1 '0' $ (\(_,_,d) -> d) . toWeekDate++ -- Day of week+ formatCharacter _ 'a' = Just $ formatString $ \locale -> snd . ((wDays locale) !!) . snd . sundayStartWeek+ formatCharacter _ 'A' = Just $ formatString $ \locale -> fst . ((wDays locale) !!) . snd . sundayStartWeek+ formatCharacter _ 'U' = Just $ formatNumber True 2 '0' $ fst . sundayStartWeek+ formatCharacter _ 'w' = Just $ formatNumber True 1 '0' $ snd . sundayStartWeek+ formatCharacter _ 'W' = Just $ formatNumber True 2 '0' $ fst . mondayStartWeek++ -- Default+ formatCharacter _ _ = Nothing++instance FormatTime UTCTime where+ formatCharacter alt c = fmap (\f fo t -> f fo (utcToZonedTime utc t)) (formatCharacter alt c)++instance FormatTime UniversalTime where+ formatCharacter alt c = fmap (\f fo t -> f fo (ut1ToLocalTime 0 t)) (formatCharacter alt c)++instance FormatTime NominalDiffTime where+ formatCharacter _ 'w' = Just $ formatNumberStd 1 $ quotBy $ 7 * 86400+ formatCharacter _ 'd' = Just $ formatNumberStd 1 $ quotBy 86400+ formatCharacter _ 'D' = Just $ formatNumberStd 1 $ remBy 7 . quotBy 86400+ formatCharacter _ 'h' = Just $ formatNumberStd 1 $ quotBy 3600+ formatCharacter _ 'H' = Just $ formatNumberStd 2 $ remBy 24 . quotBy 3600+ formatCharacter _ 'm' = Just $ formatNumberStd 1 $ quotBy 60+ formatCharacter _ 'M' = Just $ formatNumberStd 2 $ remBy 60 . quotBy 60+ formatCharacter False 's' = Just $ formatNumberStd 1 $ quotBy 1+ formatCharacter True 's' = Just $ formatGeneral False False 12 '0' $ \_ padf t -> showPaddedFixed NoPad padf (realToFrac t :: Pico)+ formatCharacter False 'S' = Just $ formatNumberStd 2 $ remBy 60 . quotBy 1+ formatCharacter True 'S' = Just $ formatGeneral False False 12 '0' $ \_ padf t -> let+ padn = case padf of+ NoPad -> NoPad+ Pad _ c -> Pad 2 c+ in showPaddedFixed padn padf (realToFrac $ remBy 60 t :: Pico)+ formatCharacter _ _ = Nothing++instance FormatTime DiffTime where+ formatCharacter _ 'w' = Just $ formatNumberStd 1 $ quotBy $ 7 * 86400+ formatCharacter _ 'd' = Just $ formatNumberStd 1 $ quotBy 86400+ formatCharacter _ 'D' = Just $ formatNumberStd 1 $ remBy 7 . quotBy 86400+ formatCharacter _ 'h' = Just $ formatNumberStd 1 $ quotBy 3600+ formatCharacter _ 'H' = Just $ formatNumberStd 2 $ remBy 24 . quotBy 3600+ formatCharacter _ 'm' = Just $ formatNumberStd 1 $ quotBy 60+ formatCharacter _ 'M' = Just $ formatNumberStd 2 $ remBy 60 . quotBy 60+ formatCharacter False 's' = Just $ formatNumberStd 1 $ quotBy 1+ formatCharacter True 's' = Just $ formatGeneral False False 12 '0' $ \_ padf t -> showPaddedFixed NoPad padf (realToFrac t :: Pico)+ formatCharacter False 'S' = Just $ formatNumberStd 2 $ remBy 60 . quotBy 1+ formatCharacter True 'S' = Just $ formatGeneral False False 12 '0' $ \_ padf t -> let+ padn = case padf of+ NoPad -> NoPad+ Pad _ c -> Pad 2 c+ in showPaddedFixed padn padf (realToFrac $ remBy 60 t :: Pico)+ formatCharacter _ _ = Nothing++instance FormatTime CalendarDiffDays where+ formatCharacter _ 'y' = Just $ formatNumberStd 1 $ quotBy 12 . cdMonths+ formatCharacter _ 'b' = Just $ formatNumberStd 1 $ cdMonths+ formatCharacter _ 'B' = Just $ formatNumberStd 2 $ remBy 12 . cdMonths+ formatCharacter _ 'w' = Just $ formatNumberStd 1 $ quotBy 7 . cdDays+ formatCharacter _ 'd' = Just $ formatNumberStd 1 $ cdDays+ formatCharacter _ 'D' = Just $ formatNumberStd 1 $ remBy 7 . cdDays+ formatCharacter _ _ = Nothing++instance FormatTime CalendarDiffTime where+ formatCharacter _ 'y' = Just $ formatNumberStd 1 $ quotBy 12 . ctMonths+ formatCharacter _ 'b' = Just $ formatNumberStd 1 $ ctMonths+ formatCharacter _ 'B' = Just $ formatNumberStd 2 $ remBy 12 . ctMonths+ formatCharacter alt c = fmap (\f fo t -> f fo (ctTime t)) (formatCharacter alt c)
+ lib/Data/Time/Format/ISO8601.hs view
@@ -0,0 +1,388 @@+module Data.Time.Format.ISO8601+ (+ -- * Format+ Format,+ formatShowM,+ formatShow,+ formatReadP,+ formatParseM,+ -- * Common formats+ ISO8601(..),+ iso8601Show,+ iso8601ParseM,+ -- * All formats+ FormatExtension(..),+ formatReadPExtension,+ parseFormatExtension,+ calendarFormat,+ yearMonthFormat,+ yearFormat,+ centuryFormat,+ expandedCalendarFormat,+ expandedYearMonthFormat,+ expandedYearFormat,+ expandedCenturyFormat,+ ordinalDateFormat,+ expandedOrdinalDateFormat,+ weekDateFormat,+ yearWeekFormat,+ expandedWeekDateFormat,+ expandedYearWeekFormat,+ timeOfDayFormat,+ hourMinuteFormat,+ hourFormat,+ withTimeDesignator,+ withUTCDesignator,+ timeOffsetFormat,+ timeOfDayAndOffsetFormat,+ localTimeFormat,+ zonedTimeFormat,+ utcTimeFormat,+ dayAndTimeFormat,+ timeAndOffsetFormat,+ durationDaysFormat,+ durationTimeFormat,+ alternativeDurationDaysFormat,+ alternativeDurationTimeFormat,+ intervalFormat,+ recurringIntervalFormat,+ ) where++#if MIN_VERSION_base(4,9,0)+import Control.Monad.Fail+import Prelude hiding (fail)+#endif+#if MIN_VERSION_base(4,8,0)+#else+import Data.Monoid+#endif+import Data.Ratio+import Data.Fixed+import Text.ParserCombinators.ReadP+import Data.Format+import Data.Time+import Data.Time.Calendar.OrdinalDate+import Data.Time.Calendar.WeekDate+import Data.Time.Calendar.Private++data FormatExtension =+ -- | ISO 8601:2004(E) sec. 2.3.4. Use hyphens and colons.+ ExtendedFormat |+ -- | ISO 8601:2004(E) sec. 2.3.3. Omit hyphens and colons. "The basic format should be avoided in plain text."+ BasicFormat++-- | Read a value in either extended or basic format+formatReadPExtension :: (FormatExtension -> Format t) -> ReadP t+formatReadPExtension ff = formatReadP (ff ExtendedFormat) +++ formatReadP (ff BasicFormat)++-- | Parse a value in either extended or basic format+parseFormatExtension :: (+#if MIN_VERSION_base(4,9,0)+ MonadFail m+#else+ Monad m+#endif+ ) => (FormatExtension -> Format t) -> String -> m t+parseFormatExtension ff = parseReader $ formatReadPExtension ff++sepFormat :: String -> Format a -> Format b -> Format (a,b)+sepFormat sep fa fb = (fa <** literalFormat sep) <**> fb++dashFormat :: Format a -> Format b -> Format (a,b)+dashFormat = sepFormat "-"++colnFormat :: Format a -> Format b -> Format (a,b)+colnFormat = sepFormat ":"++extDashFormat :: FormatExtension -> Format a -> Format b -> Format (a,b)+extDashFormat ExtendedFormat = dashFormat+extDashFormat BasicFormat = (<**>)++extColonFormat :: FormatExtension -> Format a -> Format b -> Format (a,b)+extColonFormat ExtendedFormat = colnFormat+extColonFormat BasicFormat = (<**>)++expandedYearFormat' :: Int -> Format Integer+expandedYearFormat' n = integerFormat PosNegSign (Just n)++yearFormat' :: Format Integer+yearFormat' = integerFormat NegSign (Just 4)++monthFormat :: Format Int+monthFormat = integerFormat NoSign (Just 2)++dayOfMonthFormat :: Format Int+dayOfMonthFormat = integerFormat NoSign (Just 2)++dayOfYearFormat :: Format Int+dayOfYearFormat = integerFormat NoSign (Just 3)++weekOfYearFormat :: Format Int+weekOfYearFormat = literalFormat "W" **> integerFormat NoSign (Just 2)++dayOfWeekFormat :: Format Int+dayOfWeekFormat = integerFormat NoSign (Just 1)++hourFormat' :: Format Int+hourFormat' = integerFormat NoSign (Just 2)++data E14+instance HasResolution E14 where+ resolution _ = 100000000000000+data E16+instance HasResolution E16 where+ resolution _ = 10000000000000000++hourDecimalFormat :: Format (Fixed E16) -- need four extra decimal places for hours+hourDecimalFormat = decimalFormat NoSign (Just 2)++minuteFormat :: Format Int+minuteFormat = integerFormat NoSign (Just 2)++minuteDecimalFormat :: Format (Fixed E14) -- need two extra decimal places for minutes+minuteDecimalFormat = decimalFormat NoSign (Just 2)++secondFormat :: Format Pico+secondFormat = decimalFormat NoSign (Just 2)++mapGregorian :: Format (Integer,(Int,Int)) -> Format Day+mapGregorian = mapMFormat (\(y,(m,d)) -> fromGregorianValid y m d) (\day -> (\(y,m,d) -> Just (y,(m,d))) $ toGregorian day)++mapOrdinalDate :: Format (Integer,Int) -> Format Day+mapOrdinalDate = mapMFormat (\(y,d) -> fromOrdinalDateValid y d) (Just . toOrdinalDate)++mapWeekDate :: Format (Integer,(Int,Int)) -> Format Day+mapWeekDate = mapMFormat (\(y,(w,d)) -> fromWeekDateValid y w d) (\day -> (\(y,w,d) -> Just (y,(w,d))) $ toWeekDate day)++mapTimeOfDay :: Format (Int,(Int,Pico)) -> Format TimeOfDay+mapTimeOfDay = mapMFormat (\(h,(m,s)) -> makeTimeOfDayValid h m s) (\(TimeOfDay h m s) -> Just (h,(m,s)))+++-- | ISO 8601:2004(E) sec. 4.1.2.2+calendarFormat :: FormatExtension -> Format Day+calendarFormat fe = mapGregorian $ extDashFormat fe yearFormat $ extDashFormat fe monthFormat dayOfMonthFormat++-- | ISO 8601:2004(E) sec. 4.1.2.3(a)+yearMonthFormat :: Format (Integer,Int)+yearMonthFormat = yearFormat <**> literalFormat "-" **> monthFormat++-- | ISO 8601:2004(E) sec. 4.1.2.3(b)+yearFormat :: Format Integer+yearFormat = yearFormat'++-- | ISO 8601:2004(E) sec. 4.1.2.3(c)+centuryFormat :: Format Integer+centuryFormat = integerFormat NegSign (Just 2)++-- | ISO 8601:2004(E) sec. 4.1.2.4(a)+expandedCalendarFormat :: Int -> FormatExtension -> Format Day+expandedCalendarFormat n fe = mapGregorian $ extDashFormat fe (expandedYearFormat n) $ extDashFormat fe monthFormat dayOfMonthFormat++-- | ISO 8601:2004(E) sec. 4.1.2.4(b)+expandedYearMonthFormat :: Int -> Format (Integer,Int)+expandedYearMonthFormat n = dashFormat (expandedYearFormat n) monthFormat++-- | ISO 8601:2004(E) sec. 4.1.2.4(c)+expandedYearFormat :: Int -> Format Integer+expandedYearFormat = expandedYearFormat'++-- | ISO 8601:2004(E) sec. 4.1.2.4(d)+expandedCenturyFormat :: Int -> Format Integer+expandedCenturyFormat n = integerFormat PosNegSign (Just n)++-- | ISO 8601:2004(E) sec. 4.1.3.2+ordinalDateFormat :: FormatExtension -> Format Day+ordinalDateFormat fe = mapOrdinalDate $ extDashFormat fe yearFormat dayOfYearFormat++-- | ISO 8601:2004(E) sec. 4.1.3.3+expandedOrdinalDateFormat :: Int -> FormatExtension -> Format Day+expandedOrdinalDateFormat n fe = mapOrdinalDate $ extDashFormat fe (expandedYearFormat n) dayOfYearFormat++-- | ISO 8601:2004(E) sec. 4.1.4.2+weekDateFormat :: FormatExtension -> Format Day+weekDateFormat fe = mapWeekDate $ extDashFormat fe yearFormat $ extDashFormat fe weekOfYearFormat dayOfWeekFormat++-- | ISO 8601:2004(E) sec. 4.1.4.3+yearWeekFormat :: FormatExtension -> Format (Integer,Int)+yearWeekFormat fe = extDashFormat fe yearFormat weekOfYearFormat++-- | ISO 8601:2004(E) sec. 4.1.4.2+expandedWeekDateFormat :: Int -> FormatExtension -> Format Day+expandedWeekDateFormat n fe = mapWeekDate $ extDashFormat fe (expandedYearFormat n) $ extDashFormat fe weekOfYearFormat dayOfWeekFormat++-- | ISO 8601:2004(E) sec. 4.1.4.3+expandedYearWeekFormat :: Int -> FormatExtension -> Format (Integer,Int)+expandedYearWeekFormat n fe = extDashFormat fe (expandedYearFormat n) weekOfYearFormat++-- | ISO 8601:2004(E) sec. 4.2.2.2, 4.2.2.4(a)+timeOfDayFormat :: FormatExtension -> Format TimeOfDay+timeOfDayFormat fe = mapTimeOfDay $ extColonFormat fe hourFormat' $ extColonFormat fe minuteFormat secondFormat++-- workaround for the 'fromRational' in 'Fixed', which uses 'floor' instead of 'round'+fromRationalRound :: Rational -> NominalDiffTime+fromRationalRound r = fromRational $ round (r * 1000000000000) % 1000000000000++-- | ISO 8601:2004(E) sec. 4.2.2.3(a), 4.2.2.4(b)+hourMinuteFormat :: FormatExtension -> Format TimeOfDay+hourMinuteFormat fe = let+ toTOD (h,m) = case timeToDaysAndTimeOfDay $ fromRationalRound $ toRational $ (fromIntegral h) * 3600 + m * 60 of+ (0,tod) -> Just tod+ _ -> Nothing+ fromTOD tod = let+ mm = (realToFrac $ daysAndTimeOfDayToTime 0 tod) / 60+ in Just $ quotRemBy 60 mm+ in mapMFormat toTOD fromTOD $ extColonFormat fe hourFormat' $ minuteDecimalFormat++-- | ISO 8601:2004(E) sec. 4.2.2.3(b), 4.2.2.4(c)+hourFormat :: Format TimeOfDay+hourFormat = let+ toTOD h = case timeToDaysAndTimeOfDay $ fromRationalRound $ toRational $ h * 3600 of+ (0,tod) -> Just tod+ _ -> Nothing+ fromTOD tod = Just $ (realToFrac $ daysAndTimeOfDayToTime 0 tod) / 3600+ in mapMFormat toTOD fromTOD $ hourDecimalFormat++-- | ISO 8601:2004(E) sec. 4.2.2.5+withTimeDesignator :: Format t -> Format t+withTimeDesignator f = literalFormat "T" **> f++-- | ISO 8601:2004(E) sec. 4.2.4+withUTCDesignator :: Format t -> Format t+withUTCDesignator f = f <** literalFormat "Z"++-- | ISO 8601:2004(E) sec. 4.2.5.1+timeOffsetFormat :: FormatExtension -> Format TimeZone+timeOffsetFormat fe = let+ toTimeZone (sign,(h,m)) = minutesToTimeZone $ sign * (h * 60 + m)+ fromTimeZone tz = let+ mm = timeZoneMinutes tz+ hm = quotRem (abs mm) 60+ in (signum mm,hm)+ in isoMap toTimeZone fromTimeZone $+ mandatorySignFormat <**> extColonFormat fe (integerFormat NoSign (Just 2)) (integerFormat NoSign (Just 2))++-- | ISO 8601:2004(E) sec. 4.2.5.2+timeOfDayAndOffsetFormat :: FormatExtension -> Format (TimeOfDay,TimeZone)+timeOfDayAndOffsetFormat fe = timeOfDayFormat fe <**> timeOffsetFormat fe++-- | ISO 8601:2004(E) sec. 4.3.2+localTimeFormat :: Format Day -> Format TimeOfDay -> Format LocalTime+localTimeFormat fday ftod = isoMap (\(day,tod) -> LocalTime day tod) (\(LocalTime day tod) -> (day,tod)) $ fday <**> withTimeDesignator ftod++-- | ISO 8601:2004(E) sec. 4.3.2+zonedTimeFormat :: Format Day -> Format TimeOfDay -> FormatExtension -> Format ZonedTime+zonedTimeFormat fday ftod fe = isoMap (\(lt,tz) -> ZonedTime lt tz) (\(ZonedTime lt tz) -> (lt,tz)) $ timeAndOffsetFormat (localTimeFormat fday ftod) fe++-- | ISO 8601:2004(E) sec. 4.3.2+utcTimeFormat :: Format Day -> Format TimeOfDay -> Format UTCTime+utcTimeFormat fday ftod = isoMap (localTimeToUTC utc) (utcToLocalTime utc) $ withUTCDesignator $ localTimeFormat fday ftod++-- | ISO 8601:2004(E) sec. 4.3.3+dayAndTimeFormat :: Format Day -> Format time -> Format (Day,time)+dayAndTimeFormat fday ft = fday <**> withTimeDesignator ft++-- | ISO 8601:2004(E) sec. 4.3.3+timeAndOffsetFormat :: Format t -> FormatExtension -> Format (t,TimeZone)+timeAndOffsetFormat ft fe = ft <**> timeOffsetFormat fe++intDesignator :: (Eq t,Show t,Read t,Num t) => Char -> Format t+intDesignator c = optionalFormat 0 $ integerFormat NoSign Nothing <** literalFormat [c]++decDesignator :: (Eq t,Show t,Read t,Num t) => Char -> Format t+decDesignator c = optionalFormat 0 $ decimalFormat NoSign Nothing <** literalFormat [c]++daysDesigs :: Format CalendarDiffDays+daysDesigs = let+ toCD (y,(m,(w,d))) = CalendarDiffDays (y * 12 + m) (w * 7 + d)+ fromCD (CalendarDiffDays mm d) = (quot mm 12,(rem mm 12,(0,d)))+ in isoMap toCD fromCD $+ intDesignator 'Y' <**> intDesignator 'M' <**> intDesignator 'W' <**> intDesignator 'D'++-- | ISO 8601:2004(E) sec. 4.4.3.2+durationDaysFormat :: Format CalendarDiffDays+durationDaysFormat = (**>) (literalFormat "P") $ specialCaseShowFormat (mempty,"0D") $ daysDesigs++-- | ISO 8601:2004(E) sec. 4.4.3.2+durationTimeFormat :: Format CalendarDiffTime+durationTimeFormat = let+ toCT (cd,(h,(m,s))) = mappend (calendarTimeDays cd) (calendarTimeTime $ daysAndTimeOfDayToTime 0 $ TimeOfDay h m s)+ fromCT (CalendarDiffTime mm t) = let+ (d,TimeOfDay h m s) = timeToDaysAndTimeOfDay t+ in (CalendarDiffDays mm d,(h,(m,s)))+ in (**>) (literalFormat "P") $ specialCaseShowFormat (mempty,"0D") $ isoMap toCT fromCT $+ (<**>) daysDesigs $ optionalFormat (0,(0,0)) $ literalFormat "T" **> intDesignator 'H' <**> intDesignator 'M' <**> decDesignator 'S'++-- | ISO 8601:2004(E) sec. 4.4.3.3+alternativeDurationDaysFormat :: FormatExtension -> Format CalendarDiffDays+alternativeDurationDaysFormat fe = let+ toCD (y,(m,d)) = CalendarDiffDays (y * 12 + m) d+ fromCD (CalendarDiffDays mm d) = (quot mm 12,(rem mm 12,d))+ in isoMap toCD fromCD $ (**>) (literalFormat "P") $+ extDashFormat fe (clipFormat (0,9999) $ integerFormat NegSign $ Just 4) $+ extDashFormat fe (clipFormat (0,12) $ integerFormat NegSign $ Just 2) $+ (clipFormat (0,30) $ integerFormat NegSign $ Just 2)++-- | ISO 8601:2004(E) sec. 4.4.3.3+alternativeDurationTimeFormat :: FormatExtension -> Format CalendarDiffTime+alternativeDurationTimeFormat fe = let+ toCT (cd,(h,(m,s))) = mappend (calendarTimeDays cd) (calendarTimeTime $ daysAndTimeOfDayToTime 0 $ TimeOfDay h m s)+ fromCT (CalendarDiffTime mm t) = let+ (d,TimeOfDay h m s) = timeToDaysAndTimeOfDay t+ in (CalendarDiffDays mm d,(h,(m,s)))+ in isoMap toCT fromCT $+ (<**>) (alternativeDurationDaysFormat fe) $+ withTimeDesignator $+ extColonFormat fe (clipFormat (0,24) $ integerFormat NegSign (Just 2)) $+ extColonFormat fe (clipFormat (0,60) $ integerFormat NegSign (Just 2)) $+ (clipFormat (0,60) $ decimalFormat NegSign (Just 2))++-- | ISO 8601:2004(E) sec. 4.4.4.1+intervalFormat :: Format a -> Format b -> Format (a,b)+intervalFormat = sepFormat "/"++-- | ISO 8601:2004(E) sec. 4.5+recurringIntervalFormat :: Format a -> Format b -> Format (Int,a,b)+recurringIntervalFormat fa fb = isoMap (\(r,(a,b)) -> (r,a,b)) (\(r,a,b) -> (r,(a,b))) $ sepFormat "/" (literalFormat "R" **> integerFormat NoSign Nothing) $ intervalFormat fa fb++class ISO8601 t where+ -- | The most commonly used ISO 8601 format for this type.+ iso8601Format :: Format t++-- | Show in the most commonly used ISO 8601 format.+iso8601Show :: ISO8601 t => t -> String+iso8601Show = formatShow iso8601Format++-- | Parse the most commonly used ISO 8601 format.+iso8601ParseM :: (+#if MIN_VERSION_base(4,9,0)+ MonadFail m+#else+ Monad m+#endif+ ,ISO8601 t) => String -> m t+iso8601ParseM = formatParseM iso8601Format++-- | @yyyy-mm-dd@ (ISO 8601:2004(E) sec. 4.1.2.2 extended format)+instance ISO8601 Day where+ iso8601Format = calendarFormat ExtendedFormat+-- | @hh:mm:ss[.sss]@ (ISO 8601:2004(E) sec. 4.2.2.2, 4.2.2.4(a) extended format)+instance ISO8601 TimeOfDay where+ iso8601Format = timeOfDayFormat ExtendedFormat+-- | @±hh:mm@ (ISO 8601:2004(E) sec. 4.2.5.1 extended format)+instance ISO8601 TimeZone where+ iso8601Format = timeOffsetFormat ExtendedFormat+-- | @yyyy-mm-ddThh:mm:ss[.sss]@ (ISO 8601:2004(E) sec. 4.3.2 extended format)+instance ISO8601 LocalTime where+ iso8601Format = localTimeFormat iso8601Format iso8601Format+-- | @yyyy-mm-ddThh:mm:ss[.sss]±hh:mm@ (ISO 8601:2004(E) sec. 4.3.2 extended format)+instance ISO8601 ZonedTime where+ iso8601Format = zonedTimeFormat iso8601Format iso8601Format ExtendedFormat+-- | @yyyy-mm-ddThh:mm:ss[.sss]Z@ (ISO 8601:2004(E) sec. 4.3.2 extended format)+instance ISO8601 UTCTime where+ iso8601Format = utcTimeFormat iso8601Format iso8601Format+-- | @PyYmMdD@ (ISO 8601:2004(E) sec. 4.4.3.2)+instance ISO8601 CalendarDiffDays where+ iso8601Format = durationDaysFormat+-- | @PyYmMdDThHmMs[.sss]S@ (ISO 8601:2004(E) sec. 4.4.3.2)+instance ISO8601 CalendarDiffTime where+ iso8601Format = durationTimeFormat
lib/Data/Time/Format/Locale.hs view
@@ -32,7 +32,7 @@ -- -- 'knownTimeZones' contains only the ten time-zones mentioned in RFC 822 sec. 5: -- \"UT\", \"GMT\", \"EST\", \"EDT\", \"CST\", \"CDT\", \"MST\", \"MDT\", \"PST\", \"PDT\".--- Note that the parsing functions will regardless parse single-letter military time-zones and +HHMM format.+-- Note that the parsing functions will regardless parse "UTC", single-letter military time-zones, and +HHMM format. defaultTimeLocale :: TimeLocale defaultTimeLocale = TimeLocale { wDays = [("Sunday", "Sun"), ("Monday", "Mon"),
lib/Data/Time/Format/Parse.hs view
@@ -1,86 +1,38 @@ {-# OPTIONS -fno-warn-orphans #-}-#include "HsConfigure.h"---- #hide module Data.Time.Format.Parse ( -- * UNIX-style parsing-#if LANGUAGE_Rank2Types parseTimeM, parseTimeOrError, readSTime, readPTime, parseTime, readTime, readsTime,-#endif- ParseTime(..),+ ParseTime(), -- * Locale module Data.Time.Format.Locale ) where -import Text.Read(readMaybe)+import Data.Proxy+#if MIN_VERSION_base(4,9,0)+import Control.Monad.Fail+import Prelude hiding (fail)+#endif+import Data.Char+import Data.Time.Format.Locale+import Text.ParserCombinators.ReadP hiding (char, string) import Data.Time.Clock.Internal.UniversalTime-import Data.Time.Clock.POSIX import Data.Time.Clock.Internal.UTCTime import Data.Time.Calendar.Days-import Data.Time.Calendar.Gregorian-import Data.Time.Calendar.OrdinalDate-import Data.Time.Calendar.WeekDate-import Data.Time.Calendar.Private(clipValid) import Data.Time.LocalTime.Internal.TimeZone import Data.Time.LocalTime.Internal.TimeOfDay import Data.Time.LocalTime.Internal.LocalTime import Data.Time.LocalTime.Internal.ZonedTime--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>),(<*>))-#endif-#if LANGUAGE_Rank2Types-import Control.Monad-#endif-import Data.Char-import Data.Fixed-import Data.List-import Data.Maybe-import Data.Ratio-import Data.Time.Format.Locale-#if LANGUAGE_Rank2Types-import Text.ParserCombinators.ReadP hiding (char, string)-#endif--#if LANGUAGE_Rank2Types--- | Case-insensitive version of 'Text.ParserCombinators.ReadP.char'.-char :: Char -> ReadP Char-char c = satisfy (\x -> toUpper c == toUpper x)--- | Case-insensitive version of 'Text.ParserCombinators.ReadP.string'.-string :: String -> ReadP String-string this = do s <- look; scan this s- where- scan [] _ = do return this- scan (x:xs) (y:ys) | toUpper x == toUpper y = do _ <- get; scan xs ys- scan _ _ = do pfail-#endif--- | Convert string to upper case.-up :: String -> String-up = map toUpper----- | The class of types which can be parsed given a UNIX-style time format--- string.-class ParseTime t where- -- | Builds a time value from a parsed input string.- -- If the input does not include all the information needed to- -- construct a complete value, any missing parts should be taken- -- from 1970-01-01 00:00:00 +0000 (which was a Thursday).- -- In the absence of @%C@ or @%Y@, century is 1969 - 2068.- buildTime :: TimeLocale -- ^ The time locale.- -> [(Char,String)] -- ^ Pairs of format characters and the- -- corresponding part of the input.- -> Maybe t+import Data.Time.Format.Parse.Class+import Data.Time.Format.Parse.Instances() -#if LANGUAGE_Rank2Types -- | Parses a time value given a format string. -- Supports the same %-codes as 'formatTime', including @%-@, @%_@ and @%0@ modifiers, however padding widths are not supported. -- Case is not significant in the input string. -- Some variations in the input are accepted: ----- [@%z@] accepts any of @-HHMM@ or @-HH:MM@.+-- [@%z@] accepts any of @±HHMM@ or @±HH:MM@. -- -- [@%Z@] accepts any string of letters, or any of the formats accepted by @%z@. --@@ -99,7 +51,13 @@ -- > Prelude Data.Time> parseTimeM True defaultTimeLocale "%Y-%-m-%-d" "2010-3-04" :: Maybe Day -- > Just 2010-03-04 ---parseTimeM :: (Monad m,ParseTime t) =>+parseTimeM :: (+#if MIN_VERSION_base(4,9,0)+ MonadFail m+#else+ Monad m+#endif+ ,ParseTime t) => Bool -- ^ Accept leading and trailing whitespace? -> TimeLocale -- ^ Time locale. -> String -- ^ Format string.@@ -150,16 +108,19 @@ readPTime False l f = readPOnlyTime l f readPTime True l f = (skipSpaces >> readPOnlyTime l f) <++ readPOnlyTime l f +readPOnlyTime' :: ParseTime t => proxy t -> TimeLocale -> String -> ReadP t+readPOnlyTime' pt l f = do+ pairs <- parseSpecifiers pt l f+ case buildTime l pairs of+ Just t -> return t+ Nothing -> pfail+ -- | Parse a time value given a format string (without allowing leading whitespace). See 'parseTimeM' for details. readPOnlyTime :: ParseTime t => TimeLocale -- ^ Time locale. -> String -- ^ Format string -> ReadP t-readPOnlyTime l f = do- mt <- liftM (buildTime l) (parseInput l (parseFormat l f))- case mt of- Just t -> return t- Nothing -> pfail+readPOnlyTime = readPOnlyTime' Proxy {-# DEPRECATED parseTime "use \"parseTimeM True\" instead" #-} parseTime :: ParseTime t =>@@ -185,427 +146,8 @@ -> ReadS t readsTime = readSTime True ------- * Internals-----data Padding = NoPadding | SpacePadding | ZeroPadding- deriving Show--type DateFormat = [DateFormatSpec]--data DateFormatSpec = Value (Maybe Padding) Char- | WhiteSpace- | Literal Char- deriving Show--parseFormat :: TimeLocale -> String -> DateFormat-parseFormat l = p- where p "" = []- p ('%': '-' : c :cs) = (pc (Just NoPadding) c) ++ p cs- p ('%': '_' : c :cs) = (pc (Just SpacePadding) c) ++ p cs- p ('%': '0' : c :cs) = (pc (Just ZeroPadding) c) ++ p cs- p ('%': c :cs) = (pc Nothing c) ++ p cs- p (c:cs) | isSpace c = WhiteSpace : p cs- p (c:cs) = Literal c : p cs- pc _ 'c' = p (dateTimeFmt l)- pc _ 'R' = p "%H:%M"- pc _ 'T' = p "%H:%M:%S"- pc _ 'X' = p (timeFmt l)- pc _ 'r' = p (time12Fmt l)- pc _ 'D' = p "%m/%d/%y"- pc _ 'F' = p "%Y-%m-%d"- pc _ 'x' = p (dateFmt l)- pc _ 'h' = p "%b"- pc _ '%' = [Literal '%']- pc mpad c = [Value mpad c]--parseInput :: TimeLocale -> DateFormat -> ReadP [(Char,String)]-parseInput _ [] = return []-parseInput l (Value mpad c:ff) = do- s <- parseValue l mpad c- r <- parseInput l ff- return ((c,s):r)-parseInput l (Literal c:ff) = do- _ <- char c- parseInput l ff-parseInput l (WhiteSpace:ff) = do- _ <- satisfy isSpace- case ff of- (WhiteSpace:_) -> return ()- _ -> skipSpaces- parseInput l ff---- | Get the string corresponding to the given format specifier.-parseValue :: TimeLocale -> Maybe Padding -> Char -> ReadP String-parseValue l mpad c =- case c of- -- century- 'C' -> digits SpacePadding 2- 'f' -> digits SpacePadding 2-- -- year- 'Y' -> digits SpacePadding 4- 'G' -> digits SpacePadding 4-- -- year of century- 'y' -> digits ZeroPadding 2- 'g' -> digits ZeroPadding 2-- -- month of year- 'B' -> oneOf (map fst (months l))- 'b' -> oneOf (map snd (months l))- 'm' -> digits ZeroPadding 2-- -- day of month- 'd' -> digits ZeroPadding 2- 'e' -> digits SpacePadding 2-- -- week of year- 'V' -> digits ZeroPadding 2- 'U' -> digits ZeroPadding 2- 'W' -> digits ZeroPadding 2-- -- day of week- 'u' -> oneOf $ map (:[]) ['1'..'7']- 'a' -> oneOf (map snd (wDays l))- 'A' -> oneOf (map fst (wDays l))- 'w' -> oneOf $ map (:[]) ['0'..'6']-- -- day of year- 'j' -> digits ZeroPadding 3-- -- dayhalf of day (i.e. AM or PM)- 'P' -> oneOf (let (am,pm) = amPm l in [am, pm])- 'p' -> oneOf (let (am,pm) = amPm l in [am, pm])-- -- hour of day (i.e. 24h)- 'H' -> digits ZeroPadding 2- 'k' -> digits SpacePadding 2-- -- hour of dayhalf (i.e. 12h)- 'I' -> digits ZeroPadding 2- 'l' -> digits SpacePadding 2-- -- minute of hour- 'M' -> digits ZeroPadding 2-- -- second of minute- 'S' -> digits ZeroPadding 2-- -- picosecond of second- 'q' -> digits ZeroPadding 12- 'Q' -> liftM2 (:) (char '.') (munch isDigit) <++ return ""-- -- time zone- 'z' -> numericTZ- 'Z' -> munch1 isAlpha <++- numericTZ <++- return "" -- produced by %Z for LocalTime-- -- seconds since epoch- 's' -> (char '-' >> liftM ('-':) (munch1 isDigit))- <++ munch1 isDigit-- _ -> fail $ "Unknown format character: " ++ show c- where- oneOf = choice . map string- digitsforce ZeroPadding n = count n (satisfy isDigit)- digitsforce SpacePadding _n = skipSpaces >> many1 (satisfy isDigit)- digitsforce NoPadding _n = many1 (satisfy isDigit)- digits pad = digitsforce (fromMaybe pad mpad)- numericTZ = do s <- choice [char '+', char '-']- h <- digitsforce ZeroPadding 2- optional (char ':')- m <- digitsforce ZeroPadding 2- return (s:h++m)-#endif------- * Instances for the time package types-----data DayComponent = Century Integer -- century of all years- | CenturyYear Integer -- 0-99, last two digits of both real years and week years- | YearMonth Int -- 1-12- | MonthDay Int -- 1-31- | YearDay Int -- 1-366- | WeekDay Int -- 1-7 (mon-sun)- | YearWeek WeekType Int -- 1-53 or 0-53--data WeekType = ISOWeek | SundayWeek | MondayWeek--instance ParseTime Day where- buildTime l = let-- -- 'Nothing' indicates a parse failure,- -- while 'Just []' means no information- f :: Char -> String -> Maybe [DayComponent]- f c x = let- ra :: (Read a) => Maybe a- ra = readMaybe x-- zeroBasedListIndex :: [String] -> Maybe Int- zeroBasedListIndex ss = elemIndex (up x) $ fmap up ss-- oneBasedListIndex :: [String] -> Maybe Int- oneBasedListIndex ss = do- index <- zeroBasedListIndex ss- return $ 1 + index-- in case c of- -- %C: century (all but the last two digits of the year), 00 - 99- 'C' -> do- a <- ra- return [Century a]- -- %f century (all but the last two digits of the year), 00 - 99- 'f' -> do- a <- ra- return [Century a]- -- %Y: year- 'Y' -> do- a <- ra- return [Century (a `div` 100), CenturyYear (a `mod` 100)]- -- %G: year for Week Date format- 'G' -> do- a <- ra- return [Century (a `div` 100), CenturyYear (a `mod` 100)]- -- %y: last two digits of year, 00 - 99- 'y' -> do- a <- ra- return [CenturyYear a]- -- %g: last two digits of year for Week Date format, 00 - 99- 'g' -> do- a <- ra- return [CenturyYear a]- -- %B: month name, long form (fst from months locale), January - December- 'B' -> do- a <- oneBasedListIndex $ fmap fst $ months l- return [YearMonth a]- -- %b: month name, short form (snd from months locale), Jan - Dec- 'b' -> do- a <- oneBasedListIndex $ fmap snd $ months l- return [YearMonth a]- -- %m: month of year, leading 0 as needed, 01 - 12- 'm' -> do- raw <- ra- a <- clipValid 1 12 raw- return [YearMonth a]- -- %d: day of month, leading 0 as needed, 01 - 31- 'd' -> do- raw <- ra- a <- clipValid 1 31 raw- return [MonthDay a]- -- %e: day of month, leading space as needed, 1 - 31- 'e' -> do- raw <- ra- a <- clipValid 1 31 raw- return [MonthDay a]- -- %V: week for Week Date format, 01 - 53- 'V' -> do- raw <- ra- a <- clipValid 1 53 raw- return [YearWeek ISOWeek a]- -- %U: week number of year, where weeks start on Sunday (as sundayStartWeek), 00 - 53- 'U' -> do- raw <- ra- a <- clipValid 0 53 raw- return [YearWeek SundayWeek a]- -- %W: week number of year, where weeks start on Monday (as mondayStartWeek), 00 - 53- 'W' -> do- raw <- ra- a <- clipValid 0 53 raw- return [YearWeek MondayWeek a]- -- %u: day for Week Date format, 1 - 7- 'u' -> do- raw <- ra- a <- clipValid 1 7 raw- return [WeekDay a]- -- %a: day of week, short form (snd from wDays locale), Sun - Sat- 'a' -> do- a' <- zeroBasedListIndex $ fmap snd $ wDays l- let a = if a' == 0 then 7 else a'- return [WeekDay a]- -- %A: day of week, long form (fst from wDays locale), Sunday - Saturday- 'A' -> do- a' <- zeroBasedListIndex $ fmap fst $ wDays l- let a = if a' == 0 then 7 else a'- return [WeekDay a]- -- %w: day of week number, 0 (= Sunday) - 6 (= Saturday)- 'w' -> do- raw <- ra- a' <- clipValid 0 6 raw- let a = if a' == 0 then 7 else a'- return [WeekDay a]- -- %j: day of year for Ordinal Date format, 001 - 366- 'j' -> do- raw <- ra- a <- clipValid 1 366 raw- return [YearDay a]- -- unrecognised, pass on to other parsers- _ -> return []-- buildDay :: [DayComponent] -> Maybe Day- buildDay cs = let- safeLast x xs = last (x:xs)- y = let- d = safeLast 70 [x | CenturyYear x <- cs]- c = safeLast (if d >= 69 then 19 else 20) [x | Century x <- cs]- in 100 * c + d- rest (YearMonth m:_) = let- d = safeLast 1 [x | MonthDay x <- cs]- in fromGregorianValid y m d- rest (YearDay d:_) = fromOrdinalDateValid y d- rest (YearWeek wt w:_) = let- d = safeLast 4 [x | WeekDay x <- cs]- in case wt of- ISOWeek -> fromWeekDateValid y w d- SundayWeek -> fromSundayStartWeekValid y w (d `mod` 7)- MondayWeek -> fromMondayStartWeekValid y w d- rest (_:xs) = rest xs- rest [] = rest [YearMonth 1]-- in rest cs-- in \pairs -> do- components <- mapM (uncurry f) pairs- buildDay $ concat components--mfoldl :: (Monad m) => (a -> b -> m a) -> m a -> [b] -> m a-mfoldl f = let- mf ma b = do- a <- ma- f a b- in foldl mf--instance ParseTime TimeOfDay where- buildTime l = let- f t@(TimeOfDay h m s) (c,x) = let- ra :: (Read a) => Maybe a- ra = readMaybe x-- getAmPm = let- upx = up x- (amStr,pmStr) = amPm l- in if upx == amStr- then Just $ TimeOfDay (h `mod` 12) m s- else if upx == pmStr- then Just $ TimeOfDay (if h < 12 then h + 12 else h) m s- else Nothing-- in case c of- 'P' -> getAmPm- 'p' -> getAmPm- 'H' -> do- a <- ra- return $ TimeOfDay a m s- 'I' -> do- a <- ra- return $ TimeOfDay a m s- 'k' -> do- a <- ra- return $ TimeOfDay a m s- 'l' -> do- a <- ra- return $ TimeOfDay a m s- 'M' -> do- a <- ra- return $ TimeOfDay h a s- 'S' -> do- a <- ra- return $ TimeOfDay h m (fromInteger a)- 'q' -> do- a <- ra- return $ TimeOfDay h m (mkPico (floor s) a)- 'Q' -> if null x then Just t else do- ps <- readMaybe $ take 12 $ rpad 12 '0' $ drop 1 x- return $ TimeOfDay h m (mkPico (floor s) ps)- _ -> Just t-- in mfoldl f (Just midnight)--rpad :: Int -> a -> [a] -> [a]-rpad n c xs = xs ++ replicate (n - length xs) c--mkPico :: Integer -> Integer -> Pico-mkPico i f = fromInteger i + fromRational (f % 1000000000000)--instance ParseTime LocalTime where- buildTime l xs = LocalTime <$> (buildTime l xs) <*> (buildTime l xs)--enumDiff :: (Enum a) => a -> a -> Int-enumDiff a b = (fromEnum a) - (fromEnum b)--getMilZoneHours :: Char -> Maybe Int-getMilZoneHours c | c < 'A' = Nothing-getMilZoneHours c | c <= 'I' = Just $ 1 + enumDiff c 'A'-getMilZoneHours 'J' = Nothing-getMilZoneHours c | c <= 'M' = Just $ 10 + enumDiff c 'K'-getMilZoneHours c | c <= 'Y' = Just $ (enumDiff 'N' c) - 1-getMilZoneHours 'Z' = Just 0-getMilZoneHours _ = Nothing--getMilZone :: Char -> Maybe TimeZone-getMilZone c = let- yc = toUpper c- in do- hours <- getMilZoneHours yc- return $ TimeZone (hours * 60) False [yc]--getKnownTimeZone :: TimeLocale -> String -> Maybe TimeZone-getKnownTimeZone locale x = find (\tz -> up x == timeZoneName tz) (knownTimeZones locale)--instance ParseTime TimeZone where- buildTime l = let- f (TimeZone _ dst name) ('z',x) | Just offset <- readTzOffset x = TimeZone offset dst name- f t ('Z',"") = t- f _ ('Z',x) | Just zone <- getKnownTimeZone l x = zone- f _ ('Z',[c]) | Just zone <- getMilZone c = zone- f (TimeZone offset dst _) ('Z',x) | isAlpha (head x) = TimeZone offset dst (up x)- f (TimeZone _ dst name) ('Z',x) | Just offset <- readTzOffset x = TimeZone offset dst name- f t _ = t- in Just . foldl f (minutesToTimeZone 0)--readTzOffset :: String -> Maybe Int-readTzOffset str = let-- getSign '+' = Just 1- getSign '-' = Just (-1)- getSign _ = Nothing-- calc s h1 h2 m1 m2 = do- sign <- getSign s- h <- readMaybe [h1,h2]- m <- readMaybe [m1,m2]- return $ sign * (60 * h + m)-- in case str of- (s:h1:h2:':':m1:m2:[]) -> calc s h1 h2 m1 m2- (s:h1:h2:m1:m2:[]) -> calc s h1 h2 m1 m2- _ -> Nothing--instance ParseTime ZonedTime where- buildTime l xs = let- f (ZonedTime (LocalTime _ tod) z) ('s',x) = do- a <- readMaybe x- let- s = fromInteger a- (_,ps) = properFraction (todSec tod) :: (Integer,Pico)- s' = s + fromRational (toRational ps)- return $ utcToZonedTime z (posixSecondsToUTCTime s')- f t _ = Just t- in mfoldl f (ZonedTime <$> (buildTime l xs) <*> (buildTime l xs)) xs--instance ParseTime UTCTime where- buildTime l xs = zonedTimeToUTC <$> buildTime l xs--instance ParseTime UniversalTime where- buildTime l xs = localTimeToUT1 0 <$> buildTime l xs- -- * Read instances for time package types -#if LANGUAGE_Rank2Types instance Read Day where readsPrec _ = readParen False $ readSTime True defaultTimeLocale "%Y-%m-%d" @@ -627,4 +169,3 @@ instance Read UniversalTime where readsPrec n s = [ (localTimeToUT1 0 t, r) | (t,r) <- readsPrec n s ]-#endif
+ lib/Data/Time/Format/Parse/Class.hs view
@@ -0,0 +1,220 @@+module Data.Time.Format.Parse.Class+ (+ -- * Parsing+ ParseNumericPadding(..),+ ParseTime(..),+ parseSpecifiers,+ timeSubstituteTimeSpecifier,+ timeParseTimeSpecifier,+ durationParseTimeSpecifier,+ )+ where++import Control.Applicative hiding (optional,many)+import Data.Char+import Data.Maybe+import Data.Time.Format.Locale+import Text.ParserCombinators.ReadP++data ParseNumericPadding = NoPadding | SpacePadding | ZeroPadding++-- | The class of types which can be parsed given a UNIX-style time format+-- string.+class ParseTime t where+ substituteTimeSpecifier :: proxy t -> TimeLocale -> Char -> Maybe String+ substituteTimeSpecifier _ _ _ = Nothing+ -- | Get the string corresponding to the given format specifier.+ parseTimeSpecifier :: proxy t -> TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String+ -- | Builds a time value from a parsed input string.+ -- If the input does not include all the information needed to+ -- construct a complete value, any missing parts should be taken+ -- from 1970-01-01 00:00:00 +0000 (which was a Thursday).+ -- In the absence of @%C@ or @%Y@, century is 1969 - 2068.+ buildTime :: TimeLocale -- ^ The time locale.+ -> [(Char,String)] -- ^ Pairs of format characters and the+ -- corresponding part of the input.+ -> Maybe t++-- | Case-insensitive version of 'Text.ParserCombinators.ReadP.char'.+charCI :: Char -> ReadP Char+charCI c = satisfy (\x -> toUpper c == toUpper x)++-- | Case-insensitive version of 'Text.ParserCombinators.ReadP.string'.+stringCI :: String -> ReadP String+stringCI this = do+ let+ scan [] _ = return this+ scan (x:xs) (y:ys) | toUpper x == toUpper y = do+ _ <- get+ scan xs ys+ scan _ _ = pfail+ s <- look+ scan this s++parseSpecifiers :: ParseTime t => proxy t -> TimeLocale -> String -> ReadP [(Char,String)]+parseSpecifiers pt locale = let+ parse :: String -> ReadP [(Char,String)]+ parse [] = return []+ parse ('%':cs) = parse1 cs+ parse (c:cs) | isSpace c = do+ _ <- satisfy isSpace+ case cs of+ (c':_) | isSpace c' -> return ()+ _ -> skipSpaces+ parse cs+ parse (c:cs) = do+ _ <- charCI c+ parse cs++ parse1 :: String -> ReadP [(Char,String)]+ parse1 ('-':cs) = parse2 (Just NoPadding) cs+ parse1 ('_':cs) = parse2 (Just SpacePadding) cs+ parse1 ('0':cs) = parse2 (Just ZeroPadding) cs+ parse1 cs = parse2 Nothing cs++ parse2 :: Maybe ParseNumericPadding -> String -> ReadP [(Char,String)]+ parse2 mpad ('E':cs) = parse3 mpad True cs+ parse2 mpad cs = parse3 mpad False cs++ parse3 :: Maybe ParseNumericPadding -> Bool -> String -> ReadP [(Char,String)]+ parse3 _ _ ('%':cs) = do+ _ <- char '%'+ parse cs+ parse3 _ _ (c:cs) | Just s <- substituteTimeSpecifier pt locale c = parse $ s ++ cs+ parse3 mpad _alt (c:cs) = do+ str <- parseTimeSpecifier pt locale mpad c+ specs <- parse cs+ return $ (c,str) : specs+ parse3 _ _ [] = return []+ in parse++parsePaddedDigits :: ParseNumericPadding -> Int -> ReadP String+parsePaddedDigits ZeroPadding n = count n (satisfy isDigit)+parsePaddedDigits SpacePadding _n = skipSpaces >> many1 (satisfy isDigit)+parsePaddedDigits NoPadding _n = many1 (satisfy isDigit)++parsePaddedSignedDigits :: ParseNumericPadding -> Int -> ReadP String+parsePaddedSignedDigits pad n = do+ sign <- option "" $ char '-' >> return "-"+ digits <- parsePaddedDigits pad n+ return $ sign ++ digits++parseSignedDecimal :: ReadP String+parseSignedDecimal = do+ sign <- option "" $ char '-' >> return "-"+ skipSpaces+ digits <- many1 $ satisfy isDigit+ decimaldigits <- option "" $ do+ _ <- char '.'+ dd <- many $ satisfy isDigit+ return $ '.':dd+ return $ sign ++ digits ++ decimaldigits++timeParseTimeSpecifier :: TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String+timeParseTimeSpecifier l mpad c = let+ digits pad = parsePaddedDigits (fromMaybe pad mpad)+ oneOf = choice . map stringCI+ numericTZ = do+ s <- choice [char '+', char '-']+ h <- parsePaddedDigits ZeroPadding 2+ optional (char ':')+ m <- parsePaddedDigits ZeroPadding 2+ return (s:h++m)+ in case c of+ -- century+ 'C' -> digits SpacePadding 2+ 'f' -> digits SpacePadding 2++ -- year+ 'Y' -> digits SpacePadding 4+ 'G' -> digits SpacePadding 4++ -- year of century+ 'y' -> digits ZeroPadding 2+ 'g' -> digits ZeroPadding 2++ -- month of year+ 'B' -> oneOf (map fst (months l))+ 'b' -> oneOf (map snd (months l))+ 'm' -> digits ZeroPadding 2++ -- day of month+ 'd' -> digits ZeroPadding 2+ 'e' -> digits SpacePadding 2++ -- week of year+ 'V' -> digits ZeroPadding 2+ 'U' -> digits ZeroPadding 2+ 'W' -> digits ZeroPadding 2++ -- day of week+ 'u' -> oneOf $ map (:[]) ['1'..'7']+ 'a' -> oneOf (map snd (wDays l))+ 'A' -> oneOf (map fst (wDays l))+ 'w' -> oneOf $ map (:[]) ['0'..'6']++ -- day of year+ 'j' -> digits ZeroPadding 3++ -- dayhalf of day (i.e. AM or PM)+ 'P' -> oneOf (let (am,pm) = amPm l in [am, pm])+ 'p' -> oneOf (let (am,pm) = amPm l in [am, pm])++ -- hour of day (i.e. 24h)+ 'H' -> digits ZeroPadding 2+ 'k' -> digits SpacePadding 2++ -- hour of dayhalf (i.e. 12h)+ 'I' -> digits ZeroPadding 2+ 'l' -> digits SpacePadding 2++ -- minute of hour+ 'M' -> digits ZeroPadding 2++ -- second of minute+ 'S' -> digits ZeroPadding 2++ -- picosecond of second+ 'q' -> digits ZeroPadding 12+ 'Q' -> liftA2 (:) (char '.') (munch isDigit) <++ return ""++ -- time zone+ 'z' -> numericTZ+ 'Z' -> munch1 isAlpha <+++ numericTZ++ -- seconds since epoch+ 's' -> (char '-' >> fmap ('-':) (munch1 isDigit))+ <++ munch1 isDigit++ _ -> fail $ "Unknown format character: " ++ show c++timeSubstituteTimeSpecifier :: TimeLocale -> Char -> Maybe String+timeSubstituteTimeSpecifier l 'c' = Just $ dateTimeFmt l+timeSubstituteTimeSpecifier _ 'R' = Just "%H:%M"+timeSubstituteTimeSpecifier _ 'T' = Just "%H:%M:%S"+timeSubstituteTimeSpecifier l 'X' = Just $ timeFmt l+timeSubstituteTimeSpecifier l 'r' = Just $ time12Fmt l+timeSubstituteTimeSpecifier _ 'D' = Just "%m/%d/%y"+timeSubstituteTimeSpecifier _ 'F' = Just "%Y-%m-%d"+timeSubstituteTimeSpecifier l 'x' = Just $ dateFmt l+timeSubstituteTimeSpecifier _ 'h' = Just "%b"+timeSubstituteTimeSpecifier _ _ = Nothing++durationParseTimeSpecifier :: TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String+durationParseTimeSpecifier _ mpad c = let+ padopt = parsePaddedSignedDigits $ fromMaybe NoPadding mpad+ in case c of+ 'y' -> padopt 1+ 'b' -> padopt 1+ 'B' -> padopt 2+ 'w' -> padopt 1+ 'd' -> padopt 1+ 'D' -> padopt 1+ 'h' -> padopt 1+ 'H' -> padopt 2+ 'm' -> padopt 1+ 'M' -> padopt 2+ 's' -> parseSignedDecimal+ 'S' -> parseSignedDecimal+ _ -> fail $ "Unknown format character: " ++ show c
+ lib/Data/Time/Format/Parse/Instances.hs view
@@ -0,0 +1,392 @@+{-# OPTIONS -fno-warn-orphans #-}+module Data.Time.Format.Parse.Instances() where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>),(<*>))+#endif+import Data.Char+import Data.Fixed+import Data.List+import Data.Ratio+import Data.Traversable+import Text.Read(readMaybe)+import Data.Time.Clock.Internal.DiffTime+import Data.Time.Clock.Internal.NominalDiffTime+import Data.Time.Clock.Internal.UniversalTime+import Data.Time.Clock.POSIX+import Data.Time.Clock.Internal.UTCTime+import Data.Time.Calendar.Days+import Data.Time.Calendar.Gregorian+import Data.Time.Calendar.CalendarDiffDays+import Data.Time.Calendar.OrdinalDate+import Data.Time.Calendar.WeekDate+import Data.Time.Calendar.Private(clipValid)+import Data.Time.LocalTime.Internal.CalendarDiffTime+import Data.Time.LocalTime.Internal.TimeZone+import Data.Time.LocalTime.Internal.TimeOfDay+import Data.Time.LocalTime.Internal.LocalTime+import Data.Time.LocalTime.Internal.ZonedTime+import Data.Time.Format.Locale+import Data.Time.Format.Parse.Class++data DayComponent = Century Integer -- century of all years+ | CenturyYear Integer -- 0-99, last two digits of both real years and week years+ | YearMonth Int -- 1-12+ | MonthDay Int -- 1-31+ | YearDay Int -- 1-366+ | WeekDay Int -- 1-7 (mon-sun)+ | YearWeek WeekType Int -- 1-53 or 0-53++data WeekType = ISOWeek | SundayWeek | MondayWeek++instance ParseTime Day where+ substituteTimeSpecifier _ = timeSubstituteTimeSpecifier+ parseTimeSpecifier _ = timeParseTimeSpecifier+ buildTime l = let++ -- 'Nothing' indicates a parse failure,+ -- while 'Just []' means no information+ f :: Char -> String -> Maybe [DayComponent]+ f c x = let+ ra :: (Read a) => Maybe a+ ra = readMaybe x++ zeroBasedListIndex :: [String] -> Maybe Int+ zeroBasedListIndex ss = elemIndex (map toUpper x) $ fmap (map toUpper) ss++ oneBasedListIndex :: [String] -> Maybe Int+ oneBasedListIndex ss = do+ index <- zeroBasedListIndex ss+ return $ 1 + index++ in case c of+ -- %C: century (all but the last two digits of the year), 00 - 99+ 'C' -> do+ a <- ra+ return [Century a]+ -- %f century (all but the last two digits of the year), 00 - 99+ 'f' -> do+ a <- ra+ return [Century a]+ -- %Y: year+ 'Y' -> do+ a <- ra+ return [Century (a `div` 100), CenturyYear (a `mod` 100)]+ -- %G: year for Week Date format+ 'G' -> do+ a <- ra+ return [Century (a `div` 100), CenturyYear (a `mod` 100)]+ -- %y: last two digits of year, 00 - 99+ 'y' -> do+ a <- ra+ return [CenturyYear a]+ -- %g: last two digits of year for Week Date format, 00 - 99+ 'g' -> do+ a <- ra+ return [CenturyYear a]+ -- %B: month name, long form (fst from months locale), January - December+ 'B' -> do+ a <- oneBasedListIndex $ fmap fst $ months l+ return [YearMonth a]+ -- %b: month name, short form (snd from months locale), Jan - Dec+ 'b' -> do+ a <- oneBasedListIndex $ fmap snd $ months l+ return [YearMonth a]+ -- %m: month of year, leading 0 as needed, 01 - 12+ 'm' -> do+ raw <- ra+ a <- clipValid 1 12 raw+ return [YearMonth a]+ -- %d: day of month, leading 0 as needed, 01 - 31+ 'd' -> do+ raw <- ra+ a <- clipValid 1 31 raw+ return [MonthDay a]+ -- %e: day of month, leading space as needed, 1 - 31+ 'e' -> do+ raw <- ra+ a <- clipValid 1 31 raw+ return [MonthDay a]+ -- %V: week for Week Date format, 01 - 53+ 'V' -> do+ raw <- ra+ a <- clipValid 1 53 raw+ return [YearWeek ISOWeek a]+ -- %U: week number of year, where weeks start on Sunday (as sundayStartWeek), 00 - 53+ 'U' -> do+ raw <- ra+ a <- clipValid 0 53 raw+ return [YearWeek SundayWeek a]+ -- %W: week number of year, where weeks start on Monday (as mondayStartWeek), 00 - 53+ 'W' -> do+ raw <- ra+ a <- clipValid 0 53 raw+ return [YearWeek MondayWeek a]+ -- %u: day for Week Date format, 1 - 7+ 'u' -> do+ raw <- ra+ a <- clipValid 1 7 raw+ return [WeekDay a]+ -- %a: day of week, short form (snd from wDays locale), Sun - Sat+ 'a' -> do+ a' <- zeroBasedListIndex $ fmap snd $ wDays l+ let a = if a' == 0 then 7 else a'+ return [WeekDay a]+ -- %A: day of week, long form (fst from wDays locale), Sunday - Saturday+ 'A' -> do+ a' <- zeroBasedListIndex $ fmap fst $ wDays l+ let a = if a' == 0 then 7 else a'+ return [WeekDay a]+ -- %w: day of week number, 0 (= Sunday) - 6 (= Saturday)+ 'w' -> do+ raw <- ra+ a' <- clipValid 0 6 raw+ let a = if a' == 0 then 7 else a'+ return [WeekDay a]+ -- %j: day of year for Ordinal Date format, 001 - 366+ 'j' -> do+ raw <- ra+ a <- clipValid 1 366 raw+ return [YearDay a]+ -- unrecognised, pass on to other parsers+ _ -> return []++ buildDay :: [DayComponent] -> Maybe Day+ buildDay cs = let+ safeLast x xs = last (x:xs)+ y = let+ d = safeLast 70 [x | CenturyYear x <- cs]+ c = safeLast (if d >= 69 then 19 else 20) [x | Century x <- cs]+ in 100 * c + d+ rest (YearMonth m:_) = let+ d = safeLast 1 [x | MonthDay x <- cs]+ in fromGregorianValid y m d+ rest (YearDay d:_) = fromOrdinalDateValid y d+ rest (YearWeek wt w:_) = let+ d = safeLast 4 [x | WeekDay x <- cs]+ in case wt of+ ISOWeek -> fromWeekDateValid y w d+ SundayWeek -> fromSundayStartWeekValid y w (d `mod` 7)+ MondayWeek -> fromMondayStartWeekValid y w d+ rest (_:xs) = rest xs+ rest [] = rest [YearMonth 1]++ in rest cs++ in \pairs -> do+ components <- for pairs $ \(c,x) -> f c x+ buildDay $ concat components++mfoldl :: (Monad m) => (a -> b -> m a) -> m a -> [b] -> m a+mfoldl f = let+ mf ma b = do+ a <- ma+ f a b+ in foldl mf++instance ParseTime TimeOfDay where+ substituteTimeSpecifier _ = timeSubstituteTimeSpecifier+ parseTimeSpecifier _ = timeParseTimeSpecifier+ buildTime l = let+ f t@(TimeOfDay h m s) (c,x) = let+ ra :: (Read a) => Maybe a+ ra = readMaybe x++ getAmPm = let+ upx = map toUpper x+ (amStr,pmStr) = amPm l+ in if upx == amStr+ then Just $ TimeOfDay (h `mod` 12) m s+ else if upx == pmStr+ then Just $ TimeOfDay (if h < 12 then h + 12 else h) m s+ else Nothing++ in case c of+ 'P' -> getAmPm+ 'p' -> getAmPm+ 'H' -> do+ raw <- ra+ a <- clipValid 0 23 raw+ return $ TimeOfDay a m s+ 'I' -> do+ raw <- ra+ a <- clipValid 1 12 raw+ return $ TimeOfDay a m s+ 'k' -> do+ raw <- ra+ a <- clipValid 0 23 raw+ return $ TimeOfDay a m s+ 'l' -> do+ raw <- ra+ a <- clipValid 1 12 raw+ return $ TimeOfDay a m s+ 'M' -> do+ raw <- ra+ a <- clipValid 0 59 raw+ return $ TimeOfDay h a s+ 'S' -> do+ raw <- ra+ a <- clipValid 0 60 raw+ return $ TimeOfDay h m (fromInteger a)+ 'q' -> do+ a <- ra+ return $ TimeOfDay h m (mkPico (floor s) a)+ 'Q' -> if null x then Just t else do+ ps <- readMaybe $ take 12 $ rpad 12 '0' $ drop 1 x+ return $ TimeOfDay h m (mkPico (floor s) ps)+ _ -> Just t++ in mfoldl f (Just midnight)++rpad :: Int -> a -> [a] -> [a]+rpad n c xs = xs ++ replicate (n - length xs) c++mkPico :: Integer -> Integer -> Pico+mkPico i f = fromInteger i + fromRational (f % 1000000000000)++instance ParseTime LocalTime where+ substituteTimeSpecifier _ = timeSubstituteTimeSpecifier+ parseTimeSpecifier _ = timeParseTimeSpecifier+ buildTime l xs = LocalTime <$> (buildTime l xs) <*> (buildTime l xs)++enumDiff :: (Enum a) => a -> a -> Int+enumDiff a b = (fromEnum a) - (fromEnum b)++getMilZoneHours :: Char -> Maybe Int+getMilZoneHours c | c < 'A' = Nothing+getMilZoneHours c | c <= 'I' = Just $ 1 + enumDiff c 'A'+getMilZoneHours 'J' = Nothing+getMilZoneHours c | c <= 'M' = Just $ 10 + enumDiff c 'K'+getMilZoneHours c | c <= 'Y' = Just $ (enumDiff 'N' c) - 1+getMilZoneHours 'Z' = Just 0+getMilZoneHours _ = Nothing++getMilZone :: Char -> Maybe TimeZone+getMilZone c = let+ yc = toUpper c+ in do+ hours <- getMilZoneHours yc+ return $ TimeZone (hours * 60) False [yc]++getKnownTimeZone :: TimeLocale -> String -> Maybe TimeZone+getKnownTimeZone locale x = find (\tz -> map toUpper x == timeZoneName tz) (knownTimeZones locale)++instance ParseTime TimeZone where+ substituteTimeSpecifier _ = timeSubstituteTimeSpecifier+ parseTimeSpecifier _ = timeParseTimeSpecifier+ buildTime l = let+ f :: Char -> String -> TimeZone -> Maybe TimeZone+ f 'z' str (TimeZone _ dst name) | Just offset <- readTzOffset str = Just $ TimeZone offset dst name+ f 'z' _ _ = Nothing+ f 'Z' str _ | Just offset <- readTzOffset str = Just $ TimeZone offset False ""+ f 'Z' str _ | Just zone <- getKnownTimeZone l str = Just zone+ f 'Z' "UTC" _ = Just utc+ f 'Z' [c] _ | Just zone <- getMilZone c = Just zone+ f 'Z' _ _ = Nothing+ f _ _ tz = Just tz+ in foldl (\mt (c,s) -> mt >>= f c s) (Just $ minutesToTimeZone 0)++readTzOffset :: String -> Maybe Int+readTzOffset str = let++ getSign '+' = Just 1+ getSign '-' = Just (-1)+ getSign _ = Nothing++ calc s h1 h2 m1 m2 = do+ sign <- getSign s+ h <- readMaybe [h1,h2]+ m <- readMaybe [m1,m2]+ return $ sign * (60 * h + m)++ in case str of+ (s:h1:h2:':':m1:m2:[]) -> calc s h1 h2 m1 m2+ (s:h1:h2:m1:m2:[]) -> calc s h1 h2 m1 m2+ _ -> Nothing++instance ParseTime ZonedTime where+ substituteTimeSpecifier _ = timeSubstituteTimeSpecifier+ parseTimeSpecifier _ = timeParseTimeSpecifier+ buildTime l xs = let+ f (ZonedTime (LocalTime _ tod) z) ('s',x) = do+ a <- readMaybe x+ let+ s = fromInteger a+ (_,ps) = properFraction (todSec tod) :: (Integer,Pico)+ s' = s + fromRational (toRational ps)+ return $ utcToZonedTime z (posixSecondsToUTCTime s')+ f t _ = Just t+ in mfoldl f (ZonedTime <$> (buildTime l xs) <*> (buildTime l xs)) xs++instance ParseTime UTCTime where+ substituteTimeSpecifier _ = timeSubstituteTimeSpecifier+ parseTimeSpecifier _ = timeParseTimeSpecifier+ buildTime l xs = zonedTimeToUTC <$> buildTime l xs++instance ParseTime UniversalTime where+ substituteTimeSpecifier _ = timeSubstituteTimeSpecifier+ parseTimeSpecifier _ = timeParseTimeSpecifier+ buildTime l xs = localTimeToUT1 0 <$> buildTime l xs++buildTimeMonths :: [(Char,String)] -> Maybe Integer+buildTimeMonths xs = do+ tt <- for xs $ \(c,s) -> case c of+ 'y' -> fmap ((*) 12) $ readMaybe s+ 'b' -> readMaybe s+ 'B' -> readMaybe s+ _ -> return 0+ return $ sum tt++buildTimeDays :: [(Char,String)] -> Maybe Integer+buildTimeDays xs = do+ tt <- for xs $ \(c,s) -> case c of+ 'w' -> fmap ((*) 7) $ readMaybe s+ 'd' -> readMaybe s+ 'D' -> readMaybe s+ _ -> return 0+ return $ sum tt++buildTimeSeconds :: [(Char,String)] -> Maybe Pico+buildTimeSeconds xs = do+ tt <- for xs $ \(c,s) -> let+ readInt :: Integer -> Maybe Pico+ readInt t = do+ i <- readMaybe s+ return $ fromInteger $ i * t+ in case c of+ 'h' -> readInt 3600+ 'H' -> readInt 3600+ 'm' -> readInt 60+ 'M' -> readInt 60+ 's' -> readMaybe s+ 'S' -> readMaybe s+ _ -> return 0+ return $ sum tt++instance ParseTime NominalDiffTime where+ parseTimeSpecifier _ = durationParseTimeSpecifier+ buildTime _ xs = do+ dd <- buildTimeDays xs+ tt <- buildTimeSeconds xs+ return $ (fromInteger dd * 86400) + realToFrac tt++instance ParseTime DiffTime where+ parseTimeSpecifier _ = durationParseTimeSpecifier+ buildTime _ xs = do+ dd <- buildTimeDays xs+ tt <- buildTimeSeconds xs+ return $ (fromInteger dd * 86400) + realToFrac tt++instance ParseTime CalendarDiffDays where+ parseTimeSpecifier _ = durationParseTimeSpecifier+ buildTime _ xs = do+ mm <- buildTimeMonths xs+ dd <- buildTimeDays xs+ return $ CalendarDiffDays mm dd++instance ParseTime CalendarDiffTime where+ parseTimeSpecifier _ = durationParseTimeSpecifier+ buildTime locale xs = do+ mm <- buildTimeMonths xs+ tt <- buildTime locale xs+ return $ CalendarDiffTime mm tt
lib/Data/Time/LocalTime.hs view
@@ -7,6 +7,7 @@ getTimeZone,getCurrentTimeZone, module Data.Time.LocalTime.Internal.TimeOfDay,+ module Data.Time.LocalTime.Internal.CalendarDiffTime, module Data.Time.LocalTime.Internal.LocalTime, module Data.Time.LocalTime.Internal.ZonedTime, ) where@@ -14,5 +15,6 @@ import Data.Time.Format() import Data.Time.LocalTime.Internal.TimeZone hiding (timeZoneOffsetString'') import Data.Time.LocalTime.Internal.TimeOfDay+import Data.Time.LocalTime.Internal.CalendarDiffTime import Data.Time.LocalTime.Internal.LocalTime import Data.Time.LocalTime.Internal.ZonedTime
+ lib/Data/Time/LocalTime/Internal/CalendarDiffTime.hs view
@@ -0,0 +1,46 @@+module Data.Time.LocalTime.Internal.CalendarDiffTime+ (+ -- * Calendar Duration+ module Data.Time.LocalTime.Internal.CalendarDiffTime+ ) where+#if MIN_VERSION_base(4,8,0)+#else+import Data.Monoid+#endif+#if MIN_VERSION_base(4,9,0)+import Data.Semigroup hiding (option)+#endif+import Data.Fixed+import Data.Time.Calendar.CalendarDiffDays+import Data.Time.Clock.Internal.NominalDiffTime++data CalendarDiffTime = CalendarDiffTime+ { ctMonths :: Integer+ , ctTime :: NominalDiffTime+ } deriving (Eq)+#if MIN_VERSION_base(4,9,0)+-- | Additive+instance Semigroup CalendarDiffTime where+ CalendarDiffTime m1 d1 <> CalendarDiffTime m2 d2 = CalendarDiffTime (m1 + m2) (d1 + d2)+#endif+-- | Additive+instance Monoid CalendarDiffTime where+ mempty = CalendarDiffTime 0 0+#if MIN_VERSION_base(4,9,0)+ mappend = (<>)+#else+ mappend (CalendarDiffTime m1 d1) (CalendarDiffTime m2 d2) = CalendarDiffTime (m1 + m2) (d1 + d2)+#endif++instance Show CalendarDiffTime where+ show (CalendarDiffTime m t) = "P" ++ show m ++ "MT" ++ showFixed True (realToFrac t :: Pico) ++ "S"++calendarTimeDays :: CalendarDiffDays -> CalendarDiffTime+calendarTimeDays (CalendarDiffDays m d) = CalendarDiffTime m $ fromInteger d * nominalDay++calendarTimeTime :: NominalDiffTime -> CalendarDiffTime+calendarTimeTime dt = CalendarDiffTime 0 dt++-- | Scale by a factor. Note that @scaleCalendarDiffTime (-1)@ will not perfectly invert a duration, due to variable month lengths.+scaleCalendarDiffTime :: Integer -> CalendarDiffTime -> CalendarDiffTime+scaleCalendarDiffTime k (CalendarDiffTime m d) = CalendarDiffTime (k * m) (fromInteger k * d)
lib/Data/Time/LocalTime/Internal/LocalTime.hs view
@@ -1,12 +1,12 @@ {-# OPTIONS -fno-warn-orphans #-} #include "HsConfigure.h"---- #hide module Data.Time.LocalTime.Internal.LocalTime ( -- * Local Time LocalTime(..), + addLocalTime,diffLocalTime,+ -- converting UTC and UT1 times to LocalTime utcToLocalTime,localTimeToUTC,ut1ToLocalTime,localTimeToUT1, ) where@@ -20,7 +20,9 @@ #endif import Data.Time.Calendar.Days import Data.Time.Calendar.Gregorian+import Data.Time.Clock.Internal.NominalDiffTime import Data.Time.Clock.Internal.UniversalTime+import Data.Time.Clock.Internal.UTCDiff import Data.Time.Clock.Internal.UTCTime import Data.Time.LocalTime.Internal.TimeOfDay import Data.Time.LocalTime.Internal.TimeZone@@ -48,6 +50,14 @@ instance Show LocalTime where show (LocalTime d t) = (showGregorian d) ++ " " ++ (show t)++-- | addLocalTime a b = a + b+addLocalTime :: NominalDiffTime -> LocalTime -> LocalTime+addLocalTime x = utcToLocalTime utc . addUTCTime x . localTimeToUTC utc++-- | diffLocalTime a b = a - b+diffLocalTime :: LocalTime -> LocalTime -> NominalDiffTime+diffLocalTime a b = diffUTCTime (localTimeToUTC utc a) (localTimeToUTC utc b) -- | Get the local time of a UTC time in a time zone. utcToLocalTime :: TimeZone -> UTCTime -> LocalTime
lib/Data/Time/LocalTime/Internal/TimeOfDay.hs view
@@ -1,10 +1,10 @@ {-# OPTIONS -fno-warn-unused-imports #-} #include "HsConfigure.h"--- #hide module Data.Time.LocalTime.Internal.TimeOfDay ( -- * Time of day TimeOfDay(..),midnight,midday,makeTimeOfDayValid,+ timeToDaysAndTimeOfDay,daysAndTimeOfDayToTime, utcToLocalTimeOfDay,localToUTCTimeOfDay, timeToTimeOfDay,timeOfDayToTime, dayFractionToTimeOfDay,timeOfDayToDayFraction@@ -17,6 +17,7 @@ import Data.Data #endif import Data.Time.Clock.Internal.DiffTime+import Data.Time.Clock.Internal.NominalDiffTime import Data.Time.Calendar.Private import Data.Time.LocalTime.Internal.TimeZone @@ -60,6 +61,20 @@ _ <- clipValid 0 59 m _ <- clipValid 0 60.999999999999 s return (TimeOfDay h m s)++-- | Convert a period of time into a count of days and a time of day since midnight.+-- The time of day will never have a leap second.+timeToDaysAndTimeOfDay :: NominalDiffTime -> (Integer,TimeOfDay)+timeToDaysAndTimeOfDay dt = let+ s = realToFrac dt+ (m,ms) = divMod' s 60+ (h,hm) = divMod' m 60+ (d,dh) = divMod' h 24+ in (d,TimeOfDay dh hm ms)++-- | Convert a count of days and a time of day since midnight into a period of time.+daysAndTimeOfDayToTime :: Integer -> TimeOfDay -> NominalDiffTime+daysAndTimeOfDayToTime d (TimeOfDay dh hm ms) = (+) (realToFrac ms) $ (*) 60 $ (+) (realToFrac hm) $ (*) 60 $ (+) (realToFrac dh) $ (*) 24 $ realToFrac d -- | Convert a time of day in UTC to a time of day in some timezone, together with a day adjustment. utcToLocalTimeOfDay :: TimeZone -> TimeOfDay -> (Integer,TimeOfDay)
lib/Data/Time/LocalTime/Internal/TimeZone.hs view
@@ -1,8 +1,6 @@ {-# OPTIONS -fno-warn-unused-imports #-} {-# LANGUAGE ForeignFunctionInterface #-} #include "HsConfigure.h"---- #hide module Data.Time.LocalTime.Internal.TimeZone ( -- * Time zones@@ -57,21 +55,26 @@ hoursToTimeZone :: Int -> TimeZone hoursToTimeZone i = minutesToTimeZone (60 * i) -showT :: PadOption -> Int -> String-showT opt t = showPaddedNum opt ((div t 60) * 100 + (mod t 60))+showT :: Bool -> PadOption -> Int -> String+showT False opt t = showPaddedNum opt ((div t 60) * 100 + (mod t 60))+showT True opt t = let+ opt' = case opt of+ NoPad -> NoPad+ Pad i c -> Pad (max 0 $ i - 3) c+ in showPaddedNum opt' (div t 60) ++ ":" ++ show2 (mod t 60) -timeZoneOffsetString'' :: PadOption -> TimeZone -> String-timeZoneOffsetString'' opt (TimeZone t _ _) | t < 0 = '-':(showT opt (negate t))-timeZoneOffsetString'' opt (TimeZone t _ _) = '+':(showT opt t)+timeZoneOffsetString'' :: Bool -> PadOption -> TimeZone -> String+timeZoneOffsetString'' colon opt (TimeZone t _ _) | t < 0 = '-':(showT colon opt (negate t))+timeZoneOffsetString'' colon opt (TimeZone t _ _) = '+':(showT colon opt t) -- | Text representing the offset of this timezone, such as \"-0800\" or \"+0400\" (like @%z@ in formatTime), with arbitrary padding. timeZoneOffsetString' :: Maybe Char -> TimeZone -> String-timeZoneOffsetString' Nothing = timeZoneOffsetString'' NoPad-timeZoneOffsetString' (Just c) = timeZoneOffsetString'' $ Pad 4 c+timeZoneOffsetString' Nothing = timeZoneOffsetString'' False NoPad+timeZoneOffsetString' (Just c) = timeZoneOffsetString'' False $ Pad 4 c -- | Text representing the offset of this timezone, such as \"-0800\" or \"+0400\" (like @%z@ in formatTime). timeZoneOffsetString :: TimeZone -> String-timeZoneOffsetString = timeZoneOffsetString'' (Pad 4 '0')+timeZoneOffsetString = timeZoneOffsetString'' False (Pad 4 '0') instance Show TimeZone where show zone@(TimeZone _ _ "") = timeZoneOffsetString zone
lib/Data/Time/LocalTime/Internal/ZonedTime.hs view
@@ -1,7 +1,5 @@ {-# OPTIONS -fno-warn-orphans #-} #include "HsConfigure.h"---- #hide module Data.Time.LocalTime.Internal.ZonedTime ( ZonedTime(..),utcToZonedTime,zonedTimeToUTC,getZonedTime,utcToLocalZonedTime
test/main/Main.hs view
@@ -5,16 +5,21 @@ import Test.Calendar.Calendars import Test.Calendar.ClipDates import Test.Calendar.ConvertBack+import Test.Calendar.Duration import Test.Calendar.Easter import Test.Calendar.LongWeekYears import Test.Calendar.MonthDay import Test.Calendar.Valid+import Test.Calendar.Week import Test.Clock.Conversion import Test.Clock.Resolution import Test.Clock.TAI import Test.Format.Format import Test.Format.ParseTime+import Test.Format.ISO8601 import Test.LocalTime.Time+import Test.LocalTime.TimeOfDay+import Test.LocalTime.CalendarDiffTime tests :: TestTree@@ -27,7 +32,9 @@ longWeekYears, testMonthDay, testEaster,- testValid+ testValid,+ testWeek,+ testDuration ], testGroup "Clock" [ testClockConversion,@@ -36,10 +43,13 @@ ], testGroup "Format" [ testFormat,- testParseTime+ testParseTime,+ testISO8601 ], testGroup "LocalTime" [- testTime+ testTime,+ testTimeOfDay,+ testCalendarDiffTime ] ]
+ test/main/Test/Arbitrary.hs view
@@ -0,0 +1,107 @@+{-# OPTIONS -fno-warn-orphans #-}++module Test.Arbitrary where++import Control.Monad+import Data.Ratio+import Data.Time+import Data.Time.Clock.POSIX+import Test.Tasty.QuickCheck hiding (reason)++instance Arbitrary DayOfWeek where+ arbitrary = fmap toEnum $ choose (1,7)++instance Arbitrary Day where+ arbitrary = liftM ModifiedJulianDay $ choose (-313698, 2973483) -- 1000-01-1 to 9999-12-31+ shrink day = let+ (y, m, d) = toGregorian day+ dayShrink =+ if d > 1+ then [fromGregorian y m (d - 1)]+ else []+ monthShrink =+ if m > 1+ then [fromGregorian y (m - 1) d]+ else []+ yearShrink =+ if y > 2000+ then [fromGregorian (y - 1) m d]+ else if y < 2000+ then [fromGregorian (y + 1) m d]+ else []+ in dayShrink ++ monthShrink ++ yearShrink++instance CoArbitrary Day where+ coarbitrary (ModifiedJulianDay d) = coarbitrary d++instance Arbitrary CalendarDiffDays where+ arbitrary = liftM2 CalendarDiffDays arbitrary arbitrary++instance Arbitrary DiffTime where+ arbitrary = oneof [intSecs, fracSecs] -- up to 1 leap second+ where+ intSecs = liftM secondsToDiffTime' $ choose (0, 86400)+ fracSecs = liftM picosecondsToDiffTime' $ choose (0, 86400 * 10 ^ (12 :: Int))+ secondsToDiffTime' :: Integer -> DiffTime+ secondsToDiffTime' = fromInteger+ picosecondsToDiffTime' :: Integer -> DiffTime+ picosecondsToDiffTime' x = fromRational (x % 10 ^ (12 :: Int))++instance CoArbitrary DiffTime where+ coarbitrary t = coarbitrary (fromEnum t)++instance Arbitrary NominalDiffTime where+ arbitrary = oneof [intSecs, fracSecs]+ where+ limit = 1000 * 86400+ picofactor = 10 ^ (12 :: Int)+ intSecs = liftM secondsToDiffTime' $ choose (negate limit, limit)+ fracSecs = liftM picosecondsToDiffTime' $ choose (negate limit * picofactor, limit * picofactor)+ secondsToDiffTime' :: Integer -> NominalDiffTime+ secondsToDiffTime' = fromInteger+ picosecondsToDiffTime' :: Integer -> NominalDiffTime+ picosecondsToDiffTime' x = fromRational (x % 10 ^ (12 :: Int))++instance CoArbitrary NominalDiffTime where+ coarbitrary t = coarbitrary (fromEnum t)++instance Arbitrary CalendarDiffTime where+ arbitrary = liftM2 CalendarDiffTime arbitrary arbitrary++instance Arbitrary TimeOfDay where+ arbitrary = liftM timeToTimeOfDay arbitrary++instance CoArbitrary TimeOfDay where+ coarbitrary t = coarbitrary (timeOfDayToTime t)++instance Arbitrary LocalTime where+ arbitrary = liftM2 LocalTime arbitrary arbitrary++instance CoArbitrary LocalTime where+ coarbitrary t = coarbitrary (floor (utcTimeToPOSIXSeconds (localTimeToUTC utc t)) :: Integer)++instance Arbitrary TimeZone where+ arbitrary = liftM minutesToTimeZone $ choose (-720, 720)++instance CoArbitrary TimeZone where+ coarbitrary tz = coarbitrary (timeZoneMinutes tz)++instance Arbitrary ZonedTime where+ arbitrary = liftM2 ZonedTime arbitrary arbitrary++instance CoArbitrary ZonedTime where+ coarbitrary t = coarbitrary (floor (utcTimeToPOSIXSeconds (zonedTimeToUTC t)) :: Integer)++instance Arbitrary UTCTime where+ arbitrary = liftM2 UTCTime arbitrary arbitrary++instance CoArbitrary UTCTime where+ coarbitrary t = coarbitrary (floor (utcTimeToPOSIXSeconds t) :: Integer)++instance Arbitrary UniversalTime where+ arbitrary = liftM (\n -> ModJulianDate $ n % k) $ choose (-313698 * k, 2973483 * k) -- 1000-01-1 to 9999-12-31+ where+ k = 86400++instance CoArbitrary UniversalTime where+ coarbitrary (ModJulianDate d) = coarbitrary d
+ test/main/Test/Calendar/Duration.hs view
@@ -0,0 +1,46 @@+module Test.Calendar.Duration+ ( testDuration+ ) where++import Data.Time.Calendar+import Data.Time.Calendar.Julian+import Test.Arbitrary ()+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck hiding (reason)++testAddDiff :: TestTree+testAddDiff =+ testGroup+ "add diff"+ [ testProperty "add diff GregorianDurationClip" $ \day1 day2 ->+ addGregorianDurationClip (diffGregorianDurationClip day2 day1) day1 == day2+ , testProperty "add diff GregorianDurationRollOver" $ \day1 day2 ->+ addGregorianDurationRollOver (diffGregorianDurationRollOver day2 day1) day1 == day2+ , testProperty "add diff JulianDurationClip" $ \day1 day2 ->+ addJulianDurationClip (diffJulianDurationClip day2 day1) day1 == day2+ , testProperty "add diff JulianDurationRollOver" $ \day1 day2 ->+ addJulianDurationRollOver (diffJulianDurationRollOver day2 day1) day1 == day2+ ]++testClip :: (Integer, Int, Int) -> (Integer, Int, Int) -> (Integer, Integer) -> TestTree+testClip (y1,m1,d1) (y2,m2,d2) (em, ed) = let+ day1 = fromGregorian y1 m1 d1+ day2 = fromGregorian y2 m2 d2+ expected = CalendarDiffDays em ed+ found = diffGregorianDurationClip day1 day2+ in testCase (show day1 ++ " - " ++ show day2) $ assertEqual "" expected found++testDiffs :: TestTree+testDiffs =+ testGroup+ "diffs"+ [ testClip (2017, 04, 07) (2017, 04, 07) (0, 0)+ , testClip (2017, 04, 07) (2017, 04, 01) (0, 6)+ , testClip (2017, 04, 01) (2017, 04, 07) (0, -6)+ , testClip (2017, 04, 07) (2017, 02, 01) (2, 6)+ , testClip (2017, 02, 01) (2017, 04, 07) (-2, -6)+ ]++testDuration :: TestTree+testDuration = testGroup "CalendarDiffDays" [testAddDiff, testDiffs]
+ test/main/Test/Calendar/Week.hs view
@@ -0,0 +1,93 @@+module Test.Calendar.Week+ ( testWeek+ ) where++import Data.Time.Calendar+import Data.Time.Calendar.WeekDate+import Test.Tasty+import Test.Tasty.HUnit++testDay :: TestTree+testDay =+ testCase "day" $ do+ let day = fromGregorian 2018 1 9+ assertEqual "" (ModifiedJulianDay 58127) day+ assertEqual "" (2018, 2, 2) $ toWeekDate day+ assertEqual "" Tuesday $ dayOfWeek day++allDaysOfWeek :: [DayOfWeek]+allDaysOfWeek = [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]++testAllDays :: String -> (DayOfWeek -> IO ()) -> TestTree+testAllDays name f = testGroup name $ fmap (\wd -> testCase (show wd) $ f wd) allDaysOfWeek++testSucc :: TestTree+testSucc = testAllDays "succ" $ \wd -> assertEqual "" (toEnum $ succ $ fromEnum wd) $ succ wd++testPred :: TestTree+testPred = testAllDays "pred" $ \wd -> assertEqual "" (toEnum $ pred $ fromEnum wd) $ pred wd++testSequences :: TestTree+testSequences =+ testGroup+ "sequence"+ [ testCase "[Monday .. Sunday]" $ assertEqual "" allDaysOfWeek [Monday .. Sunday]+ , testCase "[Wednesday .. Wednesday]" $ assertEqual "" [Wednesday] [Wednesday .. Wednesday]+ , testCase "[Sunday .. Saturday]" $+ assertEqual "" [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] [Sunday .. Saturday]+ , testCase "[Thursday .. Wednesday]" $+ assertEqual "" [Thursday, Friday, Saturday, Sunday, Monday, Tuesday, Wednesday] [Thursday .. Wednesday]+ , testCase "[Tuesday ..]" $+ assertEqual+ ""+ [ Tuesday+ , Wednesday+ , Thursday+ , Friday+ , Saturday+ , Sunday+ , Monday+ , Tuesday+ , Wednesday+ , Thursday+ , Friday+ , Saturday+ , Sunday+ , Monday+ , Tuesday+ ] $+ take 15 [Tuesday ..]+ , testCase "[Wednesday, Tuesday ..]" $+ assertEqual+ ""+ [ Wednesday+ , Tuesday+ , Monday+ , Sunday+ , Saturday+ , Friday+ , Thursday+ , Wednesday+ , Tuesday+ , Monday+ , Sunday+ , Saturday+ , Friday+ , Thursday+ , Wednesday+ ] $+ take 15 [Wednesday,Tuesday ..]+ , testCase "[Sunday, Friday ..]" $+ assertEqual "" [Sunday, Friday, Wednesday, Monday, Saturday, Thursday, Tuesday, Sunday] $+ take 8 [Sunday,Friday ..]+ , testCase "[Monday,Sunday .. Tuesday]" $+ assertEqual "" [Monday, Sunday, Saturday, Friday, Thursday, Wednesday, Tuesday] [Monday,Sunday .. Tuesday]+ , testCase "[Thursday, Saturday .. Tuesday]" $+ assertEqual "" [Thursday, Saturday, Monday, Wednesday, Friday, Sunday, Tuesday] [Thursday,Saturday .. Tuesday]+ ]++testReadShow :: TestTree+testReadShow = testAllDays "read show" $ \wd -> assertEqual "" wd $ read $ show wd++testWeek :: TestTree+testWeek = testGroup "Week" [testDay, testSucc, testPred, testSequences, testReadShow]
test/main/Test/Format/Format.hs view
@@ -1,7 +1,7 @@ module Test.Format.Format(testFormat) where import Data.Time-import Control.Exception;+import Data.Proxy import Test.Tasty import Test.Tasty.HUnit import Test.TestUtil@@ -29,45 +29,130 @@ somestrings :: [String] somestrings = ["", " ", "-", "\n"] -getBottom :: a -> IO (Maybe Control.Exception.SomeException);-getBottom a = Control.Exception.catch (seq a (return Nothing)) (return . Just);--compareExpected :: (Eq t,Show t,ParseTime t) => String -> String -> String -> Maybe t -> TestTree-compareExpected testname fmt str expected = testCase testname $ do- let found = parseTimeM False defaultTimeLocale fmt str- mex <- getBottom found- case mex of- Just ex -> assertFailure $ unwords [ "Exception: expected" , show expected ++ ", caught", show ex]- Nothing -> assertEqual "" expected found--class (ParseTime t) => TestParse t where- expectedParse :: String -> String -> Maybe t- expectedParse "%Z" "" = buildTime defaultTimeLocale []- expectedParse "%_Z" "" = buildTime defaultTimeLocale []- expectedParse "%-Z" "" = buildTime defaultTimeLocale []- expectedParse "%0Z" "" = buildTime defaultTimeLocale []- expectedParse _ _ = Nothing--instance TestParse Day-instance TestParse TimeOfDay-instance TestParse LocalTime-instance TestParse TimeZone-instance TestParse ZonedTime-instance TestParse UTCTime+compareExpected :: (Eq t,Show t,ParseTime t) => String -> String -> String -> proxy t -> TestTree+compareExpected testname fmt str proxy = testCase testname $ do+ let+ found :: ParseTime t => proxy t -> Maybe t+ found _ = parseTimeM False defaultTimeLocale fmt str+ assertEqual "" Nothing $ found proxy checkParse :: String -> String -> [TestTree] checkParse fmt str = [- compareExpected "Day" fmt str (expectedParse fmt str :: Maybe Day),- compareExpected "TimeOfDay" fmt str (expectedParse fmt str :: Maybe TimeOfDay),- compareExpected "LocalTime" fmt str (expectedParse fmt str :: Maybe LocalTime),- compareExpected "TimeZone" fmt str (expectedParse fmt str :: Maybe TimeZone),- compareExpected "UTCTime" fmt str (expectedParse fmt str :: Maybe UTCTime)+ compareExpected "Day" fmt str (Proxy :: Proxy Day),+ compareExpected "TimeOfDay" fmt str (Proxy :: Proxy TimeOfDay),+ compareExpected "LocalTime" fmt str (Proxy :: Proxy LocalTime),+ compareExpected "TimeZone" fmt str (Proxy :: Proxy TimeZone),+ compareExpected "UTCTime" fmt str (Proxy :: Proxy UTCTime) ] testCheckParse :: TestTree testCheckParse = testGroup "checkParse" $ tgroup formats $ \fmt -> tgroup somestrings $ \str -> checkParse fmt str +days :: [Day]+days = [(fromGregorian 2018 1 5) .. (fromGregorian 2018 1 26)]++testDayOfWeek :: TestTree+testDayOfWeek = testGroup "DayOfWeek" $ tgroup "uwaA" $ \fmt -> tgroup days $ \day -> let+ dayFormat = formatTime defaultTimeLocale ['%',fmt] day+ dowFormat = formatTime defaultTimeLocale ['%',fmt] $ dayOfWeek day+ in assertEqual "" dayFormat dowFormat++testZone :: String -> String -> Int -> TestTree+testZone fmt expected minutes = testCase (show fmt) $ assertEqual "" expected $ formatTime defaultTimeLocale fmt $ TimeZone minutes False ""++testZonePair :: String -> String -> Int -> TestTree+testZonePair mods expected minutes = testGroup (show mods ++ " " ++ show minutes)+ [+ testZone ("%" ++ mods ++ "z") expected minutes,+ testZone ("%" ++ mods ++ "Z") expected minutes+ ]++testTimeZone :: TestTree+testTimeZone = testGroup "TimeZone"+ [+ testZonePair "" "+0000" 0,+ testZonePair "E" "+00:00" 0,+ testZonePair "" "+0500" 300,+ testZonePair "E" "+05:00" 300,+ testZonePair "3" "+0500" 300,+ testZonePair "4E" "+05:00" 300,+ testZonePair "4" "+0500" 300,+ testZonePair "5E" "+05:00" 300,+ testZonePair "5" "+00500" 300,+ testZonePair "6E" "+005:00" 300,+ testZonePair "" "-0700" (-420),+ testZonePair "E" "-07:00" (-420),+ testZonePair "" "+1015" 615,+ testZonePair "E" "+10:15" 615,+ testZonePair "3" "+1015" 615,+ testZonePair "4E" "+10:15" 615,+ testZonePair "4" "+1015" 615,+ testZonePair "5E" "+10:15" 615,+ testZonePair "5" "+01015" 615,+ testZonePair "6E" "+010:15" 615,+ testZonePair "" "-1130" (-690),+ testZonePair "E" "-11:30" (-690)+ ]++testAFormat :: FormatTime t => String -> String -> t -> TestTree+testAFormat fmt expected t = testCase fmt $ assertEqual "" expected $ formatTime defaultTimeLocale fmt t++testNominalDiffTime :: TestTree+testNominalDiffTime = testGroup "NominalDiffTime"+ [+ testAFormat "%ww%Dd%Hh%Mm%ESs" "3w2d2h22m8.21s" $ (fromRational $ 23 * 86400 + 8528.21 :: NominalDiffTime),+ testAFormat "%dd %hh %mm %ss %Ess" "0d 0h 0m 0s 0.74s" $ (fromRational $ 0.74 :: NominalDiffTime),+ testAFormat "%dd %hh %mm %ss %Ess" "0d 0h 0m 0s -0.74s" $ (fromRational $ negate $ 0.74 :: NominalDiffTime),+ testAFormat "%dd %hh %mm %ss %Ess %0Ess" "23d 554h 33262m 1995728s 1995728.21s 1995728.210000000000s" $ (fromRational $ 23 * 86400 + 8528.21 :: NominalDiffTime),+ testAFormat "%ww%Dd%Hh%Mm%Ss" "-3w-2d-2h-22m-8s" $ (fromRational $ negate $ 23 * 86400 + 8528.21 :: NominalDiffTime),+ testAFormat "%ww%Dd%Hh%Mm%ESs" "-3w-2d-2h-22m-8.21s" $ (fromRational $ negate $ 23 * 86400 + 8528.21 :: NominalDiffTime),+ testAFormat "%ww%Dd%Hh%Mm%Ss" "-3w-2d-2h-22m0s" $ (fromRational $ negate $ 23 * 86400 + 8520.21 :: NominalDiffTime),+ testAFormat "%ww%Dd%Hh%Mm%ESs" "-3w-2d-2h-22m-0.21s" $ (fromRational $ negate $ 23 * 86400 + 8520.21 :: NominalDiffTime),+ testAFormat "%dd %hh %mm %Ess" "-23d -554h -33262m -1995728.21s" $ (fromRational $ negate $ 23 * 86400 + 8528.21 :: NominalDiffTime)+ ]++testDiffTime :: TestTree+testDiffTime = testGroup "DiffTime"+ [+ testAFormat "%ww%Dd%Hh%Mm%ESs" "3w2d2h22m8.21s" $ (fromRational $ 23 * 86400 + 8528.21 :: DiffTime),+ testAFormat "%dd %hh %mm %ss %Ess" "0d 0h 0m 0s 0.74s" $ (fromRational $ 0.74 :: DiffTime),+ testAFormat "%dd %hh %mm %ss %Ess" "0d 0h 0m 0s -0.74s" $ (fromRational $ negate $ 0.74 :: DiffTime),+ testAFormat "%dd %hh %mm %ss %Ess %0Ess" "23d 554h 33262m 1995728s 1995728.21s 1995728.210000000000s" $ (fromRational $ 23 * 86400 + 8528.21 :: DiffTime),+ testAFormat "%ww%Dd%Hh%Mm%Ss" "-3w-2d-2h-22m-8s" $ (fromRational $ negate $ 23 * 86400 + 8528.21 :: DiffTime),+ testAFormat "%ww%Dd%Hh%Mm%ESs" "-3w-2d-2h-22m-8.21s" $ (fromRational $ negate $ 23 * 86400 + 8528.21 :: DiffTime),+ testAFormat "%ww%Dd%Hh%Mm%Ss" "-3w-2d-2h-22m0s" $ (fromRational $ negate $ 23 * 86400 + 8520.21 :: DiffTime),+ testAFormat "%ww%Dd%Hh%Mm%ESs" "-3w-2d-2h-22m-0.21s" $ (fromRational $ negate $ 23 * 86400 + 8520.21 :: DiffTime),+ testAFormat "%dd %hh %mm %Ess" "-23d -554h -33262m -1995728.21s" $ (fromRational $ negate $ 23 * 86400 + 8528.21 :: DiffTime)+ ]++testCalenderDiffDays :: TestTree+testCalenderDiffDays = testGroup "CalenderDiffDays"+ [+ testAFormat "%yy%Bm%ww%Dd" "5y4m3w2d" $ CalendarDiffDays 64 23,+ testAFormat "%bm %dd" "64m 23d" $ CalendarDiffDays 64 23,+ testAFormat "%yy%Bm%ww%Dd" "-5y-4m-3w-2d" $ CalendarDiffDays (-64) (-23),+ testAFormat "%bm %dd" "-64m -23d" $ CalendarDiffDays (-64) (-23)+ ]++testCalenderDiffTime :: TestTree+testCalenderDiffTime = testGroup "CalenderDiffTime"+ [+ testAFormat "%yy%Bm%ww%Dd%Hh%Mm%Ss" "5y4m3w2d2h22m8s" $ CalendarDiffTime 64 $ 23 * 86400 + 8528.21,+ testAFormat "%yy%Bm%ww%Dd%Hh%Mm%ESs" "5y4m3w2d2h22m8.21s" $ CalendarDiffTime 64 $ 23 * 86400 + 8528.21,+ testAFormat "%yy%Bm%ww%Dd%Hh%Mm%0ESs" "5y4m3w2d2h22m08.210000000000s" $ CalendarDiffTime 64 $ 23 * 86400 + 8528.21,+ testAFormat "%bm %dd %hh %mm %Ess" "64m 23d 554h 33262m 1995728.21s" $ CalendarDiffTime 64 $ 23 * 86400 + 8528.21,+ testAFormat "%yy%Bm%ww%Dd%Hh%Mm%Ss" "-5y-4m-3w-2d-2h-22m-8s" $ CalendarDiffTime (-64) $ negate $ 23 * 86400 + 8528.21,+ testAFormat "%yy%Bm%ww%Dd%Hh%Mm%ESs" "-5y-4m-3w-2d-2h-22m-8.21s" $ CalendarDiffTime (-64) $ negate $ 23 * 86400 + 8528.21,+ testAFormat "%bm %dd %hh %mm %Ess" "-64m -23d -554h -33262m -1995728.21s" $ CalendarDiffTime (-64) $ negate $ 23 * 86400 + 8528.21+ ]+ testFormat :: TestTree testFormat = testGroup "testFormat" $ [- testCheckParse+ testCheckParse,+ testDayOfWeek,+ testTimeZone,+ testNominalDiffTime,+ testDiffTime,+ testCalenderDiffDays,+ testCalenderDiffTime ]
+ test/main/Test/Format/ISO8601.hs view
@@ -0,0 +1,290 @@+{-# OPTIONS -fno-warn-orphans #-}+module Test.Format.ISO8601(testISO8601) where++import Data.Ratio+import Data.Time+import Data.Time.Format.ISO8601+import Test.QuickCheck.Property+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck hiding (reason)+import Test.TestUtil+import Test.Arbitrary()+++deriving instance Eq ZonedTime++readShowProperty :: (Eq a,Show a) => Format a -> a -> Property+readShowProperty fmt val = case formatShowM fmt val of+ Nothing -> property Discard+ Just str -> let+ found = formatParseM fmt str+ expected = Just val+ in property $ if expected == found then succeeded else+ failed {reason = show str ++ ": expected " ++ (show expected) ++ ", found " ++ (show found)}++readBoth :: NameTest t => (FormatExtension -> t) -> [TestTree]+readBoth fmts =+ [+ nameTest "extended" $ fmts ExtendedFormat,+ nameTest "basic" $ fmts BasicFormat+ ]++readShowProperties :: (Eq a,Show a,Arbitrary a) => (FormatExtension -> Format a) -> [TestTree]+readShowProperties fmts = readBoth $ \fe -> readShowProperty $ fmts fe++newtype Durational t = MkDurational t++instance Show t => Show (Durational t) where+ show (MkDurational t) = show t++instance Arbitrary (Durational CalendarDiffDays) where+ arbitrary = do+ mm <- choose (-10000,10000)+ dd <- choose (-40,40)+ return $ MkDurational $ CalendarDiffDays mm dd++instance Arbitrary (Durational CalendarDiffTime) where+ arbitrary = let+ limit = 40 * 86400+ picofactor = 10 ^ (12 :: Int)+ in do+ mm <- choose (-10000,10000)+ ss <- choose (negate limit * picofactor, limit * picofactor)+ return $ MkDurational $ CalendarDiffTime mm $ fromRational $ ss % picofactor++testReadShowFormat :: TestTree+testReadShowFormat = nameTest "read-show format"+ [+ nameTest "calendarFormat" $ readShowProperties $ calendarFormat,+ nameTest "yearMonthFormat" $ readShowProperty $ yearMonthFormat,+ nameTest "yearFormat" $ readShowProperty $ yearFormat,+ nameTest "centuryFormat" $ readShowProperty $ centuryFormat,+ nameTest "expandedCalendarFormat" $ readShowProperties $ expandedCalendarFormat 6,+ nameTest "expandedYearMonthFormat" $ readShowProperty $ expandedYearMonthFormat 6,+ nameTest "expandedYearFormat" $ readShowProperty $ expandedYearFormat 6,+ nameTest "expandedCenturyFormat" $ readShowProperty $ expandedCenturyFormat 4,+ nameTest "ordinalDateFormat" $ readShowProperties $ ordinalDateFormat,+ nameTest "expandedOrdinalDateFormat" $ readShowProperties $ expandedOrdinalDateFormat 6,+ nameTest "weekDateFormat" $ readShowProperties $ weekDateFormat,+ nameTest "yearWeekFormat" $ readShowProperties $ yearWeekFormat,+ nameTest "expandedWeekDateFormat" $ readShowProperties $ expandedWeekDateFormat 6,+ nameTest "expandedYearWeekFormat" $ readShowProperties $ expandedYearWeekFormat 6,+ nameTest "timeOfDayFormat" $ readShowProperties $ timeOfDayFormat,+ nameTest "hourMinuteFormat" $ readShowProperties $ hourMinuteFormat,+ nameTest "hourFormat" $ readShowProperty $ hourFormat,+ nameTest "withTimeDesignator" $ readShowProperties $ \fe -> withTimeDesignator $ timeOfDayFormat fe,+ nameTest "withUTCDesignator" $ readShowProperties $ \fe -> withUTCDesignator $ timeOfDayFormat fe,+ nameTest "timeOffsetFormat" $ readShowProperties $ timeOffsetFormat,+ nameTest "timeOfDayAndOffsetFormat" $ readShowProperties $ timeOfDayAndOffsetFormat,+ nameTest "localTimeFormat" $ readShowProperties $ \fe -> localTimeFormat (calendarFormat fe) (timeOfDayFormat fe),+ nameTest "zonedTimeFormat" $ readShowProperties $ \fe -> zonedTimeFormat (calendarFormat fe) (timeOfDayFormat fe) fe,+ nameTest "utcTimeFormat" $ readShowProperties $ \fe -> utcTimeFormat (calendarFormat fe) (timeOfDayFormat fe),+ nameTest "dayAndTimeFormat" $ readShowProperties $ \fe -> dayAndTimeFormat (calendarFormat fe) (timeOfDayFormat fe),+ nameTest "timeAndOffsetFormat" $ readShowProperties $ \fe -> timeAndOffsetFormat (timeOfDayFormat fe) fe,+ nameTest "durationDaysFormat" $ readShowProperty $ durationDaysFormat,+ nameTest "durationTimeFormat" $ readShowProperty $ durationTimeFormat,+ nameTest "alternativeDurationDaysFormat" $ readBoth $ \fe (MkDurational t) -> readShowProperty (alternativeDurationDaysFormat fe) t,+ nameTest "alternativeDurationTimeFormat" $ readBoth $ \fe (MkDurational t) -> readShowProperty (alternativeDurationTimeFormat fe) t,+ nameTest "intervalFormat" $ readShowProperties $ \fe -> intervalFormat (localTimeFormat (calendarFormat fe) (timeOfDayFormat fe)) durationTimeFormat,+ nameTest "recurringIntervalFormat" $ readShowProperties $ \fe -> recurringIntervalFormat (localTimeFormat (calendarFormat fe) (timeOfDayFormat fe)) durationTimeFormat+ ]++testShowFormat :: String -> Format t -> String -> t -> TestTree+testShowFormat name fmt str t = nameTest (name ++ ": " ++ str) $+ assertEqual "" (Just str) $ formatShowM fmt t++testShowFormats :: TestTree+testShowFormats = nameTest "show format"+ [+ testShowFormat "durationDaysFormat" durationDaysFormat "P0D" $ CalendarDiffDays 0 0,+ testShowFormat "durationDaysFormat" durationDaysFormat "P4Y" $ CalendarDiffDays 48 0,+ testShowFormat "durationDaysFormat" durationDaysFormat "P7M" $ CalendarDiffDays 7 0,+ testShowFormat "durationDaysFormat" durationDaysFormat "P5D" $ CalendarDiffDays 0 5,+ testShowFormat "durationDaysFormat" durationDaysFormat "P2Y3M81D" $ CalendarDiffDays 27 81,+ testShowFormat "durationTimeFormat" durationTimeFormat "P0D" $ CalendarDiffTime 0 0,+ testShowFormat "durationTimeFormat" durationTimeFormat "P4Y" $ CalendarDiffTime 48 0,+ testShowFormat "durationTimeFormat" durationTimeFormat "P7M" $ CalendarDiffTime 7 0,+ testShowFormat "durationTimeFormat" durationTimeFormat "P5D" $ CalendarDiffTime 0 $ 5 * nominalDay,+ testShowFormat "durationTimeFormat" durationTimeFormat "P2Y3M81D" $ CalendarDiffTime 27 $ 81 * nominalDay,+ testShowFormat "durationTimeFormat" durationTimeFormat "PT2H" $ CalendarDiffTime 0 $ 7200,+ testShowFormat "durationTimeFormat" durationTimeFormat "PT3M" $ CalendarDiffTime 0 $ 180,+ testShowFormat "durationTimeFormat" durationTimeFormat "PT12S" $ CalendarDiffTime 0 $ 12,+ testShowFormat "durationTimeFormat" durationTimeFormat "PT1M18.77634S" $ CalendarDiffTime 0 $ 78.77634,+ testShowFormat "durationTimeFormat" durationTimeFormat "PT2H1M18.77634S" $ CalendarDiffTime 0 $ 7278.77634,+ testShowFormat "durationTimeFormat" durationTimeFormat "P5DT2H1M18.77634S" $ CalendarDiffTime 0 $ 5 * nominalDay + 7278.77634,+ testShowFormat "durationTimeFormat" durationTimeFormat "P7Y10M5DT2H1M18.77634S" $ CalendarDiffTime 94 $ 5 * nominalDay + 7278.77634,+ testShowFormat "durationTimeFormat" durationTimeFormat "P7Y10MT2H1M18.77634S" $ CalendarDiffTime 94 $ 7278.77634,+ testShowFormat "durationTimeFormat" durationTimeFormat "P8YT2H1M18.77634S" $ CalendarDiffTime 96 $ 7278.77634,+ testShowFormat "alternativeDurationDaysFormat" (alternativeDurationDaysFormat ExtendedFormat) "P0001-00-00" $ CalendarDiffDays 12 0,+ testShowFormat "alternativeDurationDaysFormat" (alternativeDurationDaysFormat ExtendedFormat) "P0002-03-29" $ CalendarDiffDays 27 29,+ testShowFormat "alternativeDurationDaysFormat" (alternativeDurationDaysFormat ExtendedFormat) "P0561-08-29" $ CalendarDiffDays (561 * 12 + 8) 29,+ testShowFormat "alternativeDurationTimeFormat" (alternativeDurationTimeFormat ExtendedFormat) "P0000-00-01T00:00:00" $ CalendarDiffTime 0 86400,+ testShowFormat "alternativeDurationTimeFormat" (alternativeDurationTimeFormat ExtendedFormat) "P0007-10-05T02:01:18.77634" $ CalendarDiffTime 94 $ 5 * nominalDay + 7278.77634,+ testShowFormat "alternativeDurationTimeFormat" (alternativeDurationTimeFormat ExtendedFormat) "P4271-10-05T02:01:18.77634" $ CalendarDiffTime (12 * 4271 + 10) $ 5 * nominalDay + 7278.77634,+ testShowFormat "centuryFormat" centuryFormat "02" 2,+ testShowFormat "centuryFormat" centuryFormat "21" 21,+ testShowFormat "intervalFormat etc."+ (intervalFormat (localTimeFormat (calendarFormat ExtendedFormat) (timeOfDayFormat ExtendedFormat)) durationTimeFormat)+ "2015-06-13T21:13:56/P1Y2M7DT5H33M2.34S"+ (LocalTime (fromGregorian 2015 6 13) (TimeOfDay 21 13 56),CalendarDiffTime 14 $ 7 * nominalDay + 5 * 3600 + 33 * 60 + 2.34),+ testShowFormat "recurringIntervalFormat etc."+ (recurringIntervalFormat (localTimeFormat (calendarFormat ExtendedFormat) (timeOfDayFormat ExtendedFormat)) durationTimeFormat)+ "R74/2015-06-13T21:13:56/P1Y2M7DT5H33M2.34S"+ (74,LocalTime (fromGregorian 2015 6 13) (TimeOfDay 21 13 56),CalendarDiffTime 14 $ 7 * nominalDay + 5 * 3600 + 33 * 60 + 2.34),+ testShowFormat "recurringIntervalFormat etc."+ (recurringIntervalFormat (calendarFormat ExtendedFormat) durationDaysFormat)+ "R74/2015-06-13/P1Y2M7D"+ (74,fromGregorian 2015 6 13,CalendarDiffDays 14 7),+ testShowFormat "timeOffsetFormat"+ iso8601Format+ "-06:30"+ (minutesToTimeZone (-390)),+ testShowFormat "timeOffsetFormat"+ iso8601Format+ "+00:00"+ (minutesToTimeZone 0),+ testShowFormat "timeOffsetFormat"+ (timeOffsetFormat BasicFormat)+ "+0000"+ (minutesToTimeZone 0),+ testShowFormat "timeOffsetFormat"+ iso8601Format+ "+00:10"+ (minutesToTimeZone 10),+ testShowFormat "timeOffsetFormat"+ iso8601Format+ "-00:10"+ (minutesToTimeZone (-10)),+ testShowFormat "timeOffsetFormat"+ iso8601Format+ "+01:35"+ (minutesToTimeZone 95),+ testShowFormat "timeOffsetFormat"+ iso8601Format+ "-01:35"+ (minutesToTimeZone (-95)),+ testShowFormat "timeOffsetFormat"+ (timeOffsetFormat BasicFormat)+ "+0135"+ (minutesToTimeZone 95),+ testShowFormat "timeOffsetFormat"+ (timeOffsetFormat BasicFormat)+ "-0135"+ (minutesToTimeZone (-95)),+ testShowFormat "timeOffsetFormat"+ (timeOffsetFormat BasicFormat)+ "-1100"+ (minutesToTimeZone $ negate $ 11 * 60),+ testShowFormat "timeOffsetFormat"+ (timeOffsetFormat BasicFormat)+ "+1015"+ (minutesToTimeZone $ 615),+ testShowFormat "zonedTimeFormat"+ iso8601Format+ "2024-07-06T08:45:56.553-06:30"+ (ZonedTime (LocalTime (fromGregorian 2024 07 06) (TimeOfDay 8 45 56.553)) (minutesToTimeZone (-390))),+ testShowFormat "zonedTimeFormat"+ iso8601Format+ "2024-07-06T08:45:56.553+06:30"+ (ZonedTime (LocalTime (fromGregorian 2024 07 06) (TimeOfDay 8 45 56.553)) (minutesToTimeZone 390)),+ testShowFormat "utcTimeFormat"+ iso8601Format+ "2024-07-06T08:45:56.553Z"+ (UTCTime (fromGregorian 2024 07 06) (timeOfDayToTime $ TimeOfDay 8 45 56.553)),+ testShowFormat "utcTimeFormat"+ iso8601Format+ "2028-12-31T23:59:60.9Z"+ (UTCTime (fromGregorian 2028 12 31) (timeOfDayToTime $ TimeOfDay 23 59 60.9)),+ testShowFormat "weekDateFormat"+ (weekDateFormat ExtendedFormat)+ "1994-W52-7"+ (fromGregorian 1995 1 1),+ testShowFormat "weekDateFormat"+ (weekDateFormat ExtendedFormat)+ "1995-W01-1"+ (fromGregorian 1995 1 2),+ testShowFormat "weekDateFormat"+ (weekDateFormat ExtendedFormat)+ "1996-W52-7"+ (fromGregorian 1996 12 29),+ testShowFormat "weekDateFormat"+ (weekDateFormat ExtendedFormat)+ "1997-W01-2"+ (fromGregorian 1996 12 31),+ testShowFormat "weekDateFormat"+ (weekDateFormat ExtendedFormat)+ "1997-W01-3"+ (fromGregorian 1997 1 1),+ testShowFormat "weekDateFormat"+ (weekDateFormat ExtendedFormat)+ "1974-W32-6"+ (fromGregorian 1974 8 10),+ testShowFormat "weekDateFormat"+ (weekDateFormat BasicFormat)+ "1974W326"+ (fromGregorian 1974 8 10),+ testShowFormat "weekDateFormat"+ (weekDateFormat ExtendedFormat)+ "1995-W05-6"+ (fromGregorian 1995 2 4),+ testShowFormat "weekDateFormat"+ (weekDateFormat BasicFormat)+ "1995W056"+ (fromGregorian 1995 2 4),+ testShowFormat "weekDateFormat"+ (expandedWeekDateFormat 6 ExtendedFormat)+ "+001995-W05-6"+ (fromGregorian 1995 2 4),+ testShowFormat "weekDateFormat"+ (expandedWeekDateFormat 6 BasicFormat)+ "+001995W056"+ (fromGregorian 1995 2 4),+ testShowFormat "ordinalDateFormat"+ (ordinalDateFormat ExtendedFormat)+ "1846-235"+ (fromGregorian 1846 8 23),+ testShowFormat "ordinalDateFormat"+ (ordinalDateFormat BasicFormat)+ "1844236"+ (fromGregorian 1844 8 23),+ testShowFormat "ordinalDateFormat"+ (expandedOrdinalDateFormat 5 ExtendedFormat)+ "+01846-235"+ (fromGregorian 1846 8 23),+ testShowFormat "hourMinuteFormat"+ (hourMinuteFormat ExtendedFormat)+ "13:17.25"+ (TimeOfDay 13 17 15),+ testShowFormat "hourMinuteFormat"+ (hourMinuteFormat ExtendedFormat)+ "01:12.4"+ (TimeOfDay 1 12 24),+ testShowFormat "hourMinuteFormat"+ (hourMinuteFormat BasicFormat)+ "1317.25"+ (TimeOfDay 13 17 15),+ testShowFormat "hourMinuteFormat"+ (hourMinuteFormat BasicFormat)+ "0112.4"+ (TimeOfDay 1 12 24),+ testShowFormat "hourFormat"+ hourFormat+ "22"+ (TimeOfDay 22 0 0),+ testShowFormat "hourFormat"+ hourFormat+ "06"+ (TimeOfDay 6 0 0),+ testShowFormat "hourFormat"+ hourFormat+ "18.9475"+ (TimeOfDay 18 56 51)+ ]++testISO8601 :: TestTree+testISO8601 = nameTest "ISO8601"+ [+ testShowFormats,+ testReadShowFormat+ ]
test/main/Test/Format/ParseTime.hs view
@@ -3,16 +3,16 @@ import Control.Monad import Data.Char-import Data.Ratio+import Text.Read import Data.Time import Data.Time.Calendar.OrdinalDate import Data.Time.Calendar.WeekDate-import Data.Time.Clock.POSIX import Test.QuickCheck.Property import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck hiding (reason) import Test.TestUtil+import Test.Arbitrary() testParseTime :: TestTree@@ -233,61 +233,6 @@ format :: (FormatTime t) => String -> t -> String format f t = formatTime defaultTimeLocale f t -instance Arbitrary Day where- arbitrary = liftM ModifiedJulianDay $ choose (-313698, 2973483) -- 1000-01-1 to 9999-12-31--instance CoArbitrary Day where- coarbitrary (ModifiedJulianDay d) = coarbitrary d--instance Arbitrary DiffTime where- arbitrary = oneof [intSecs, fracSecs] -- up to 1 leap second- where intSecs = liftM secondsToDiffTime' $ choose (0, 86400)- fracSecs = liftM picosecondsToDiffTime' $ choose (0, 86400 * 10^(12::Int))- secondsToDiffTime' :: Integer -> DiffTime- secondsToDiffTime' = fromInteger- picosecondsToDiffTime' :: Integer -> DiffTime- picosecondsToDiffTime' x = fromRational (x % 10^(12::Int))--instance CoArbitrary DiffTime where- coarbitrary t = coarbitrary (fromEnum t)--instance Arbitrary TimeOfDay where- arbitrary = liftM timeToTimeOfDay arbitrary--instance CoArbitrary TimeOfDay where- coarbitrary t = coarbitrary (timeOfDayToTime t)--instance Arbitrary LocalTime where- arbitrary = liftM2 LocalTime arbitrary arbitrary--instance CoArbitrary LocalTime where- coarbitrary t = coarbitrary (floor (utcTimeToPOSIXSeconds (localTimeToUTC utc t)) :: Integer)--instance Arbitrary TimeZone where- arbitrary = liftM minutesToTimeZone $ choose (-720,720)--instance CoArbitrary TimeZone where- coarbitrary tz = coarbitrary (timeZoneMinutes tz)--instance Arbitrary ZonedTime where- arbitrary = liftM2 ZonedTime arbitrary arbitrary--instance CoArbitrary ZonedTime where- coarbitrary t = coarbitrary (floor (utcTimeToPOSIXSeconds (zonedTimeToUTC t)) :: Integer)--instance Arbitrary UTCTime where- arbitrary = liftM2 UTCTime arbitrary arbitrary--instance CoArbitrary UTCTime where- coarbitrary t = coarbitrary (floor (utcTimeToPOSIXSeconds t) :: Integer)--instance Arbitrary UniversalTime where- arbitrary = liftM (\n -> ModJulianDate $ n % k) $ choose (-313698 * k, 2973483 * k) where -- 1000-01-1 to 9999-12-31- k = 86400--instance CoArbitrary UniversalTime where- coarbitrary (ModJulianDate d) = coarbitrary d- -- missing from the time package instance Eq ZonedTime where ZonedTime t1 tz1 == ZonedTime t2 tz2 = t1 == t2 && tz1 == tz2@@ -315,7 +260,7 @@ -- prop_read_show :: (Read a, Show a, Eq a) => a -> Result-prop_read_show t = compareResult [(t,"")] (reads (show t))+prop_read_show t = compareResult (Just t) (readMaybe (show t)) -- -- * special show functions@@ -361,7 +306,7 @@ prop_parse_format_lower :: (Eq t, FormatTime t, ParseTime t, Show t) => FormatString t -> t -> Result prop_parse_format_lower (FormatString f) t = compareParse t f (map toLower $ format f t) -prop_format_parse_format :: (FormatTime t, ParseTime t, Show t) => FormatString t -> t -> Result+prop_format_parse_format :: (FormatTime t, ParseTime t) => FormatString t -> t -> Result prop_format_parse_format (FormatString f) t = compareResult (Just (format f t)) (fmap (format f) (parse False f (format f t) `asTypeOf` Just t))@@ -400,7 +345,6 @@ instance Show (FormatString a) where show (FormatString f) = show f - typedTests :: (forall t. (Eq t, FormatTime t, ParseTime t, Show t) => FormatString t -> t -> Result) -> [TestTree] typedTests prop = [ nameTest "Day" $ tgroup dayFormats prop,@@ -408,8 +352,13 @@ nameTest "LocalTime" $ tgroup localTimeFormats prop, nameTest "TimeZone" $ tgroup timeZoneFormats prop, nameTest "ZonedTime" $ tgroup zonedTimeFormats prop,- nameTest "UTCTime" $ tgroup utcTimeFormats prop,- nameTest "UniversalTime" $ tgroup universalTimeFormats prop+ nameTest "ZonedTime" $ tgroup zonedTimeAlmostFormats $ \fmt t -> (todSec $ localTimeOfDay $ zonedTimeToLocalTime t) < 60 ==> prop fmt t,+ nameTest "UTCTime" $ tgroup utcTimeAlmostFormats $ \fmt t -> utctDayTime t < 86400 ==> prop fmt t,+ nameTest "UniversalTime" $ tgroup universalTimeFormats prop,+ nameTest "CalendarDiffDays" $ tgroup calendarDiffDaysFormats prop,+ nameTest "CalenderDiffTime" $ tgroup calendarDiffTimeFormats prop,+ nameTest "DiffTime" $ tgroup diffTimeFormats prop,+ nameTest "NominalDiffTime" $ tgroup nominalDiffTimeFormats prop ] formatParseFormatTests :: TestTree@@ -428,8 +377,8 @@ nameTest "TimeOfDay" $ tgroup (timeOfDayFormats ++ partialTimeOfDayFormats) prop_no_crash_bad_input, nameTest "LocalTime" $ tgroup (localTimeFormats ++ partialLocalTimeFormats) prop_no_crash_bad_input, nameTest "TimeZone" $ tgroup (timeZoneFormats) prop_no_crash_bad_input,- nameTest "ZonedTime" $ tgroup (zonedTimeFormats ++ partialZonedTimeFormats) prop_no_crash_bad_input,- nameTest "UTCTime" $ tgroup (utcTimeFormats ++ partialUTCTimeFormats) prop_no_crash_bad_input,+ nameTest "ZonedTime" $ tgroup (zonedTimeFormats ++ zonedTimeAlmostFormats ++ partialZonedTimeFormats) prop_no_crash_bad_input,+ nameTest "UTCTime" $ tgroup (utcTimeAlmostFormats ++ partialUTCTimeFormats) prop_no_crash_bad_input, nameTest "UniversalTime" $ tgroup (universalTimeFormats ++ partialUniversalTimeFormats) prop_no_crash_bad_input ] @@ -442,6 +391,8 @@ nameTest "ZonedTime" (prop_read_show :: ZonedTime -> Result), nameTest "UTCTime" (prop_read_show :: UTCTime -> Result), nameTest "UniversalTime" (prop_read_show :: UniversalTime -> Result)+ --nameTest "CalendarDiffDays" (prop_read_show :: CalendarDiffDays -> Result),+ --nameTest "CalendarDiffTime" (prop_read_show :: CalendarDiffTime -> Result) ] parseShowTests :: TestTree@@ -501,15 +452,29 @@ zonedTimeFormats :: [FormatString ZonedTime] zonedTimeFormats = map FormatString- ["%a, %d %b %Y %H:%M:%S.%q %z", "%a, %d %b %Y %H:%M:%S%Q %z", "%s.%q %z", "%s%Q %z",- "%a, %d %b %Y %H:%M:%S.%q %Z", "%a, %d %b %Y %H:%M:%S%Q %Z", "%s.%q %Z", "%s%Q %Z"]+ ["%a, %d %b %Y %H:%M:%S.%q %z", "%a, %d %b %Y %H:%M:%S%Q %z",+ "%a, %d %b %Y %H:%M:%S.%q %Z", "%a, %d %b %Y %H:%M:%S%Q %Z"] -utcTimeFormats :: [FormatString UTCTime]-utcTimeFormats = map FormatString- ["%s.%q","%s%Q"]+zonedTimeAlmostFormats :: [FormatString ZonedTime]+zonedTimeAlmostFormats = map FormatString ["%s.%q %z", "%s%Q %z", "%s.%q %Z", "%s%Q %Z"] +utcTimeAlmostFormats :: [FormatString UTCTime]+utcTimeAlmostFormats = map FormatString ["%s.%q","%s%Q"]+ universalTimeFormats :: [FormatString UniversalTime] universalTimeFormats = map FormatString []++calendarDiffDaysFormats :: [FormatString CalendarDiffDays]+calendarDiffDaysFormats = map FormatString ["%yy%Bm%ww%Dd","%yy%Bm%dd","%bm%ww%Dd","%bm%dd"]++calendarDiffTimeFormats :: [FormatString CalendarDiffTime]+calendarDiffTimeFormats = map FormatString ["%yy%Bm%ww%Dd%Hh%Mm%ESs","%bm%ww%Dd%Hh%Mm%ESs","%bm%dd%Hh%Mm%ESs","%bm%hh%Mm%ESs","%bm%mm%ESs","%bm%mm%0ESs","%bm%Ess","%bm%0Ess"]++diffTimeFormats :: [FormatString DiffTime]+diffTimeFormats = map FormatString ["%ww%Dd%Hh%Mm%ESs","%dd%Hh%Mm%ESs","%hh%Mm%ESs","%mm%ESs","%mm%0ESs","%Ess","%0Ess"]++nominalDiffTimeFormats :: [FormatString NominalDiffTime]+nominalDiffTimeFormats = map FormatString ["%ww%Dd%Hh%Mm%ESs","%dd%Hh%Mm%ESs","%hh%Mm%ESs","%mm%ESs","%mm%0ESs","%Ess","%0Ess"] -- -- * Formats that do not include all the information
+ test/main/Test/LocalTime/CalendarDiffTime.hs view
@@ -0,0 +1,18 @@+module Test.LocalTime.CalendarDiffTime+ ( testCalendarDiffTime+ ) where++--import Data.Time.LocalTime+import Test.Arbitrary ()+import Test.Tasty+--import Test.Tasty.QuickCheck hiding (reason)++--testReadShow :: TestTree+--testReadShow = testProperty "read . show" $ \(t :: CalendarDiffTime) -> read (show t) == t++testCalendarDiffTime :: TestTree+testCalendarDiffTime =+ testGroup+ "CalendarDiffTime"+ [ --testReadShow+ ]
+ test/main/Test/LocalTime/TimeOfDay.hs view
@@ -0,0 +1,22 @@+module Test.LocalTime.TimeOfDay+ ( testTimeOfDay+ ) where++import Data.Time.LocalTime+import Test.Arbitrary ()+import Test.Tasty+import Test.Tasty.QuickCheck hiding (reason)++testTimeOfDay :: TestTree+testTimeOfDay =+ testGroup+ "TimeOfDay"+ [ testProperty "daysAndTimeOfDayToTime . timeToDaysAndTimeOfDay" $ \ndt -> let+ (d, tod) = timeToDaysAndTimeOfDay ndt+ ndt' = daysAndTimeOfDayToTime d tod+ in ndt' == ndt+ , testProperty "timeOfDayToTime . timeToTimeOfDay" $ \dt -> let+ tod = timeToTimeOfDay dt+ dt' = timeOfDayToTime tod+ in dt' == dt+ ]
time.cabal view
@@ -1,5 +1,5 @@ name: time-version: 1.8.0.4+version: 1.9 stability: stable license: BSD3 license-file: LICENSE@@ -67,13 +67,17 @@ Data.Time.Clock.TAI, Data.Time.LocalTime, Data.Time.Format,+ Data.Time.Format.ISO8601, Data.Time default-extensions: CPP c-sources: lib/cbits/HsTime.c other-modules:+ Data.Format Data.Time.Calendar.Private, Data.Time.Calendar.Days, Data.Time.Calendar.Gregorian,+ Data.Time.Calendar.CalendarDiffDays,+ Data.Time.Calendar.Week, Data.Time.Calendar.JulianYearDay, Data.Time.Clock.Internal.DiffTime, Data.Time.Clock.Internal.AbsoluteTime,@@ -87,10 +91,15 @@ Data.Time.Clock.Internal.UTCDiff, Data.Time.LocalTime.Internal.TimeZone, Data.Time.LocalTime.Internal.TimeOfDay,+ Data.Time.LocalTime.Internal.CalendarDiffTime Data.Time.LocalTime.Internal.LocalTime, Data.Time.LocalTime.Internal.ZonedTime,- Data.Time.Format.Parse- Data.Time.Format.Locale+ Data.Time.Format.Parse,+ Data.Time.Format.Locale,+ Data.Time.Format.Format.Class,+ Data.Time.Format.Format.Instances,+ Data.Time.Format.Parse.Class,+ Data.Time.Format.Parse.Instances include-dirs: lib/include if os(windows) install-includes:@@ -137,6 +146,7 @@ main-is: Main.hs other-modules: Test.TestUtil+ Test.Arbitrary Test.Calendar.AddDays Test.Calendar.AddDaysRef Test.Calendar.Calendars@@ -144,6 +154,7 @@ Test.Calendar.ClipDates Test.Calendar.ClipDatesRef Test.Calendar.ConvertBack+ Test.Calendar.Duration Test.Calendar.Easter Test.Calendar.EasterRef Test.Calendar.LongWeekYears@@ -151,12 +162,16 @@ Test.Calendar.MonthDay Test.Calendar.MonthDayRef Test.Calendar.Valid+ Test.Calendar.Week Test.Clock.Conversion Test.Clock.Resolution Test.Clock.TAI Test.Format.Format Test.Format.ParseTime+ Test.Format.ISO8601+ Test.LocalTime.CalendarDiffTime Test.LocalTime.Time+ Test.LocalTime.TimeOfDay Test.LocalTime.TimeRef test-suite test-unix