chronos 1.1 → 1.1.1
raw patch · 5 files changed
+698/−26 lines, 5 filesdep ~aesonPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: aeson
API changes (from Hackage documentation)
+ Chronos: datetimeToDayOfWeek :: Datetime -> DayOfWeek
+ Chronos: decode_DmyHMS_lenient :: Text -> Maybe Datetime
+ Chronos: decode_DmyHMS_opt_S_lenient :: Text -> Maybe Datetime
+ Chronos: decode_MdyHMS :: DatetimeFormat -> Text -> Maybe Datetime
+ Chronos: decode_MdyHMS_lenient :: Text -> Maybe Datetime
+ Chronos: decode_MdyHMS_opt_S :: DatetimeFormat -> Text -> Maybe Datetime
+ Chronos: decode_MdyHMS_opt_S_lenient :: Text -> Maybe Datetime
+ Chronos: decode_YmdHMS_lenient :: Text -> Maybe Datetime
+ Chronos: decode_YmdHMS_opt_S_lenient :: Text -> Maybe Datetime
+ Chronos: decode_lenient :: Text -> Maybe Datetime
+ Chronos: parser_DmyHMS_lenient :: Parser Datetime
+ Chronos: parser_DmyHMS_opt_S_lenient :: Parser Datetime
+ Chronos: parser_Dmy_lenient :: Parser Date
+ Chronos: parser_MdyHMS :: DatetimeFormat -> Parser Datetime
+ Chronos: parser_MdyHMS_lenient :: Parser Datetime
+ Chronos: parser_MdyHMS_opt_S :: DatetimeFormat -> Parser Datetime
+ Chronos: parser_MdyHMS_opt_S_lenient :: Parser Datetime
+ Chronos: parser_Mdy_lenient :: Parser Date
+ Chronos: parser_YmdHMS_lenient :: Parser Datetime
+ Chronos: parser_YmdHMS_opt_S_lenient :: Parser Datetime
+ Chronos: parser_Ymd_lenient :: Parser Date
+ Chronos: parser_lenient :: Parser Datetime
+ Chronos: timeToDayOfWeek :: Time -> DayOfWeek
+ Chronos: todayDayOfWeek :: IO DayOfWeek
+ Chronos: tomorrowDayOfWeek :: IO DayOfWeek
+ Chronos: yesterdayDayOfWeek :: IO DayOfWeek
Files
- bench/Bench.hs +18/−4
- chronos.cabal +1/−1
- src/Chronos.hs +377/−4
- src/Chronos/Internal/CTimespec.hs +3/−1
- test/Spec.hs +299/−16
bench/Bench.hs view
@@ -46,17 +46,22 @@ Thyme.buildTime @Thyme.UTCTime . either error id . parseOnly (Thyme.timeParser Thyme.defaultTimeLocale isoFormatString)- chronosAttoparsec :: BS8.ByteString -> Chronos.Datetime+ -- With chronos, we explicitly convert the ymdhms time to the+ -- nanoseconds since the epoch. This is done to make the benchmark+ -- compare apples to apples. Both thyme and time are computing epoch+ -- times, so we want chronos to do the same.+ chronosAttoparsec :: BS8.ByteString -> Chronos.Time chronosAttoparsec =- either error id+ either error Chronos.datetimeToTime . parseOnly (Chronos.parserUtf8_YmdHMS Chronos.w3c)- chronosZepto :: BS8.ByteString -> Chronos.Datetime+ chronosZepto :: BS8.ByteString -> Chronos.Time chronosZepto =- either error id+ either error Chronos.datetimeToTime . Z.parse (Chronos.zeptoUtf8_YmdHMS Chronos.w3c) dmy = "%d:%m:%y." hms = "%%H:%M:%S."+ dmyhms = isoFormatString timePretty = Time.formatTime Time.defaultTimeLocale thymePretty = Thyme.formatTime Thyme.defaultTimeLocale chronosPrettyDmy = toLazyText@@ -67,6 +72,9 @@ . Chronos.builder_HMS Chronos.SubsecondPrecisionAuto (Just ':') . Chronos.datetimeTime . Chronos.timeToDatetime+ chronosPrettyYmdHMS = toLazyText+ . Chronos.builder_YmdHMS Chronos.SubsecondPrecisionAuto Chronos.w3c+ . Chronos.timeToDatetime timeTime <- Time.getCurrentTime thymeTime <- Thyme.getCurrentTime@@ -95,6 +103,11 @@ , bench "Thyme.formatTime" $ nf (thymePretty hms) thymeTime , bench "Chronos.builder_HMS" $ nf chronosPrettyHMS chronosTime ]+ , bgroup "YmdHMS"+ [ bench "Time.formatTime" $ nf (timePretty dmyhms) timeTime+ , bench "Thyme.formatTime" $ nf (thymePretty dmyhms) thymeTime+ , bench "Chronos.builder_YmdHMS" $ nf chronosPrettyYmdHMS chronosTime+ ] ] ] @@ -111,3 +124,4 @@ deriving instance NFData Chronos.Month deriving instance NFData Chronos.Year deriving instance NFData Chronos.Day+deriving instance NFData Chronos.Time
chronos.cabal view
@@ -2,7 +2,7 @@ name: chronos version:- 1.1+ 1.1.1 synopsis: A performant time library description:
src/Chronos.hs view
@@ -7,6 +7,7 @@ , OverloadedStrings , RecordWildCards , ScopedTypeVariables+ , TypeApplications , TypeFamilies , TypeInType , UnboxedTuples@@ -29,7 +30,7 @@ * Chronos uses normal non-overloaded haskell functions for encoding and decoding time. It provides <http://hackage.haskell.org/package/attoparsec attoparsec> parsers for both 'Text' and 'ByteString'. Additionally, Chronos provides functions for- encoding time to 'Text' or 'ByteString'. The http://hackage.haskell.org/package/time time> library accomplishes these with the+ encoding time to 'Text' or 'ByteString'. The <http://hackage.haskell.org/package/time time> library accomplishes these with the <http://hackage.haskell.org/package/time-1.9.3/docs/Data-Time-Format.html Data.Time.Format> module, which uses UNIX-style datetime format strings. The approach taken by Chronos is faster and catches more mistakes at compile time, at the cost of being@@ -43,6 +44,10 @@ , today , tomorrow , yesterday+ , todayDayOfWeek+ , yesterdayDayOfWeek+ , tomorrowDayOfWeek+ , timeToDayOfWeek , epoch -- ** Duration , stopwatch@@ -57,6 +62,7 @@ -- ** Conversion , timeToDatetime , datetimeToTime+ , datetimeToDayOfWeek , timeToOffsetDatetime , offsetDatetimeToTime , timeToDayTruncate@@ -119,8 +125,11 @@ , builder_Dmy , builder_HMS , parser_Ymd+ , parser_Ymd_lenient , parser_Mdy+ , parser_Mdy_lenient , parser_Dmy+ , parser_Dmy_lenient -- *** UTF-8 ByteString , builderUtf8_Ymd , parserUtf8_Ymd@@ -151,13 +160,31 @@ , encode_YmdHMS , encode_YmdIMS_p , parser_DmyHMS+ , parser_DmyHMS_lenient , parser_YmdHMS+ , parser_YmdHMS_lenient , parser_YmdHMS_opt_S+ , parser_YmdHMS_opt_S_lenient , parser_DmyHMS_opt_S+ , parser_DmyHMS_opt_S_lenient+ , parser_MdyHMS+ , parser_MdyHMS_lenient+ , parser_MdyHMS_opt_S+ , parser_MdyHMS_opt_S_lenient+ , parser_lenient , decode_DmyHMS+ , decode_DmyHMS_lenient+ , decode_MdyHMS+ , decode_MdyHMS_lenient+ , decode_MdyHMS_opt_S+ , decode_MdyHMS_opt_S_lenient , decode_YmdHMS+ , decode_YmdHMS_lenient , decode_YmdHMS_opt_S+ , decode_YmdHMS_opt_S_lenient , decode_DmyHMS_opt_S+ , decode_DmyHMS_opt_S_lenient+ , decode_lenient -- *** UTF-8 ByteString , encodeUtf8_YmdHMS , encodeUtf8_YmdIMS_p@@ -377,6 +404,16 @@ datetimeToTime :: Datetime -> Time datetimeToTime = fromUtc . datetimeToUtcTime +-- | Convert 'Datetime' to 'DayOfWeek'+datetimeToDayOfWeek :: Datetime -> DayOfWeek+datetimeToDayOfWeek (Datetime (Date year month date) _) =+ let k = getDayOfMonth date+ m = ((getMonth month + 10) `mod` 12) + 1+ y = adjustedYear `mod` 100+ c = adjustedYear `div` 100+ adjustedYear = if m >= 11 then getYear year - 1 else getYear year+ in DayOfWeek $ (k + (floor $ ((2.6 :: Double) * fromIntegral m) - 0.2) - (2*c) + y + (y `div` 4) + (c `div` 4)) `mod` 7+ -- | Convert 'Time' to 'OffsetDatetime' by providing an 'Offset'. timeToOffsetDatetime :: Offset -> Time -> OffsetDatetime timeToOffsetDatetime offset = utcTimeToOffsetDatetime offset . toUtc@@ -489,6 +526,25 @@ now = fmap Time getPosixNanoseconds #endif +-- | Convert from 'Time' to 'DayOfWeek'.+timeToDayOfWeek :: Time -> DayOfWeek+timeToDayOfWeek (Time time) = DayOfWeek $+ (fromIntegral @Int64 @Int ((time `div` 86400000000000) + 4) `mod` 7)++-- | Get the current 'DayOfWeek' from the system clock.+todayDayOfWeek :: IO DayOfWeek+todayDayOfWeek = timeToDayOfWeek <$> now++-- | Get the yesterday\'s 'DayOfWeek' from the system clock.+yesterdayDayOfWeek :: IO DayOfWeek+yesterdayDayOfWeek =+ timeToDayOfWeek . (add (Timespan (-86400000000000))) <$> now++-- | Get the tomorrow\'s 'DayOfWeek' from the system clock.+tomorrowDayOfWeek :: IO DayOfWeek+tomorrowDayOfWeek =+ timeToDayOfWeek . (add (Timespan (86400000000000))) <$> now+ -- | The Unix epoch, that is 1970-01-01 00:00:00. epoch :: Time epoch = Time 0@@ -937,6 +993,22 @@ when (d < 1 || d > 31) (fail "day must be between 1 and 31") pure (Date (Year y) (Month $ m - 1) (DayOfMonth d)) +-- | Parse a Year\/Month\/Day-encoded 'Date' that either has no separators or+-- uses any non-numeric character for each separator.+parser_Ymd_lenient :: Parser Date+parser_Ymd_lenient = do+ y <- parseFixedDigits 4+ sep1 <- optional parserLenientSeparator+ m <- parseFixedDigits 2+ when (m < 1 || m > 12) (fail "month must be between 1 and 12")+ sep2 <- optional parserLenientSeparator+ d <- parseFixedDigits 2+ when (d < 1 || d > 31) (fail "day must be between 1 and 31")+ case (sep1, sep2) of+ (Nothing, Just _) -> fail "Separators must all exist or not"+ (Just _, Nothing) -> fail "Separators must all exist or not"+ _ -> pure (Date (Year y) (Month $ m - 1) (DayOfMonth d))+ -- | Parse a Month\/Day\/Year-encoded 'Date' that uses the -- given separator. parser_Mdy :: Maybe Char -> Parser Date@@ -950,6 +1022,22 @@ y <- parseFixedDigits 4 pure (Date (Year y) (Month $ m - 1) (DayOfMonth d)) +-- | Parse a Month\/Day\/Year-encoded 'Date' that either has no separators or+-- uses any non-numeric character for each separator.+parser_Mdy_lenient :: Parser Date+parser_Mdy_lenient = do+ m <- parseFixedDigits 2+ when (m < 1 || m > 12) (fail "month must be between 1 and 12")+ sep1 <- optional parserLenientSeparator+ d <- parseFixedDigits 2+ when (d < 1 || d > 31) (fail "day must be between 1 and 31")+ sep2 <- optional parserLenientSeparator+ y <- parseFixedDigits 4+ case (sep1, sep2) of+ (Nothing, Just _) -> fail "Separators must all exist or not"+ (Just _, Nothing) -> fail "Separators must all exist or not"+ _ -> pure (Date (Year y) (Month $ m - 1) (DayOfMonth d))+ -- | Parse a Day\/Month\/Year-encoded 'Date' that uses the -- given separator. parser_Dmy :: Maybe Char -> Parser Date@@ -963,6 +1051,22 @@ y <- parseFixedDigits 4 pure (Date (Year y) (Month $ m - 1) (DayOfMonth d)) +-- | Parse a Day\/Month\/Year-encoded 'Date' that either has no separators or+-- uses any non-numeric character for each separator.+parser_Dmy_lenient :: Parser Date+parser_Dmy_lenient = do+ d <- parseFixedDigits 2+ when (d < 1 || d > 31) (fail "day must be between 1 and 31")+ sep1 <- optional parserLenientSeparator+ m <- parseFixedDigits 2+ when (m < 1 || m > 12) (fail "month must be between 1 and 12")+ sep2 <- optional parserLenientSeparator+ y <- parseFixedDigits 4+ case (sep1, sep2) of+ (Nothing, Just _) -> fail "Separators must all exist or not"+ (Just _, Nothing) -> fail "Separators must all exist or not"+ _ -> pure (Date (Year y) (Month $ m - 1) (DayOfMonth d))+ -- | Given a 'Date' and a separator, construct a 'ByteString' 'BB.Builder' -- corresponding to a Day\/Month\/Year encoding. builderUtf8_Ymd :: Maybe Char -> Date -> BB.Builder@@ -1045,6 +1149,22 @@ ns <- parseSecondsAndNanoseconds pure (TimeOfDay h m ns) +parserLenientSeparator :: Parser ()+parserLenientSeparator = AT.satisfy (not . isDigit) *> pure ()++-- | Parse an Hour\/Minute\/Second-encoded 'TimeOfDay' that either has no+-- separators or uses any given non-numeric character for each separator.+parser_HMS_lenient :: Parser TimeOfDay+parser_HMS_lenient = do+ h <- parseFixedDigits 2+ when (h > 23) (fail "hour must be between 0 and 23")+ parserLenientSeparator+ m <- parseFixedDigits 2+ when (m > 59) (fail "minute must be between 0 and 59")+ parserLenientSeparator+ ns <- parseSecondsAndNanoseconds+ pure (TimeOfDay h m ns)+ -- | Parses text that is formatted as either of the following: -- -- * @%H:%M@@@ -1080,6 +1200,33 @@ pure (TimeOfDay h m ns) else pure (TimeOfDay h m 0) +-- | Parses text that is formatted as either of the following with either no+-- separators or any non-numeric characters for each separator:+--+-- * @%H:%M@+-- * @%H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is+-- not provided, it is assumed to be zero. This format shows up+-- in Google Chrome\'s @datetime-local@ inputs.+parser_HMS_opt_S_lenient :: Parser TimeOfDay+parser_HMS_opt_S_lenient = do+ h <- parseFixedDigits 2+ when (h > 23) (fail "hour must be between 0 and 23")+ parserLenientSeparator+ m <- parseFixedDigits 2+ when (m > 59) (fail "minute must be between 0 and 59")+ mc <- AT.peekChar+ case mc of+ Nothing -> pure (TimeOfDay h m 0)+ Just c | not (isDigit c) -> do+ _ <- AT.anyChar -- should be the separator+ ns <- parseSecondsAndNanoseconds+ pure (TimeOfDay h m ns)+ Just _ -> do+ ns <- parseSecondsAndNanoseconds+ pure (TimeOfDay h m ns)+ parseSecondsAndNanoseconds :: Parser Int64 parseSecondsAndNanoseconds = do s' <- parseFixedDigits 2@@ -1289,6 +1436,13 @@ decode_YmdHMS format = either (const Nothing) Just . AT.parseOnly (parser_YmdHMS format) +-- | Decode a Year\/Month\/Day,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with either no separators or any non-numeric+-- character for each separator.+decode_YmdHMS_lenient :: Text -> Maybe Datetime+decode_YmdHMS_lenient =+ either (const Nothing) Just . AT.parseOnly parser_YmdHMS_lenient+ -- | Parse a Day\/Month\/Year,Hour\/Minute\/Second-encoded 'Datetime' -- that was encoded with the given 'DatetimeFormat'. parser_DmyHMS :: DatetimeFormat -> Parser Datetime@@ -1313,13 +1467,58 @@ time <- parser_HMS_opt_S mtimeSep pure (Datetime date time) +-- | Parse a Day\/Month\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with either no separators or any non-numeric+-- character for each separator, such as:+--+-- 01-05-2017T23:13:05+-- 01-05-2017 23:13:05+-- 01/05/2017 23:13:05+-- 01y01/2018x23;50&29+parser_DmyHMS_lenient :: Parser Datetime+parser_DmyHMS_lenient = do+ mdate <- optional $ parser_Dmy Nothing+ case mdate of+ Just date -> Datetime date <$> parser_HMS Nothing+ Nothing -> Datetime <$> parser_Dmy_lenient <* parserLenientSeparator <*> parser_HMS_lenient++-- | Parse a Day\/Month\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with either no separators or any non-numeric+-- character for each separator and with either of the following time formats:+--+-- * @%H:%M@+-- * @%H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is+-- not provided, it is assumed to be zero. This format shows up+-- in Google Chrome\'s @datetime-local@ inputs.+parser_DmyHMS_opt_S_lenient :: Parser Datetime+parser_DmyHMS_opt_S_lenient = do+ mdate <- optional $ parser_Dmy Nothing+ case mdate of+ Just date -> Datetime date <$> parser_HMS_opt_S Nothing+ Nothing -> Datetime <$> parser_Dmy_lenient <* parserLenientSeparator <*> parser_HMS_opt_S_lenient++-- | Decodes Day\/Month\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that is encoded with either no separators or any non-numeric+-- characters as separators, such as:+--+-- 2017-01-05T23:13:05+-- 2017-01-05 23:13:05+-- 2017/01/05 23:13:05+-- 2018x01y01/23;50&29+decode_DmyHMS_lenient :: Text -> Maybe Datetime+decode_DmyHMS_lenient = either (const Nothing) Just . AT.parseOnly parser_DmyHMS_lenient+ -- | Decode a Day\/Month\/Year,Hour\/Minute\/Second-encoded 'Datetime' -- from 'Text' that was encoded with the given 'DatetimeFormat'. decode_DmyHMS :: DatetimeFormat -> Text -> Maybe Datetime decode_DmyHMS format = either (const Nothing) Just . AT.parseOnly (parser_DmyHMS format) --- | Parses text that is formatted as either of the following:+-- | Decode a Day\/Month\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with with the given 'DatetimeFormat' and with+-- either of the following time formats: -- -- * @%H:%M@ -- * @%H:%M:%S@@@ -1330,6 +1529,112 @@ decode_DmyHMS_opt_S :: DatetimeFormat -> Text -> Maybe Datetime decode_DmyHMS_opt_S format = either (const Nothing) Just . AT.parseOnly (parser_DmyHMS_opt_S format)++-- | Decode a Day\/Month\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with either no separators or any non-numeric+-- character for each separator and with either of the following time formats:+--+-- * @%H:%M@+-- * @%H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is+-- not provided, it is assumed to be zero. This format shows up+-- in Google Chrome\'s @datetime-local@ inputs.+decode_DmyHMS_opt_S_lenient :: Text -> Maybe Datetime+decode_DmyHMS_opt_S_lenient =+ either (const Nothing) Just . AT.parseOnly parser_DmyHMS_opt_S_lenient+++-- | Parses a Month\/Day\/Year,Hour\/Minute\/Second-encoded 'Datetime'+-- that was encoded using the given 'DatetimeFormat'.+parser_MdyHMS :: DatetimeFormat -> Parser Datetime+parser_MdyHMS (DatetimeFormat mdateSep msep mtimeSep) = do+ date <- parser_Mdy mdateSep+ traverse_ AT.char msep+ time <- parser_HMS mtimeSep+ pure (Datetime date time)++-- | Parses a Month\/Day\/Year,Hour\/Minute\/Second-encoded 'Datetime' that was+-- encoded with either no separators or any non-numeric character for each+-- separator.+parser_MdyHMS_lenient :: Parser Datetime+parser_MdyHMS_lenient = do+ mdate <- optional $ parser_Mdy Nothing+ case mdate of+ Just date -> Datetime date <$> parser_HMS Nothing+ Nothing -> Datetime <$> parser_Mdy_lenient <* parserLenientSeparator <*> parser_HMS_lenient++-- | Parse a Month\/Day\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with with the given 'DatetimeFormat' and with+-- either of the following time formats:+--+-- * @%H:%M@+-- * @%H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is+-- not provided, it is assumed to be zero.+parser_MdyHMS_opt_S :: DatetimeFormat -> Parser Datetime+parser_MdyHMS_opt_S (DatetimeFormat mdateSep msep mtimeSep) = do+ date <- parser_Mdy mdateSep+ traverse_ AT.char msep+ time <- parser_HMS_opt_S mtimeSep+ pure (Datetime date time)++-- | Parse a Month\/Day\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with either no separators or any non-numeric+-- character for each separator and with either of the following time formats:+--+-- * @%H:%M@+-- * @%H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is+-- not provided, it is assumed to be zero.+parser_MdyHMS_opt_S_lenient :: Parser Datetime+parser_MdyHMS_opt_S_lenient = do+ mdate <- optional $ parser_Mdy Nothing+ case mdate of+ Just date -> Datetime date <$> parser_HMS_opt_S Nothing+ Nothing -> Datetime <$> parser_Mdy_lenient <* parserLenientSeparator <*> parser_HMS_opt_S_lenient++-- | Decode a Month\/Day\/Year,Hour\/Minute\/Second-encoded 'Datetime'+-- from 'Text' that was encoded with the given 'DatetimeFormat'.+decode_MdyHMS :: DatetimeFormat -> Text -> Maybe Datetime+decode_MdyHMS format =+ either (const Nothing) Just . AT.parseOnly (parser_MdyHMS format)++-- | Decode a Month\/Day\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' with either no separators or any non-numeric character for each+-- separator.+decode_MdyHMS_lenient :: Text -> Maybe Datetime+decode_MdyHMS_lenient =+ either (const Nothing) Just . AT.parseOnly parser_MdyHMS_lenient++-- | Decode a Month\/Day\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with the given 'DatetimeFormat' and with either of+-- the following time formats:+--+-- * @%H:%M@+-- * @%H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is+-- not provided, it is assumed to be zero.+decode_MdyHMS_opt_S :: DatetimeFormat -> Text -> Maybe Datetime+decode_MdyHMS_opt_S format =+ either (const Nothing) Just . AT.parseOnly (parser_MdyHMS_opt_S format)++-- | Parse a Month\/Day\/Year,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' with either no separators or any non-numeric character for each+-- separator and with either of the following time formats:+--+-- * @%H:%M@+-- * @%H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is+-- not provided, it is assumed to be zero.+decode_MdyHMS_opt_S_lenient :: Text -> Maybe Datetime+decode_MdyHMS_opt_S_lenient =+ either (const Nothing) Just . AT.parseOnly parser_MdyHMS_opt_S_lenient+ -- | Parses a Year\/Month\/Day,Hour\/Minute\/Second-encoded 'Datetime' -- that was encoded using the given 'DatetimeFormat'. parser_YmdHMS :: DatetimeFormat -> Parser Datetime@@ -1339,7 +1644,19 @@ time <- parser_HMS mtimeSep pure (Datetime date time) --- | Parses text that is formatted as either of the following:+-- | Parses a Year\/Month\/Day,Hour\/Minute\/Second-encoded 'Datetime' that was+-- encoded with either no separators or any non-numeric character for each+-- separator.+parser_YmdHMS_lenient :: Parser Datetime+parser_YmdHMS_lenient = do+ mdate <- optional $ parser_Ymd Nothing+ case mdate of+ Just date -> Datetime date <$> parser_HMS Nothing+ Nothing -> Datetime <$> parser_Ymd_lenient <* parserLenientSeparator <*> parser_HMS_lenient++-- | Parses a Year\/Month\/Date,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with the given 'DatetimeFormat' and with either of+-- the following time formats: -- -- * @%H:%M@ -- * @%H:%M:%S@@@ -1354,7 +1671,9 @@ time <- parser_HMS_opt_S mtimeSep pure (Datetime date time) --- | Parses text that is formatted as either of the following:+-- | Parses a Year\/Month\/Date,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with either no separators or any non-numeric+-- character for each separator and with either of the following time formats: -- -- * @%H:%M@ -- * @%H:%M:%S@@@ -1362,9 +1681,63 @@ -- That is, the seconds and subseconds part is optional. If it is -- not provided, it is assumed to be zero. This format shows up -- in Google Chrome\'s @datetime-local@ inputs.+parser_YmdHMS_opt_S_lenient :: Parser Datetime+parser_YmdHMS_opt_S_lenient = do+ mdate <- optional $ parser_Ymd Nothing+ case mdate of+ Just date -> Datetime date <$> parser_HMS_opt_S Nothing+ Nothing -> Datetime <$> parser_Ymd_lenient <* parserLenientSeparator <*> parser_HMS_opt_S_lenient++-- | Decode a Year\/Month\/Date,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with the given 'DatetimeFormat' and with either of+-- the following time formats:+--+-- * @%H:%M@+-- * @%H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is+-- not provided, it is assumed to be zero. This format shows up+-- in Google Chrome\'s @datetime-local@ inputs. decode_YmdHMS_opt_S :: DatetimeFormat -> Text -> Maybe Datetime decode_YmdHMS_opt_S format = either (const Nothing) Just . AT.parseOnly (parser_YmdHMS_opt_S format)++-- | Decode a Year\/Month\/Date,Hour\/Minute\/Second-encoded 'Datetime' from+-- 'Text' that was encoded with either no separators or any non-numeric+-- character for each separator and with either of the following time formats:+--+-- * @%H:%M@+-- * @%H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is+-- not provided, it is assumed to be zero. This format shows up+-- in Google Chrome\'s @datetime-local@ inputs.+decode_YmdHMS_opt_S_lenient :: Text -> Maybe Datetime+decode_YmdHMS_opt_S_lenient =+ either (const Nothing) Just . AT.parseOnly parser_YmdHMS_opt_S_lenient++-- | Parses a 'Datetime' from 'Text' that was encoded with any of the following+-- formats and with either no separators or any non-numeric character for each+-- separator.+--+-- * @%Y-%M-%D %H:%M@+-- * @%Y-%M-%D %H:%M:%S@+-- * @%D-%M-%Y %H:%M@+-- * @%D-%M-%Y %H:%M:%S@+-- * @%M-%D-%Y %H:%M@+-- * @%M-%D-%Y %H:%M:%S@+--+-- That is, the seconds and subseconds part is optional. If it is not provided,+-- it is assumed to be zero. Note that this is the least performant parser due+-- to backtracking+parser_lenient :: Parser Datetime+parser_lenient = parser_YmdHMS_opt_S_lenient <|> parser_DmyHMS_opt_S_lenient <|> parser_MdyHMS_opt_S_lenient++-- | Parses text that was encoded in DMY, YMD, or MDY format with optional+-- seconds and any non-numeric character as separators.+decode_lenient :: Text -> Maybe Datetime+decode_lenient =+ either (const Nothing) Just . AT.parseOnly parser_lenient --------------- -- ByteString stuff ---------------
src/Chronos/Internal/CTimespec.hs view
@@ -57,6 +57,8 @@ throwErrnoIfMinus1_ "clock_gettime" $ clock_gettime 0 ptspec peek ptspec- return ((s * 1000000000) + ns)+ -- On most 64-bit platforms, the uses of fromIntegral here end up+ -- being i64->i64, but on 32-bit platforms, they are i32->i64.+ return ((fromIntegral s * 1000000000) + fromIntegral ns) #endif
test/Spec.hs view
@@ -122,14 +122,46 @@ ] ] , testGroup "Date"- [ testGroup "Parser Spec Tests" $- [ PH.testCase "No Separator"- (dateParse Nothing "20160101" (Date (Year 2016) (Month 0) (DayOfMonth 1)))- , PH.testCase "Separator 1"- (dateParse (Just '-') "2016-01-01" (Date (Year 2016) (Month 0) (DayOfMonth 1)))- , PH.testCase "Separator 2"- (dateParse (Just '-') "1876-09-27" (Date (Year 1876) (Month 8) (DayOfMonth 27)))+ [ testGroup "Ymd Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (dateParse (C.parser_Ymd Nothing) "20160101" (Date (Year 2016) (Month 0) (DayOfMonth 1)))+ , PH.testCase "Passes With Separator 1"+ (dateParse (C.parser_Ymd (Just '-')) "2016-01-01" (Date (Year 2016) (Month 0) (DayOfMonth 1)))+ , PH.testCase "Passes With Separator 2"+ (dateParse (C.parser_Ymd (Just '-')) "1876-09-27" (Date (Year 1876) (Month 8) (DayOfMonth 27))) ]+ , testGroup "Dmy Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (dateParse (C.parser_Dmy Nothing) "01012016" (Date (Year 2016) (Month 0) (DayOfMonth 1)))+ , PH.testCase "Passes With Separator 1"+ (dateParse (C.parser_Dmy (Just '-')) "01-01-2016" (Date (Year 2016) (Month 0) (DayOfMonth 1)))+ , PH.testCase "Passes With Separator 2"+ (dateParse (C.parser_Dmy (Just '-')) "27-09-1876" (Date (Year 1876) (Month 8) (DayOfMonth 27)))+ ]+ , testGroup "Ymd Lenient Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (dateParse C.parser_Ymd_lenient "20160101" (Date (Year 2016) (Month 0) (DayOfMonth 1)))+ , PH.testCase "Passes With Separator 1"+ (dateParse C.parser_Ymd_lenient "2016!01@01" (Date (Year 2016) (Month 0) (DayOfMonth 1)))+ , PH.testCase "Passes With Separator 2"+ (dateParse C.parser_Ymd_lenient "1876z09+27" (Date (Year 1876) (Month 8) (DayOfMonth 27)))+ ]+ , testGroup "Dmy Lenient Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (dateParse C.parser_Dmy_lenient "01012016" (Date (Year 2016) (Month 0) (DayOfMonth 1)))+ , PH.testCase "Passes With Separator 1"+ (dateParse C.parser_Dmy_lenient "01!01@2016" (Date (Year 2016) (Month 0) (DayOfMonth 1)))+ , PH.testCase "Passes With Separator 2"+ (dateParse C.parser_Dmy_lenient "27z09+1876" (Date (Year 1876) (Month 8) (DayOfMonth 27)))+ ]+ , testGroup "Mdy Lenient Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (dateParse C.parser_Mdy_lenient "01022016" (Date (Year 2016) (Month 0) (DayOfMonth 2)))+ , PH.testCase "Passes With Separator 1"+ (dateParse C.parser_Mdy_lenient "01!02@2016" (Date (Year 2016) (Month 0) (DayOfMonth 2)))+ , PH.testCase "Passes With Separator 2"+ (dateParse C.parser_Mdy_lenient "09+27z1876" (Date (Year 1876) (Month 8) (DayOfMonth 27)))+ ] , testGroup "Builder Spec Tests" $ [ PH.testCase "No Separator" (dateBuilder Nothing "20160101" (Date (Year 2016) (Month 0) (DayOfMonth 1)))@@ -143,12 +175,214 @@ (either (const Nothing) Just . Atto.parseOnly (C.parser_Ymd (Just '-'))) ] , testGroup "Datetime"- [ testProperty "Builder Parser Isomorphism (Y-m-dTH:M:S)" $ propEncodeDecodeIsoSettings- (\format -> LText.toStrict . Builder.toLazyText . C.builder_YmdHMS (SubsecondPrecisionFixed 9) format)- (\format -> either (const Nothing) Just . Atto.parseOnly (C.parser_YmdHMS format))- , testProperty "Builder Parser Isomorphism (YmdHMS)" $ propEncodeDecodeIso- (LText.toStrict . Builder.toLazyText . C.builder_YmdHMS (SubsecondPrecisionFixed 9) (DatetimeFormat Nothing Nothing Nothing))- (either (const Nothing) Just . Atto.parseOnly (C.parser_YmdHMS (DatetimeFormat Nothing Nothing Nothing)))+ [ testGroup "DmyHMS Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (datetimeParse (C.parser_DmyHMS (DatetimeFormat Nothing Nothing Nothing)) "01022016010223" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator"+ (datetimeParse (C.parser_DmyHMS (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-02-2016 01:02:23" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ ]+ , testGroup "YmdHMS Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (datetimeParse (C.parser_YmdHMS (DatetimeFormat Nothing Nothing Nothing)) "20160101010223" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator"+ (datetimeParse (C.parser_YmdHMS (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "2016-01-01 01:02:23" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ ]+ , testGroup "MdyHMS Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (datetimeParse (C.parser_MdyHMS (DatetimeFormat Nothing Nothing Nothing)) "01012016010223" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator"+ (datetimeParse (C.parser_MdyHMS (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-01-2016 01:02:23" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ ]+ , testGroup "DmyHMS Lenient Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (datetimeParse C.parser_DmyHMS_lenient "01022016010223" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator"+ (datetimeParse C.parser_DmyHMS_lenient "01z02x2016$01;02:23" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Fails with extra seperators"+ (datetimeParseFail C.parser_DmyHMS_lenient "01-02-2016 01:02:23" "Failed reading: input does not start with a digit")+ , PH.testCase "Fails with some nonuniform empty Separators"+ (datetimeParseFail C.parser_DmyHMS_lenient "01-02-201601:02:23" "Failed reading: satisfy")+ ]+ , testGroup "YmdHMS Lenient Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (datetimeParse C.parser_YmdHMS_lenient "20160101010223" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator"+ (datetimeParse C.parser_YmdHMS_lenient "2016!01z01^01a02c23" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Fails with extra seperators"+ (datetimeParseFail C.parser_YmdHMS_lenient "2016-01-02 01:02:03" "Failed reading: input does not start with a digit")+ ]+ , testGroup "MdyHMS Lenient Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator"+ (datetimeParse C.parser_MdyHMS_lenient "01012016010223" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator"+ (datetimeParse C.parser_MdyHMS_lenient "01z01%2016^01a02c23" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Fails with extra seperators"+ (datetimeParseFail C.parser_MdyHMS_lenient "01-02-2016 01:02:03" "Failed reading: input does not start with a digit")+ ]+ , testGroup "DmyHMS Optional Seconds Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator With Seconds"+ (datetimeParse (C.parser_DmyHMS_opt_S (DatetimeFormat Nothing Nothing Nothing)) "01022016010223" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With No Separator Without Seconds"+ (datetimeParse (C.parser_DmyHMS_opt_S (DatetimeFormat Nothing Nothing Nothing)) "010220160102" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Passes With With Separator With Seconds"+ (datetimeParse (C.parser_DmyHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-02-2016 01:02:23" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator Without Seconds"+ (datetimeParse (C.parser_DmyHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-02-2016 01:02" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Fails with trailing seperator"+ (datetimeParseFail (C.parser_DmyHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-02-2016 01:02:" "not enough input")+ , PH.testCase "Fails with extra seperators"+ (datetimeParseFail (C.parser_DmyHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-02-2016 01:02" "Failed reading: input does not start with a digit")+ ]+ , testGroup "YmdHMS Optional Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator With Seconds"+ (datetimeParse (C.parser_YmdHMS_opt_S (DatetimeFormat Nothing Nothing Nothing)) "20160101010223" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With No Separator Without Seconds"+ (datetimeParse (C.parser_YmdHMS_opt_S (DatetimeFormat Nothing Nothing Nothing)) "201601010102" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Passes With With Separator With Seconds"+ (datetimeParse (C.parser_YmdHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "2016-01-01 01:02:23" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator Without Seconds"+ (datetimeParse (C.parser_YmdHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "2016-01-01 01:02" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Fails with trailing seperator"+ (datetimeParseFail (C.parser_YmdHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "2016-01-02 01:02:" "not enough input")+ , PH.testCase "Fails with extra seperators"+ (datetimeParseFail (C.parser_YmdHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "2016-01-02 01:02" "Failed reading: input does not start with a digit")+ ]+ , testGroup "MdyHMS Optional Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator With Seconds"+ (datetimeParse (C.parser_MdyHMS_opt_S (DatetimeFormat Nothing Nothing Nothing)) "01012016010223" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With No Separator Without Seconds"+ (datetimeParse (C.parser_MdyHMS_opt_S (DatetimeFormat Nothing Nothing Nothing)) "010120160102" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Passes With With Separator With Seconds"+ (datetimeParse (C.parser_MdyHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-01-2016 01:02:23" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator Without Seconds"+ (datetimeParse (C.parser_MdyHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-01-2016 01:02" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Fails with trailing seperator"+ (datetimeParseFail (C.parser_MdyHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-02-2016 01:02:" "not enough input")+ , PH.testCase "Fails with extra seperators"+ (datetimeParseFail (C.parser_MdyHMS_opt_S (DatetimeFormat (Just '-') (Just ' ') (Just ':'))) "01-02-2016 01:02" "Failed reading: input does not start with a digit")+ ]+ , testGroup "DmyHMS Optional Seconds Lenient Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator with seconds"+ (datetimeParse C.parser_DmyHMS_opt_S_lenient "01022016010223" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With No Separator without seconds"+ (datetimeParse C.parser_DmyHMS_opt_S_lenient "010220160102" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Passes With With Separator with seconds"+ (datetimeParse C.parser_DmyHMS_opt_S_lenient "01z02x2016$01;02:23" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator without seconds"+ (datetimeParse C.parser_DmyHMS_opt_S_lenient "01z02x2016$01;02" $+ Datetime (Date (Year 2016) (Month 1) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Fails with trailing seperator"+ (datetimeParseFail C.parser_DmyHMS_opt_S_lenient "01z02x2016$01;02^" "not enough input")+ , PH.testCase "Fails with extra seperators"+ (datetimeParseFail C.parser_DmyHMS_opt_S_lenient "01-02-2016 01:02" "Failed reading: input does not start with a digit")+ ]+ , testGroup "YmdHMS Optional Seconds Lenient Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator With Seconds"+ (datetimeParse C.parser_YmdHMS_opt_S_lenient "20160101010223" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With No Separator Without Seconds"+ (datetimeParse C.parser_YmdHMS_opt_S_lenient "201601010102" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Passes With With Separator With Seconds"+ (datetimeParse C.parser_YmdHMS_opt_S_lenient "2016!01z01^01a02c23" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator Without Seconds"+ (datetimeParse C.parser_YmdHMS_opt_S_lenient "2016!01z01^01a02" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Fails with trailing seperator"+ (datetimeParseFail C.parser_YmdHMS_opt_S_lenient "2016!01z01^01a02^" "not enough input")+ , PH.testCase "Fails with extra seperators"+ (datetimeParseFail C.parser_YmdHMS_opt_S_lenient "2016-01-02 01:02" "Failed reading: input does not start with a digit")+ ]+ , testGroup "MdyHMS Optional Seconds Lenient Parser Spec Tests" $+ [ PH.testCase "Passes With No Separator With Seconds"+ (datetimeParse C.parser_MdyHMS_opt_S_lenient "01012016010223" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With No Separator Without Seconds"+ (datetimeParse C.parser_MdyHMS_opt_S_lenient "010120160102" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Passes With With Separator With Seconds"+ (datetimeParse C.parser_MdyHMS_opt_S_lenient "01z01!2016^01a02c23" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 23000000000)+ )+ , PH.testCase "Passes With With Separator Without Seconds"+ (datetimeParse C.parser_MdyHMS_opt_S_lenient "01z01(2016^01a02" $+ Datetime (Date (Year 2016) (Month 0) (DayOfMonth 1)) (TimeOfDay 01 02 0)+ )+ , PH.testCase "Fails with trailing seperator"+ (datetimeParseFail C.parser_MdyHMS_opt_S_lenient "01z01!2016^01a02^" "not enough input")+ , PH.testCase "Fails with extra seperators"+ (datetimeParseFail C.parser_MdyHMS_opt_S_lenient "01-02-2016 01:02" "Failed reading: input does not start with a digit")+ ]+ , testGroup "Builder Parser Isomorphism" $+ [ testProperty "(Y-m-dTH:M:S)" $ propEncodeDecodeIsoSettings+ (\format -> LText.toStrict . Builder.toLazyText . C.builder_YmdHMS (SubsecondPrecisionFixed 9) format)+ (\format -> either (const Nothing) Just . Atto.parseOnly (C.parser_YmdHMS format))+ , testProperty "Builder Parser Isomorphism (YmdHMS)" $ propEncodeDecodeIso+ (LText.toStrict . Builder.toLazyText . C.builder_YmdHMS (SubsecondPrecisionFixed 9) (DatetimeFormat Nothing Nothing Nothing))+ (either (const Nothing) Just . Atto.parseOnly (C.parser_YmdHMS (DatetimeFormat Nothing Nothing Nothing)))+ ] ] , testGroup "Offset Datetime" [ testGroup "Builder Spec Tests" $@@ -212,6 +446,40 @@ [ testProperty "Verify TimeInterval construction correctness" propTimeIntervalBuilder ] ]+ , testGroup "Datetime Conversions"+ [ testGroup "datetimeToDayOfWeek"+ [ PH.testCase "February 2nd 2020"+ (C.datetimeToDayOfWeek (Datetime (Date (Year 2020) (Month 1) (DayOfMonth 2)) (TimeOfDay 0 0 0)) @?= DayOfWeek 0)+ , PH.testCase "July 10th 2019"+ (C.datetimeToDayOfWeek (Datetime (Date (Year 2019) (Month 6) (DayOfMonth 10)) (TimeOfDay 0 0 0)) @?= DayOfWeek 3)+ , PH.testCase "November 16th 1946"+ (C.datetimeToDayOfWeek (Datetime (Date (Year 1946) (Month 10) (DayOfMonth 16)) (TimeOfDay 0 0 0)) @?= DayOfWeek 6)+ , PH.testCase "February 29th 2024 (Leap Year)"+ (C.datetimeToDayOfWeek (Datetime (Date (Year 2024) (Month 1) (DayOfMonth 29)) (TimeOfDay 0 0 0)) @?= DayOfWeek 4)+ ]+ ]+ , testGroup "timeToDayOfWeek Conversions"+ [ PH.testCase "Sunday, February 9, 2020 4:00:00 PM"+ (C.timeToDayOfWeek (Time 1581264000000000000) @?= DayOfWeek 0)+ , PH.testCase "Monday, April 9, 2001 4:00:00 PM"+ (C.timeToDayOfWeek (Time 986832000000000000) @?= DayOfWeek 1)+ , PH.testCase "Tuesday, March 7, 1995 4:00:00 PM"+ (C.timeToDayOfWeek (Time 794592000000000000) @?= DayOfWeek 2)+ , PH.testCase "Wednesday, June 17, 1987 4:00:00 PM"+ (C.timeToDayOfWeek (Time 550944000000000000) @?= DayOfWeek 3)+ , PH.testCase "Thursday, December 18, 1980 4:00:00 PM"+ (C.timeToDayOfWeek (Time 346003200000000000) @?= DayOfWeek 4)+ , PH.testCase "Friday, October 10, 1975 4:00:00 PM"+ (C.timeToDayOfWeek (Time 182188800000000000) @?= DayOfWeek 5)+ , PH.testCase "Saturday, August 11, 1973 4:00:00 PM"+ (C.timeToDayOfWeek (Time 113932800000000000) @?= DayOfWeek 6)+ , PH.testCase "Thursday, January 1, 1970 12:00:00 AM"+ (C.timeToDayOfWeek (Time 0) @?= DayOfWeek 4)+ , PH.testCase "Saturday, June 14, 1969 4:00:00 PM"+ (C.timeToDayOfWeek (Time (-17308800000000000)) @?= DayOfWeek 6)+ , PH.testCase "Tuesday, June 6, 1944 4:00:00 PM"+ (C.timeToDayOfWeek (Time (-806918400000000000)) @?= DayOfWeek 2)+ ] ] failure :: String -> Result@@ -279,10 +547,25 @@ LText.toStrict (Builder.toLazyText (C.builder_HMS sp m tod)) @?= expected -dateParse :: Maybe Char -> Text -> Date -> Assertion-dateParse m t expected =- Atto.parseOnly (C.parser_Ymd m <* Atto.endOfInput) t+dateParse :: Atto.Parser Date -> Text -> Date -> Assertion+dateParse p t expected =+ Atto.parseOnly (p <* Atto.endOfInput) t @?= Right expected++--dateParseFail :: Atto.Parser Date -> Text -> String -> Assertion+--dateParseFail p t expected =+-- Atto.parseOnly (p <* Atto.endOfInput) t+-- @?= Left expected++datetimeParse :: Atto.Parser Datetime -> Text -> Datetime -> Assertion+datetimeParse p t expected =+ Atto.parseOnly (p <* Atto.endOfInput) t+ @?= Right expected++datetimeParseFail :: Atto.Parser Datetime -> Text -> String -> Assertion+datetimeParseFail p t expected =+ Atto.parseOnly (p <* Atto.endOfInput) t+ @?= Left expected dateBuilder :: Maybe Char -> Text -> Date -> Assertion dateBuilder m expected tod =