packages feed

chronos 0.4 → 1.1.7.0

raw patch · 31 files changed

Files

+ CHANGELOG.md view
@@ -0,0 +1,104 @@+# Revision history for chronos++## 1.1.7.0 -- 2025.07.15++* Add `decodeIso8601Zulu` for going straight from `Text` to a `Datetime`.+* Require a newer version of the `text` library so that UTF-8 encoding is used.++## 1.1.6.2 -- 2024-12-11++* Support GHC 9.10++## 1.1.6.1 -- 2024-02-15++* Update package metadata.++## 1.1.6.0 -- 2024-01-29++* Add `sinceEpoch` and `asSeconds` functions.+* Make `boundedBuilderUtf8BytesIso8601Zoneless` suppress trailing zeros+  when a timestamp has no submillisecond part. For example, this function+  prints 2021-01-05T23:00:52.123 where it previously+  printed 2021-01-05T23:00:52.123000000.++## 1.1.5.1 -- 2023-08-24++* Allow newer `natural-arithmetic`, `primitive`, and `vector`.+* Allow base 4.18.+* Add units to offset docs.++## 1.1.5 -- 2022-11-03++* Add `decodeShortTextIso8601Zulu`.+* Add `decodeUtf8BytesIso8601ZonelessSpaced`.++## 1.1.4 -- 2022-02-21++* Fix February length in `normalYearMonthLength`. Fixes GitHub issue #67.+* Support aeson-2.x, with backward compatibility for aeson-1.x.+* Add helper `dateToDayOfWeek`+* Add helpers `encode_Ymd` and `encode_Dmy` to more easily pretty-print dates+* Add various lenses for core data type fields and isos+* Drop support for GHC 8.8 and earlier.++## 1.1.3 -- 2021-09-07++* Dropped support for GHC < 8.6.+* Integrated with `byteverse` libraries and `text-short`, adding efficient+  builders and parsers targeting UT8-encoded unpinned byte arrays. The new+  functions are: `boundedBuilderUtf8BytesIso8601Zoneless`,+  `encodeShortTextIso8601Zulu`, `encodeShortTextIso8601Zoneless`,+  `parserUtf8BytesIso8601`, `boundedBuilderUtf8BytesIso8601`,+  `decodeUtf8BytesIso8601`, `decodeShortTextIso8601`,+  `decodeShortTextIso8601Zoneless`, `decodeUtf8BytesIso8601Zoneless`,+  `encodeShortTextIso8601`.+* Corrected an implementation mistake that had caused many parsers to+  incorrectly identifier a subseconds part of 0 (i.e. `23:59:17.000`).+* Improved layout of cabal file.++## 1.1.2 -- 2021-02-08++* Adds `NFData` typeclass instances for all data types.+* Add `encodeIso8601` and `builderIso8601`.+* Soft deprecate `builderW3C` in favor of `builderIso8601`.+* Fix formatting in cabal file.++## 1.1.1 -- 2020-04-17++* Add `timeToDayOfWeek`, `datetimeToDayOfWeek`, `todayDayOfWeek`,+  `yesterdayDayOfWeek`, and `tomorrowDayOfWeek`.+* Remove `stopwatchWith(_)` on GHC 8.6+.+* Fix build on 32-bit POSIX systems.++## 1.1 -- 2019-11-28++* Drop dependency of `clock` on GHC 8.6+.+* Remove `stopwatchWith(_)` on GHC 8.6+.+* Deprecate `stopwatchWith(_)` on GHC <8.6.+* Fix build on Windows. Thanks @SpaceKitteh for reporting this.++## 1.0.9 -- 2019-11-09++* Add `TimeParts` for custom formatting.++## 1.0.8 -- 2019-11-07++* Allow newer semigroups.++## 1.0.7 -- 2019-08-16++* Fix build on windows. Chronos now builds on windows, macos, and linux. Thanks+  to @nprindle for the fix/testing and @ShrykerWindgrace for reporting it!++## 1.0.6 -- 2019-07-19++* Add 'TimeInterval' type and related functions.++## 1.0.5 -- 2019-05-01++* Allow newer version of `clock` (==0.7.* ===> >=0.7 && < 0.9)+* Build with -Wall+* Build with -O2++## 1.0.4 -- 2018-08-14+* Initial version, w.r.t. CHANGELOG (i.e. there was no changelog before).
+ README.md view
@@ -0,0 +1,68 @@+# Chronos++Chronos is a performance-oriented time library for Haskell, with a+straightforward API. The main differences between this+and the [time](http://hackage.haskell.org/package/time) library+are:+  * Chronos uses machine integers where possible. This means+    that time-related arithmetic should be faster, with the+    drawback that the types are incapable of representing times+    that are very far in the future or the past (because Chronos+    provides nanosecond, rather than picosecond, resolution).+    For most users, this is not a hindrance and the tradeoff is+    worthwhile.+  * Chronos provides 'ToJSON'/'FromJSON' instances for serialisation.+  * Chronos provides 'Unbox' instances for working with unboxed vectors.+  * Chronos provides 'Prim' instances for working with byte arrays/primitive arrays.+  * Chronos uses normal non-overloaded haskell functions for+    encoding and decoding time. It provides [attoparsec](http://hackage.haskell.org/package/attoparsec) parsers for both 'Text' and+    'ByteString'. Additionally, Chronos provides functions for+    encoding time to 'Text' or 'ByteString'. The [time](http://hackage.haskell.org/package/time) library accomplishes these with the+    [Data.Time.Format](http://hackage.haskell.org/package/time-1.9.3/docs/Data-Time-Format.html) 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+    less expressive.++Jacob Stanley has written+[a blog post comparing the features and performance](https://jacobstanley.io/3-packages-you-need-to-know-about-before-processing-timestamps-in-haskell/)+of `time`, `thyme`, and `chronos`. It has a good bulleted breakdown of why+you may want to use each library along with some benchmarks.++## Benchmarks++Benchmarks of `chronos` against `time` and `thyme`.++### Parsing++| Benchmark name            | Time     |+|---------------------------|----------|+| Time.parseTimeM           | 9.679 μs |+| Thyme.parseTime           | 1.743 μs |+| Thyme.timeParser          | 1.113 μs |+| Chronos.parserUtf8_YmdHMS | 301.4 ns |+| Chronos.zeptoUtf8_YmdHMS  | 173.6 ns |++### Pretty-printing++| Benchmark name          | Time     |+|-------------------------|----------|+| dmy/Time.formatTime     | 4.404 μs |+| dmy/Thyme.formatTime    | 663.0 ns |+| dmy/Chronos.builder_Dmy | 340.9 ns |+| HMS/Time.formatTime     | 1.987 μs |+| HMS/Thyme.formatTime    | 879.1 ns |+| HMS/Chronos.builder_HMS | 481.3 ns |++## Doctest++Doctest used to be provided as a test suite, but `doctest-0.20` and higher+do not require this to be run. To run the doctests, make sure you have+`doctest` on your path (i.e. run `cabal install doctest`), and then run:++    cabal build+    cabal repl --build-depends=QuickCheck --with-ghc=doctest --repl-options='-fno-warn-orphans'++This runs incredibly slowly, but it works for now. Doctest is not run by CI,+so if you make a change that adds more doctests, it needs to be run by hand+by someone. (The maintainer is happy to do this if you're on a platform+where doctest is finicky.)
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
+ bench/Bench.hs view
@@ -0,0 +1,157 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeApplications #-}++module Main where++import Criterion.Main++import Control.DeepSeq (($!!))+import Data.Attoparsec.ByteString.Char8 (parseOnly)+import Data.Maybe (fromMaybe)+import Data.Text.Lazy.Builder (toLazyText)+import Data.Text.Short (ShortText)+import Test.QuickCheck (arbitrary, generate)++import qualified Chronos+import qualified Data.Attoparsec.Zepto as Z+import qualified Data.ByteString.Char8 as BS8+import qualified Data.Text.Lazy as LT+import qualified Data.Text.Short as TS+import qualified Data.Thyme as Thyme+import qualified Data.Time as Time+import qualified System.Locale as Thyme++main :: IO ()+main = do+  utcthyme <- generate arbitrary :: IO Thyme.UTCTime+  let+    renderIsoTime = Thyme.formatTime Thyme.defaultTimeLocale isoFormatString+    -- 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.++    dmy = "%d:%m:%y."+    hms = "%H:%M:%S."+    dmyhms = isoFormatString+    timePretty = Time.formatTime Time.defaultTimeLocale+    thymePretty = Thyme.formatTime Thyme.defaultTimeLocale++  timeTime <- Time.getCurrentTime+  thymeTime <- Thyme.getCurrentTime+  chronosTime <- Chronos.now++  string <- return $!! renderIsoTime utcthyme+  bytestring <- return $!! BS8.pack string+  shortText <- return $!! TS.pack string++  defaultMain+    [ bgroup+        "parsing"+        [ bench "Time.parseTimeM" $ nf timeParser string+        , bench "Thyme.parseTime" $ nf thymeParser string+        , bench "Thyme.timeParser" $ nf thymeAttoparsec bytestring+        , bench "Chronos.parserUtf8_YmdHMS" $ nf chronosAttoparsec bytestring+        , bench "Chronos.zeptoUtf8_YmdHMS" $ nf chronosZepto bytestring+        , bench "Chronos.decodeShortTextIso8601" $ nf chronosIso8601 shortText+        ]+    , bgroup+        "prettyPrint"+        [ bgroup+            "dmy"+            [ bench "Time.formatTime" $ nf (timePretty dmy) timeTime+            , bench "Thyme.formatTime" $ nf (thymePretty dmy) thymeTime+            , bench "Chronos.builder_Dmy" $ nf chronosPrettyDmy chronosTime+            ]+        , bgroup+            "HMS"+            [ bench "Time.formatTime" $ nf (timePretty hms) timeTime+            , 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+            ]+        , bgroup+            "ISO-8601-Zulu"+            [ bench "Chronos.encodeShortTextIso8601Zulu" $ nf encodeChronosIso8601Zulu chronosTime+            ]+        ]+    ]++encodeChronosIso8601Zulu :: Chronos.Time -> ShortText+encodeChronosIso8601Zulu !t =+  Chronos.encodeShortTextIso8601Zulu (Chronos.timeToDatetime t)++chronosIso8601 :: ShortText -> Chronos.Time+{-# NOINLINE chronosIso8601 #-}+chronosIso8601 !t = case Chronos.decodeShortTextIso8601Zoneless t of+  Just x -> Chronos.datetimeToTime x+  Nothing -> errorWithoutStackTrace "chronosIso8601: decode failure"++chronosZepto :: BS8.ByteString -> Chronos.Time+{-# NOINLINE chronosZepto #-}+chronosZepto !bs =+  either+    error+    Chronos.datetimeToTime+    (Z.parse (Chronos.zeptoUtf8_YmdHMS Chronos.w3c) bs)++chronosPrettyYmdHMS :: Chronos.Time -> LT.Text+{-# NOINLINE chronosPrettyYmdHMS #-}+chronosPrettyYmdHMS =+  toLazyText+    . Chronos.builder_YmdHMS Chronos.SubsecondPrecisionAuto Chronos.w3c+    . Chronos.timeToDatetime++chronosPrettyHMS :: Chronos.Time -> LT.Text+{-# NOINLINE chronosPrettyHMS #-}+chronosPrettyHMS =+  toLazyText+    . Chronos.builder_HMS Chronos.SubsecondPrecisionAuto (Just ':')+    . Chronos.datetimeTime+    . Chronos.timeToDatetime++chronosPrettyDmy :: Chronos.Time -> LT.Text+{-# NOINLINE chronosPrettyDmy #-}+chronosPrettyDmy =+  toLazyText+    . Chronos.builder_Dmy (Just ':')+    . Chronos.datetimeDate+    . Chronos.timeToDatetime++chronosAttoparsec :: BS8.ByteString -> Chronos.Time+{-# NOINLINE chronosAttoparsec #-}+chronosAttoparsec =+  either error Chronos.datetimeToTime+    . parseOnly (Chronos.parserUtf8_YmdHMS Chronos.w3c)++timeParser :: String -> Time.UTCTime+{-# NOINLINE timeParser #-}+timeParser =+  fromMaybe (error "Failed to parse in timeParser")+    . Time.parseTimeM True Time.defaultTimeLocale isoFormatString++thymeParser :: String -> Thyme.UTCTime+{-# NOINLINE thymeParser #-}+thymeParser =+  fromMaybe (error "Failed to parse in thymeParser")+    . Thyme.parseTime Thyme.defaultTimeLocale isoFormatString++thymeAttoparsec :: BS8.ByteString -> Thyme.UTCTime+{-# NOINLINE thymeAttoparsec #-}+thymeAttoparsec =+  Thyme.buildTime @Thyme.UTCTime+    . either error id+    . parseOnly (Thyme.timeParser Thyme.defaultTimeLocale isoFormatString)++isoFormatString :: String+{-# NOINLINE isoFormatString #-}+isoFormatString = "%Y-%m-%dT%H:%M:%S"
chronos.cabal view
@@ -1,96 +1,120 @@-name:                chronos-version:             0.4-synopsis:            A performant time library+cabal-version:   3.0+name:            chronos+version:         1.1.7.0+synopsis:        A high-performance time library description:-  Performance-oriented time library for haskell. The main differences-  between this and "time" are that this library:-  .-  * Uses machine integers where possible. This means that some time-related-    arithmetic should be faster. It also means that the types are incapable-    of representing times that are very far in the future or the past.-  .-  * Provides "ToJSON" and "FromJSON" instances for json serialization.-  .-  * Provides "Unbox" instances for working with unboxed vectors.-  .-  * Uses normal non-overloaded haskell functions for encoding and decoding time. It provides-    "attoparsec" parsers for both "Text" and "ByteString". Additionally, it-    provides functions for encoding time as "Text" or "ByteString". The "time"-    library uses accomplishes these with the "Data.Time.Format" module,-    which uses UNIX-style datetime format strings. It is expected that-    the approach taken in this library is faster and catches more mistakes-    at compile time at the cost of being less expressive.-  .-  * Only provides nanosecond resolution instead of picosecond resolution.+  Chronos is a performance-oriented time library for Haskell, with a+  straightforward API. The main differences between this+  and the <http://hackage.haskell.org/package/time time> library+  are:+  * Chronos uses machine integers where possible. This means+  that time-related arithmetic should be faster, with the+  drawback that the types are incapable of representing times+  that are very far in the future or the past (because Chronos+  provides nanosecond, rather than picosecond, resolution).+  For most users, this is not a hindrance.+  * Chronos provides 'ToJSON'/'FromJSON' instances for serialisation.+  * Chronos provides 'Unbox' instances for working with unboxed vectors.+  * Chronos provides 'Prim' instances for working with byte arrays/primitive arrays.+  * 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+  <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+  less expressive.+homepage:        https://github.com/byteverse/chronos+bug-reports:     https://github.com/byteverse/chronos/issues+license:         BSD-3-Clause+license-file:    LICENSE+author:          Andrew Martin+maintainer:      amartin@layer3com.com, chessai1996@gmail.com+copyright:       2016 Andrew Martin+category:        Data, Time, Parsing, Development+build-type:      Simple+extra-doc-files:+  CHANGELOG.md+  README.md +tested-with:     GHC ==9.6.3 || ==9.8.1 -homepage:            https://github.com/andrewthad/chronos#readme-license:             BSD3-license-file:        LICENSE-author:              Andrew Martin-maintainer:          andrew.thaddeus@gmail.com-copyright:           2016 Andrew Martin-category:            web-build-type:          Simple-cabal-version:       >=1.10+common build-settings+  default-language: Haskell2010+  ghc-options:      -Wall -Wunused-packages  library-  hs-source-dirs:      src+  import:          build-settings+  hs-source-dirs:  src   exposed-modules:-      Chronos.Types-    , Chronos.Calendar-    , Chronos.Match-    , Chronos.Nanoseconds-    , Chronos.Day-    , Chronos.Date.Text-    , Chronos.Date.ByteString.Char7-    , Chronos.Datetime-    , Chronos.Datetime.Text-    , Chronos.Datetime.ByteString.Char7-    , Chronos.OffsetDatetime.Text-    , Chronos.OffsetDatetime.ByteString.Char7-    , Chronos.TimeOfDay.Text-    , Chronos.TimeOfDay.ByteString.Char7-    , Chronos.Internal-    , Chronos.Internal.Format-    , Chronos.Internal.Conversion-    , Chronos.Internal.CTimespec-    , Chronos.Tai-    , Chronos.Posix-    , Chronos.Month-    , Chronos.Locale.English.Text+    Chronos+    Chronos.Internal.CTimespec+    Chronos.Locale.English+    Chronos.Types   build-depends:-      base >= 4.8 && < 5-    , vector >= 0.11 && < 0.13-    , text >= 1.2 && < 1.3-    , bytestring >= 0.10 && < 0.11-    , attoparsec >= 0.13 && < 0.14-    -- Remove support for aeson < 1.0 after the newer-    -- aeson lands in stackage. Currently, ToJSONKey-    -- and FromJSONKey instances cannot be added.-    , aeson >= 0.11 && < 1.1-    , hashable >= 1.2 && < 1.3-    , primitive >= 0.6 && < 0.7-  default-language:    Haskell2010+    , aeson               >=2.2     && <2.3+    , attoparsec          >=0.13    && <0.15+    , base                >=4.14    && <5+    , bytebuild           >=0.3.14  && <0.4+    , byteslice           >=0.2.5.2 && <0.3+    , bytesmith           >=0.3.7   && <0.4+    , bytestring          >=0.10    && <0.13+    , deepseq             >=1.4.4.0+    , hashable            >=1.2     && <1.5+    , natural-arithmetic  >=0.1.2   && <0.3+    , primitive           >=0.6.4   && <0.10+    , text                >=2.1.2   && <2.2+    , text-short          >=0.1.5   && <0.2+    , torsor              >=0.1     && <0.2+    , vector              >=0.11    && <0.14+  if os(windows)+    build-depends: Win32 >=2.2 && <2.14 +  c-sources:       src/cbits/hs-time.c+  ghc-options:     -O2+ test-suite chronos-test-  type:                exitcode-stdio-1.0-  hs-source-dirs:      test-  main-is:             Spec.hs+  import:         build-settings+  type:           exitcode-stdio-1.0+  hs-source-dirs: test+  main-is:        Spec.hs   build-depends:-      base-    , chronos-    , text-    , bytestring+    , aeson                       >=1.1 && <2.3+    , HUnit+    , QuickCheck     , attoparsec+    , base+    , bytestring+    , chronos     , test-framework+    , test-framework-hunit     , test-framework-quickcheck2+    , text+    , torsor++  ghc-options:    -threaded -rtsopts -with-rtsopts=-N++benchmark bench+  import:         build-settings+  type:           exitcode-stdio-1.0+  hs-source-dirs: bench+  main-is:        Bench.hs+  build-depends:+    , attoparsec+    , base+    , bytestring+    , chronos+    , criterion+    , deepseq     >=1.4.4.0+    , old-locale     , QuickCheck-    , HUnit-    , test-framework-hunit-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N-  default-language:    Haskell2010+    , text+    , text-short+    , thyme+    , time +  ghc-options:    -O2+ source-repository head   type:     git-  location: https://github.com/andrewthad/chronos+  location: git://github.com/byteverse/chronos.git
+ src/Chronos.hs view
@@ -0,0 +1,4012 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UnboxedTuples #-}++{- | Chronos is a performance-oriented time library for Haskell, with a+    straightforward API. The main differences between this+    and the <http://hackage.haskell.org/package/time time> library+    are:++      * Chronos uses machine integers where possible. This means+        that time-related arithmetic should be faster, with the+        drawback that the types are incapable of representing times+        that are very far in the future or the past (because Chronos+        provides nanosecond, rather than picosecond, resolution).+        For most users, this is not a hindrance.+      * Chronos provides 'ToJSON'/'FromJSON' instances for serialisation.+      * Chronos provides 'UVector.Unbox' instances for working with unboxed vectors.+      * Chronos provides 'Prim' instances for working with byte arrays/primitive arrays.+      * 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+        <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+        less expressive.+-}+module Chronos+  ( -- * Functions++    -- ** Current+    now+  , today+  , tomorrow+  , yesterday+  , todayDayOfWeek+  , yesterdayDayOfWeek+  , tomorrowDayOfWeek+  , timeToDayOfWeek+  , epoch++    -- ** Duration+  , stopwatch+  , stopwatch_++    -- ** Construction+  , datetimeFromYmdhms+  , timeFromYmdhms++    -- ** Conversion+  , timeToDatetime+  , datetimeToTime+  , datetimeToDayOfWeek+  , dateToDayOfWeek+  , timeToOffsetDatetime+  , offsetDatetimeToTime+  , timeToDayTruncate+  , dayToTimeMidnight+  , dayToDate+  , dateToDay+  , dayToOrdinalDate+  , ordinalDateToDay+  , monthDateToDayOfYear+  , dayOfYearToMonthDay++    -- ** Build Timespan+  , second+  , minute+  , hour+  , day+  , week++    -- ** Matching+  , buildDayOfWeekMatch+  , buildMonthMatch+  , buildUnboxedMonthMatch+  , caseDayOfWeek+  , caseMonth+  , caseUnboxedMonth++    -- ** Format+    -- $format+  , w3c+  , slash+  , hyphen+  , compact+  , timeParts++    -- ** Months+  , january+  , february+  , march+  , april+  , may+  , june+  , july+  , august+  , september+  , october+  , november+  , december++    -- ** Days of Week+  , sunday+  , monday+  , tuesday+  , wednesday+  , thursday+  , friday+  , saturday++    -- ** Utility+  , daysInMonth+  , isLeapYear+  , observedOffsets+  , sinceEpoch+  , asSeconds++    -- * Textual Conversion++    -- ** Date++    -- *** Text+  , builder_Ymd+  , 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++    -- ** Time of Day++    -- *** Text+  , builder_IMS_p+  , builder_IMSp+  , parser_HMS+  , parser_HMS_opt_S++    -- *** UTF-8 ByteString+  , builderUtf8_HMS+  , builderUtf8_IMS_p+  , builderUtf8_IMSp+  , parserUtf8_HMS+  , parserUtf8_HMS_opt_S+  , zeptoUtf8_HMS++    -- ** Datetime++    -- *** Text+  , builder_DmyHMS+  , builder_DmyIMSp+  , builder_DmyIMS_p+  , builder_YmdHMS+  , builder_YmdIMSp+  , builder_YmdIMS_p+  , builderW3C+  , builderIso8601+  , encodeIso8601+  , encode_Ymd+  , encode_Dmy+  , encode_DmyHMS+  , encode_DmyIMS_p+  , 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+  , decodeIso8601Zulu++    -- *** UTF-8 ByteString+  , encodeUtf8_YmdHMS+  , encodeUtf8_YmdIMS_p+  , builderUtf8_YmdHMS+  , builderUtf8_YmdIMSp+  , builderUtf8_YmdIMS_p+  , builderUtf8W3C+  , decodeUtf8_YmdHMS+  , decodeUtf8_YmdHMS_opt_S+  , parserUtf8_YmdHMS+  , parserUtf8_YmdHMS_opt_S+  , zeptoUtf8_YmdHMS++    -- *** UTF-8 Bytes+  , boundedBuilderUtf8BytesIso8601Zoneless+  , decodeUtf8BytesIso8601Zoneless+  , decodeUtf8BytesIso8601ZonelessSpaced++    -- *** Short Text+  , decodeShortTextIso8601Zulu+  , decodeShortTextIso8601Zoneless+  , encodeShortTextIso8601Zulu+  , encodeShortTextIso8601Zoneless++    -- ** Offset Datetime++    -- *** Text+  , encode_YmdHMSz+  , encode_DmyHMSz+  , builder_YmdHMSz+  , builder_DmyHMSz+  , parser_YmdHMSz+  , parser_DmyHMSz+  , builder_YmdIMS_p_z+  , builder_DmyIMS_p_z+  , builderW3Cz++    -- *** UTF-8 ByteString+  , builderUtf8_YmdHMSz+  , parserUtf8_YmdHMSz+  , builderUtf8_YmdIMS_p_z+  , builderUtf8W3Cz++    -- *** UTF-8 Bytes+  , parserUtf8BytesIso8601+  , boundedBuilderUtf8BytesIso8601+  , decodeUtf8BytesIso8601++    -- *** ShortText+  , decodeShortTextIso8601+  , encodeShortTextIso8601++    -- ** Offset++    -- *** Text+  , encodeOffset+  , builderOffset+  , decodeOffset+  , parserOffset++    -- *** UTF-8 ByteString+  , encodeOffsetUtf8+  , builderOffsetUtf8+  , decodeOffsetUtf8+  , parserOffsetUtf8++    -- ** Timespan++    -- *** Text+  , encodeTimespan+  , builderTimespan++    -- *** UTF-8 ByteString+  , encodeTimespanUtf8+  , builderTimespanUtf8++    -- ** TimeInterval+  , within+  , timeIntervalToTimespan+  , whole+  , singleton+  , lowerBound+  , upperBound+  , width+  , timeIntervalBuilder+  , (...)++    -- * Types+  , Day (..)+  , DayOfWeek (..)+  , DayOfMonth (..)+  , DayOfYear (..)+  , Month (..)+  , Year (..)+  , Offset (..)+  , Time (..)+  , DayOfWeekMatch (..)+  , MonthMatch (..)+  , UnboxedMonthMatch (..)+  , Timespan (..)+  , SubsecondPrecision (..)+  , Date (..)+  , OrdinalDate (..)+  , MonthDate (..)+  , Datetime (..)+  , OffsetDatetime (..)+  , TimeOfDay (..)+  , DatetimeFormat (..)+  , OffsetFormat (..)+  , DatetimeLocale (..)+  , MeridiemLocale (..)+  , TimeInterval (..)+  , TimeParts (..)++    -- * Lenses+  , _timeToDatetime+  , _datetimeToTime+  , _dayToDate+  , _dateToDay+  , _getDay+  , _getDayOfWeek+  , _getDayOfMonth+  , _getDayOfYear+  , _getMonth+  , _getOffset+  , _getTime+  , _getTimespan+  , _dateYear+  , _dateMonth+  , _dateDay+  , _ordinalDateYear+  , _ordinalDateDayOfYear+  , _monthDateMonth+  , _monthDateDay+  , _datetimeDate+  , _datetimeTime+  , _offsetDatetimeDatetime+  , _offsetDatetimeOffset+  , _timeOfDayHour+  , _timeOfDayMinute+  , _timeOfDayNanoseconds+  ) where++import Control.Applicative+import Control.DeepSeq (NFData (..), deepseq)+import Control.Exception (evaluate)+import Control.Monad+import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey)+import Data.Attoparsec.Text (Parser)+import Data.Bool (bool)+import Data.ByteString (ByteString)+import Data.Bytes (Bytes)+import Data.Char (isDigit)+import Data.Foldable+import Data.Hashable (Hashable)+import Data.Int (Int64)+import Data.Primitive+import Data.Text (Text)+import Data.Text.Short (ShortText)+import Data.Vector (Vector)+import Data.Word (Word64, Word8)+import Foreign.Storable+import GHC.Clock (getMonotonicTimeNSec)+import GHC.Generics (Generic)+import Torsor++import qualified Arithmetic.Lte as Lte+import qualified Arithmetic.Nat as Nat+import qualified Data.Aeson as AE+import qualified Data.Aeson.Encoding as AEE+import qualified Data.Aeson.Key as AK+import qualified Data.Aeson.Types as AET+import qualified Data.Attoparsec.ByteString.Char8 as AB+import qualified Data.Attoparsec.Text as AT+import qualified Data.Attoparsec.Zepto as Z+import qualified Data.ByteString.Builder as BB+import qualified Data.ByteString.Char8 as BC+import qualified Data.ByteString.Lazy as LB+import qualified Data.ByteString.Short.Internal as SBS+import qualified Data.Bytes as Bytes+import qualified Data.Bytes.Builder.Bounded as Bounded+import qualified Data.Bytes.Parser as BVP+import qualified Data.Bytes.Parser.Latin as Latin+import qualified Data.Bytes.Text.Utf8 as Utf8+import qualified Data.Semigroup as SG+import qualified Data.Text as Text+import qualified Data.Text.Lazy as LT+import qualified Data.Text.Lazy.Builder as TB+import qualified Data.Text.Lazy.Builder.Int as TB+import qualified Data.Text.Read as Text+import qualified Data.Text.Short as TS+import qualified Data.Text.Short.Unsafe as TS+import qualified Data.Vector as Vector+import qualified Data.Vector.Generic as GVector+import qualified Data.Vector.Generic.Mutable as MGVector+import qualified Data.Vector.Primitive as PVector+import qualified Data.Vector.Unboxed as UVector++#ifdef mingw32_HOST_OS+import System.Win32.Time (SYSTEMTIME(..))+import qualified System.Win32.Time as W32+#else+import Chronos.Internal.CTimespec (getPosixNanoseconds)+#endif++{- $setup+>>> import Test.QuickCheck hiding (within)+>>> import Test.QuickCheck.Gen+>>> import Data.Maybe (isJust)+>>> :set -XStandaloneDeriving+>>> :set -XGeneralizedNewtypeDeriving+>>> :set -XScopedTypeVariables++>>> deriving instance Arbitrary Time+>>> :{+  instance Arbitrary TimeInterval where+    arbitrary = do+      t0 <- arbitrary+      t1 <- suchThat arbitrary (>= t0)+      pure (TimeInterval t0 t1)+  instance Arbitrary TimeOfDay where+    arbitrary = TimeOfDay+      <$> choose (0,23)+      <*> choose (0,59)+      <*> choose (0, 60000000000 - 1)+  instance Arbitrary Date where+    arbitrary = Date+      <$> fmap Year (choose (1800,2100))+      <*> fmap Month (choose (0,11))+      <*> fmap DayOfMonth (choose (1,28))+  instance Arbitrary Datetime where+    arbitrary = Datetime <$> arbitrary <*> arbitrary+  instance Arbitrary OffsetDatetime where+    arbitrary = OffsetDatetime <$> arbitrary <*> arbitrary+  instance Arbitrary DatetimeFormat where+    arbitrary = DatetimeFormat+      <$> arbitrary+      <*> elements [ Nothing, Just '/', Just ':', Just '-']+      <*> arbitrary+  instance Arbitrary OffsetFormat where+    arbitrary = arbitraryBoundedEnum+    shrink = genericShrink+  instance Arbitrary Offset where+    arbitrary = fmap Offset (choose ((-24) * 60, 24 * 60))+  instance Arbitrary SubsecondPrecision where+    arbitrary = frequency+      [ (1, pure SubsecondPrecisionAuto)+      , (1, SubsecondPrecisionFixed <$> choose (0,9))+      ]+  instance Arbitrary Day where+    arbitrary = fmap Day (choose (0,50000))+:}+-}++-- | A 'Timespan' representing a single second.+second :: Timespan+second = Timespan 1000000000++-- | A 'Timespan' representing a single minute.+minute :: Timespan+minute = Timespan 60000000000++-- | A 'Timespan' representing a single hour.+hour :: Timespan+hour = Timespan 3600000000000++-- | A 'Timespan' representing a single day.+day :: Timespan+day = Timespan 86400000000000++-- | A 'Timespan' representing a single week.+week :: Timespan+week = Timespan 604800000000000++{- | Convert 'Time' to 'Datetime'.++  prop> \(t :: Time) -> (datetimeToTime (timeToDatetime t)) == t+-}+timeToDatetime :: Time -> Datetime+timeToDatetime = utcTimeToDatetime . toUtc++{- | Convert 'Datetime' to 'Time'.++  prop> \(d :: Datetime) -> timeToDatetime (datetimeToTime d) == d+-}+datetimeToTime :: Datetime -> Time+datetimeToTime = fromUtc . datetimeToUtcTime++{- | A lens-compatible variant of half of the `timeToDatetime`/`datetimeToTime` isomorphism.++__Note__: We do not provide an iso as that requires a dependence on the `profunctor`+package.+-}+_timeToDatetime :: forall f. (Functor f) => (Datetime -> f Datetime) -> Time -> f Time+_timeToDatetime f = fmap datetimeToTime . f . timeToDatetime++{- | A lens-compatible variant of half of the `timeToDatetime`/`datetimeToTime` isomorphism.++__Note__: We do not provide an iso as that requires a dependence on the `profunctor`+package.+-}+_datetimeToTime :: forall f. (Functor f) => (Time -> f Time) -> Datetime -> f Datetime+_datetimeToTime f = fmap timeToDatetime . f . datetimeToTime++-- | Convert 'Datetime' to 'DayOfWeek'+datetimeToDayOfWeek :: Datetime -> DayOfWeek+datetimeToDayOfWeek (Datetime date _) = dateToDayOfWeek date++-- | Convert 'Date' to 'DayOfWeek'+dateToDayOfWeek :: Date -> DayOfWeek+dateToDayOfWeek (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++-- | Convert 'OffsetDatetime' to 'Time'.+offsetDatetimeToTime :: OffsetDatetime -> Time+offsetDatetimeToTime = fromUtc . offsetDatetimeToUtcTime++{- | Convert 'Time' to 'Day'. This function is lossy; consequently, it+  does not roundtrip with 'dayToTimeMidnight'.+-}+timeToDayTruncate :: Time -> Day+timeToDayTruncate (Time i) = Day (fromIntegral (div i 86400000000000) + 40587)++-- | Convert midnight of the given 'Day' to 'Time'.+dayToTimeMidnight :: Day -> Time+dayToTimeMidnight (Day d) = Time (fromIntegral (d - 40587) * 86400000000000)++{- | Convert 'Day' to a 'Date'.++  prop> \(d :: Day) -> dateToDay (dayToDate d) == d+-}+dayToDate :: Day -> Date+dayToDate theDay = Date year month dayOfMonth+ where+  OrdinalDate year yd = dayToOrdinalDate theDay+  MonthDate month dayOfMonth = dayOfYearToMonthDay (isLeapYear year) yd++{- | Convert a 'Date' to a 'Day'.++  prop> \(d :: Date) -> dayToDate (dateToDay d) == d+-}+dateToDay :: Date -> Day+dateToDay (Date y m d) =+  ordinalDateToDay $+    OrdinalDate+      y+      (monthDateToDayOfYear (isLeapYear y) (MonthDate m d))++{- | A lens-compatible variant of half of the `dayToDate`/`dateToDay` isomorphism.++__Note__: We do not provide an iso as that requires a dependence on the `profunctor`+package.+-}+_dayToDate :: forall f. (Functor f) => (Date -> f Date) -> Day -> f Day+_dayToDate f = fmap dateToDay . f . dayToDate++{- | A lens-compatible variant of half of the `dayToDate`/`dateToDay` isomorphism.++__Note__: We do not provide an iso as that requires a dependence on the `profunctor`+package.+-}+_dateToDay :: forall f. (Functor f) => (Day -> f Day) -> Date -> f Date+_dateToDay f = fmap dayToDate . f . dateToDay++{- | Construct a 'Datetime' from year, month, day, hour, minute, second:++  >>> datetimeFromYmdhms 2014 2 26 17 58 52+  Datetime {datetimeDate = Date {dateYear = Year {getYear = 2014}, dateMonth = Month {getMonth = 1}, dateDay = DayOfMonth {getDayOfMonth = 26}}, datetimeTime = TimeOfDay {timeOfDayHour = 17, timeOfDayMinute = 58, timeOfDayNanoseconds = 52000000000}}+-}+datetimeFromYmdhms ::+  -- | Year+  Int ->+  -- | Month+  Int ->+  -- | Day+  Int ->+  -- | Hour+  Int ->+  -- | Minute+  Int ->+  -- | Second+  Int ->+  Datetime+datetimeFromYmdhms y m d h m' s =+  Datetime+    ( Date+        (Year $ fromIntegral y)+        (Month mx)+        (DayOfMonth $ fromIntegral d)+    )+    ( TimeOfDay+        (fromIntegral h)+        (fromIntegral m')+        (fromIntegral s * 1000000000)+    )+ where+  mx =+    if m >= 1 && m <= 12+      then fromIntegral (m - 1)+      else 0++{- | Construct a 'Time' from year, month, day, hour, minute, second:++  >>> timeFromYmdhms 2014 2 26 17 58 52+  Time {getTime = 1393437532000000000}+-}+timeFromYmdhms ::+  -- | Year+  Int ->+  -- | Month+  Int ->+  -- | Day+  Int ->+  -- | Hour+  Int ->+  -- | Minute+  Int ->+  -- | Second+  Int ->+  Time+timeFromYmdhms y m d h m' s = datetimeToTime (datetimeFromYmdhms y m d h m' s)++{- | Gets the current 'Day'. This does not take the user\'s+  time zone into account.+-}+today :: IO Day+today = fmap timeToDayTruncate now++-- | Gets the 'Day' of tomorrow.+tomorrow :: IO Day+tomorrow = fmap (add 1 . timeToDayTruncate) now++-- | Gets the 'Day' of yesterday.+yesterday :: IO Day+yesterday = fmap (add (-1) . timeToDayTruncate) now++-- | Get the current time from the system clock.+now :: IO Time+#ifdef mingw32_HOST_OS+now = do+  SYSTEMTIME{..} <- W32.getSystemTime+  let date = Date+        { dateYear  = Year       (fromIntegral wYear)+        , dateMonth = Month      (fromIntegral wMonth - 1)+        , dateDay   = DayOfMonth (fromIntegral wDay)+        }+  let secNano = (fromIntegral wSecond :: Int64) * 1000000000+      msNano  = (fromIntegral wMilliseconds :: Int64) * 1000000+      nano    = secNano + msNano+  let time = TimeOfDay+        { timeOfDayHour        = fromIntegral wHour+        , timeOfDayMinute      = fromIntegral wMinute+        , timeOfDayNanoseconds = fromIntegral nano+        }+  let dt = Datetime date time+  pure $ datetimeToTime dt+#else+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++{- | Measures the time it takes to run an action and evaluate+  its result to WHNF. This measurement uses a monotonic clock+  instead of the standard system clock.+-}+stopwatch :: IO a -> IO (Timespan, a)+stopwatch action = do+  start <- getMonotonicTimeNSec+  a <- action >>= evaluate+  end <- getMonotonicTimeNSec+  pure ((Timespan (fromIntegral (end - start))), a)++{- | Measures the time it takes to run an action. The result+  is discarded. This measurement uses a monotonic clock+  instead of the standard system clock.+-}+stopwatch_ :: IO a -> IO Timespan+stopwatch_ action = do+  start <- getMonotonicTimeNSec+  _ <- action+  end <- getMonotonicTimeNSec+  pure (Timespan (fromIntegral (end - start)))++-- UtcTime. Used internally only.+data UtcTime+  = UtcTime+      {-# UNPACK #-} !Day -- day+      {-# UNPACK #-} !Int64 -- nanoseconds++toUtc :: Time -> UtcTime+toUtc (Time i) =+  let (d, t) = divMod i (getTimespan day)+   in UtcTime (add (fromIntegral d) epochDay) (fromIntegral t)++fromUtc :: UtcTime -> Time+fromUtc (UtcTime d ns') =+  Time $+    getTimespan $+      plus+        (scale (intToInt64 (difference d epochDay)) day)+        (if ns > day then day else ns)+ where+  ns = Timespan ns'++intToInt64 :: Int -> Int64+intToInt64 = fromIntegral++epochDay :: Day+epochDay = Day 40587++dayLengthInt64 :: Int64+dayLengthInt64 = getTimespan day++nanosecondsInMinute :: Int64+nanosecondsInMinute = 60000000000++-- | All UTC time offsets. See <https://en.wikipedia.org/wiki/List_of_UTC_time_offsets List of UTC time offsets>.+observedOffsets :: Vector Offset+observedOffsets =+  Vector.fromList $+    map+      Offset+      [ -1200+      , -1100+      , -1000+      , -930+      , -900+      , -800+      , -700+      , -600+      , -500+      , -400+      , -330+      , -300+      , -230+      , -200+      , -100+      , 0+      , 100+      , 200+      , 300+      , 330+      , 400+      , 430+      , 500+      , 530+      , 545+      , 600+      , 630+      , 700+      , 800+      , 845+      , 900+      , 930+      , 1000+      , 1030+      , 1100+      , 1200+      , 1245+      , 1300+      , 1345+      , 1400+      ]++{- | Compute the 'Timespan' between the given date and 'epoch'.++@since 1.1.6.0+-}+sinceEpoch :: Time -> Timespan+sinceEpoch t = difference t epoch++{- | Convert a 'Timespan' to its equivalent in seconds.++@since 1.1.6.0+-}+asSeconds :: Timespan -> Int64+asSeconds (Timespan t) = case second of+  Timespan s -> t `div` s++{- | The first argument in the resulting tuple in a day+  adjustment. It should be either -1, 0, or 1, as no+  offset should ever exceed 24 hours.+-}+offsetTimeOfDay :: Offset -> TimeOfDay -> (Int, TimeOfDay)+offsetTimeOfDay (Offset offset) (TimeOfDay h m s) =+  (dayAdjustment, TimeOfDay h'' m'' s)+ where+  (!dayAdjustment, !h'') = divMod h' 24+  (!hourAdjustment, !m'') = divMod m' 60+  m' = m + offset+  h' = h + hourAdjustment++nanosecondsSinceMidnightToTimeOfDay :: Int64 -> TimeOfDay+nanosecondsSinceMidnightToTimeOfDay ns =+  if ns >= dayLengthInt64+    then TimeOfDay 23 59 (nanosecondsInMinute + (ns - dayLengthInt64))+    else TimeOfDay h' m' ns'+ where+  (!mInt64, !ns') = quotRem ns nanosecondsInMinute+  !m = fromIntegral mInt64+  (!h', !m') = quotRem m 60++timeOfDayToNanosecondsSinceMidnight :: TimeOfDay -> Int64+timeOfDayToNanosecondsSinceMidnight (TimeOfDay h m ns) =+  fromIntegral h * 3600000000000 + fromIntegral m * 60000000000 + ns++-- datetimeToOffsetDatetime :: Offset -> Datetime -> OffsetDatetime+-- datetimeToOffsetDatetime offset++utcTimeToOffsetDatetime :: Offset -> UtcTime -> OffsetDatetime+utcTimeToOffsetDatetime offset (UtcTime (Day d) nanoseconds) =+  let (!dayAdjustment, !tod) = offsetTimeOfDay offset (nanosecondsSinceMidnightToTimeOfDay nanoseconds)+      !date = dayToDate (Day (d + dayAdjustment))+   in OffsetDatetime (Datetime date tod) offset++utcTimeToDatetime :: UtcTime -> Datetime+utcTimeToDatetime (UtcTime d nanoseconds) =+  let !tod = nanosecondsSinceMidnightToTimeOfDay nanoseconds+      !date = dayToDate d+   in Datetime date tod++datetimeToUtcTime :: Datetime -> UtcTime+datetimeToUtcTime (Datetime date timeOfDay) =+  UtcTime (dateToDay date) (timeOfDayToNanosecondsSinceMidnight timeOfDay)++offsetDatetimeToUtcTime :: OffsetDatetime -> UtcTime+offsetDatetimeToUtcTime (OffsetDatetime (Datetime date timeOfDay) (Offset off)) =+  let (!dayAdjustment, !tod) = offsetTimeOfDay (Offset $ negate off) timeOfDay+      !(Day !theDay) = dateToDay date+   in UtcTime+        (Day (theDay + dayAdjustment))+        (timeOfDayToNanosecondsSinceMidnight tod)++-- | Convert a 'MonthDate' to a 'DayOfYear'.+monthDateToDayOfYear ::+  -- | Is it a leap year?+  Bool ->+  MonthDate ->+  DayOfYear+monthDateToDayOfYear isLeap (MonthDate month@(Month m) (DayOfMonth dayOfMonth)) =+  DayOfYear ((div (367 * (fromIntegral m + 1) - 362) 12) + k + day')+ where+  day' = fromIntegral $ clip 1 (daysInMonth isLeap month) dayOfMonth+  k = if month < Month 2 then 0 else if isLeap then -1 else -2++-- | Convert an 'OrdinalDate' to a 'Day'.+ordinalDateToDay :: OrdinalDate -> Day+ordinalDateToDay (OrdinalDate year@(Year y') theDay) = Day mjd+ where+  y = y' - 1+  mjd =+    ( fromIntegral . getDayOfYear $+        (clip (DayOfYear 1) (if isLeapYear year then DayOfYear 366 else DayOfYear 365) theDay)+    )+      + (365 * y)+      + (div y 4)+      - (div y 100)+      + (div y 400)+      - 678576++{- | Is the 'Year' a leap year?++  >>> isLeapYear (Year 1996)+  True++  >>> isLeapYear (Year 2019)+  False+-}+isLeapYear :: Year -> Bool+isLeapYear (Year year) = (mod year 4 == 0) && ((mod year 400 == 0) || not (mod year 100 == 0))++-- | Convert a 'DayOfYear' to a 'MonthDate'.+dayOfYearToMonthDay ::+  -- | Is it a leap year?+  Bool ->+  DayOfYear ->+  MonthDate+dayOfYearToMonthDay isLeap dayOfYear =+  let (!doyUpperBound, !monthTable, !dayTable) =+        if isLeap+          then (DayOfYear 366, leapYearDayOfYearMonthTable, leapYearDayOfYearDayOfMonthTable)+          else (DayOfYear 365, normalYearDayOfYearMonthTable, normalYearDayOfYearDayOfMonthTable)+      DayOfYear clippedDay = clip (DayOfYear 1) doyUpperBound dayOfYear+      clippedDayInt = fromIntegral clippedDay :: Int+      month = UVector.unsafeIndex monthTable clippedDayInt+      theDay = UVector.unsafeIndex dayTable clippedDayInt+   in MonthDate month theDay++-- | Convert a 'Day' to an 'OrdinalDate'.+dayToOrdinalDate :: Day -> OrdinalDate+dayToOrdinalDate (Day mjd) = OrdinalDate (Year $ fromIntegral year) (DayOfYear $ fromIntegral yd)+ where+  a = (fromIntegral mjd :: Int64) + 678575+  quadcent = div a 146097+  b = mod a 146097+  cent = min (div b 36524) 3+  c = b - (cent * 36524)+  quad = div c 1461+  d = mod c 1461+  y = min (div d 365) 3+  yd = (d - (y * 365) + 1)+  year = quadcent * 400 + cent * 100 + quad * 4 + y + 1++{- $format++The formats provided is this module are language-agnostic.+To find meridiem formats and month formats, look in a+language-specific module.+-}++{- | The W3C 'DatetimeFormat'.++  >>> encode_YmdHMS SubsecondPrecisionAuto w3c (timeToDatetime (timeFromYmdhms 2014 2 26 17 58 52))+  "2014-02-26T17:58:52"++ prop> \(s :: SubsecondPrecision) (dt :: Datetime) -> isJust (decode_YmdHMS w3c (encode_YmdHMS s w3c dt))+-}+w3c :: DatetimeFormat+w3c = DatetimeFormat (Just '-') (Just 'T') (Just ':')++{- | A 'DatetimeFormat' that separates the members of+  the 'Date' by slashes.++  >>> encode_YmdHMS SubsecondPrecisionAuto slash (timeToDatetime (timeFromYmdhms 2014 2 26 17 58 52))+  "2014/02/26 17:58:52"++  prop> \(s :: SubsecondPrecision) (dt :: Datetime) -> isJust (decode_YmdHMS slash (encode_YmdHMS s slash dt))+-}+slash :: DatetimeFormat+slash = DatetimeFormat (Just '/') (Just ' ') (Just ':')++{- | A 'DatetimeFormat' that separates the members of+  the 'Date' by hyphens.++  >>> encode_YmdHMS SubsecondPrecisionAuto hyphen (timeToDatetime (timeFromYmdhms 2014 2 26 17 58 52))+  "2014-02-26 17:58:52"++  prop> \(s :: SubsecondPrecision) (dt :: Datetime) -> isJust (decode_YmdHMS hyphen (encode_YmdHMS s hyphen dt))+-}+hyphen :: DatetimeFormat+hyphen = DatetimeFormat (Just '-') (Just ' ') (Just ':')++{- | A 'DatetimeFormat' with no separators, except for a+  `T` between the 'Date' and 'Time'.++  >>> encode_YmdHMS SubsecondPrecisionAuto compact (timeToDatetime (timeFromYmdhms 2014 2 26 17 58 52))+  "20140226T175852"++  prop> \(s :: SubsecondPrecision) (dt :: Datetime) -> isJust (decode_YmdHMS compact (encode_YmdHMS s compact dt))+-}+compact :: DatetimeFormat+compact = DatetimeFormat Nothing (Just 'T') Nothing++-- | Return the number of days in a given month.+daysInMonth ::+  -- | Is this a leap year?+  Bool ->+  -- | Month of year+  Month ->+  Int+daysInMonth isLeap m =+  if isLeap+    then caseMonth leapYearMonthLength m+    else caseMonth normalYearMonthLength m++leapYearMonthLength :: MonthMatch Int+leapYearMonthLength = buildMonthMatch 31 29 31 30 31 30 31 31 30 31 30 31++normalYearMonthLength :: MonthMatch Int+normalYearMonthLength = buildMonthMatch 31 28 31 30 31 30 31 31 30 31 30 31++leapYearDayOfYearDayOfMonthTable :: UVector.Vector DayOfMonth+leapYearDayOfYearDayOfMonthTable =+  UVector.fromList $+    (DayOfMonth 1 :) $+      concat+        [ enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 29)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 30)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 30)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 30)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 30)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        ]+{-# NOINLINE leapYearDayOfYearDayOfMonthTable #-}++normalYearDayOfYearDayOfMonthTable :: UVector.Vector DayOfMonth+normalYearDayOfYearDayOfMonthTable =+  UVector.fromList $+    (DayOfMonth 1 :) $+      concat+        [ enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 28)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 30)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 30)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 30)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 30)+        , enumFromTo (DayOfMonth 1) (DayOfMonth 31)+        ]+{-# NOINLINE normalYearDayOfYearDayOfMonthTable #-}++leapYearDayOfYearMonthTable :: UVector.Vector Month+leapYearDayOfYearMonthTable =+  UVector.fromList $+    (Month 0 :) $+      concat+        [ replicate 31 (Month 0)+        , replicate 29 (Month 1)+        , replicate 31 (Month 2)+        , replicate 30 (Month 3)+        , replicate 31 (Month 4)+        , replicate 30 (Month 5)+        , replicate 31 (Month 6)+        , replicate 31 (Month 7)+        , replicate 30 (Month 8)+        , replicate 31 (Month 9)+        , replicate 30 (Month 10)+        , replicate 31 (Month 11)+        ]+{-# NOINLINE leapYearDayOfYearMonthTable #-}++normalYearDayOfYearMonthTable :: UVector.Vector Month+normalYearDayOfYearMonthTable =+  UVector.fromList $+    (Month 0 :) $+      concat+        [ replicate 31 (Month 0)+        , replicate 28 (Month 1)+        , replicate 31 (Month 2)+        , replicate 30 (Month 3)+        , replicate 31 (Month 4)+        , replicate 30 (Month 5)+        , replicate 31 (Month 6)+        , replicate 31 (Month 7)+        , replicate 30 (Month 8)+        , replicate 31 (Month 9)+        , replicate 30 (Month 10)+        , replicate 31 (Month 11)+        ]+{-# NOINLINE normalYearDayOfYearMonthTable #-}++-- | Build a 'MonthMatch' from twelve (12) values.+buildMonthMatch :: a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> MonthMatch a+buildMonthMatch a b c d e f g h i j k l =+  MonthMatch (Vector.fromListN 12 [a, b, c, d, e, f, g, h, i, j, k, l])++-- | Match a 'Month' against a 'MonthMatch'.+caseMonth :: MonthMatch a -> Month -> a+caseMonth (MonthMatch v) (Month ix) = Vector.unsafeIndex v ix++-- | Build an 'UnboxedMonthMatch' from twelve (12) values.+buildUnboxedMonthMatch :: (UVector.Unbox a) => a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> UnboxedMonthMatch a+buildUnboxedMonthMatch a b c d e f g h i j k l =+  UnboxedMonthMatch (UVector.fromListN 12 [a, b, c, d, e, f, g, h, i, j, k, l])++-- | Match a 'Month' against an 'UnboxedMonthMatch'.+caseUnboxedMonth :: (UVector.Unbox a) => UnboxedMonthMatch a -> Month -> a+caseUnboxedMonth (UnboxedMonthMatch v) (Month ix) = UVector.unsafeIndex v ix++-- | Build a 'DayOfWeekMatch' from seven (7) values.+buildDayOfWeekMatch :: a -> a -> a -> a -> a -> a -> a -> DayOfWeekMatch a+buildDayOfWeekMatch a b c d e f g =+  DayOfWeekMatch (Vector.fromListN 7 [a, b, c, d, e, f, g])++-- | Match a 'DayOfWeek' against a 'DayOfWeekMatch'.+caseDayOfWeek :: DayOfWeekMatch a -> DayOfWeek -> a+caseDayOfWeek (DayOfWeekMatch v) (DayOfWeek ix) = Vector.unsafeIndex v ix++{- | Given a 'Date' and a separator, construct a 'Text' 'TB.Builder'+  corresponding to Year\/Month\/Day encoding.+-}+builder_Ymd :: Maybe Char -> Date -> TB.Builder+builder_Ymd msep (Date y m d) = case msep of+  Nothing ->+    yearToZeroPaddedDigit y+      <> monthToZeroPaddedDigit m+      <> zeroPadDayOfMonth d+  Just sep ->+    let sepBuilder = TB.singleton sep+     in yearToZeroPaddedDigit y+          <> sepBuilder+          <> monthToZeroPaddedDigit m+          <> sepBuilder+          <> zeroPadDayOfMonth d++{- | Given a 'Date' and a separator, construct a 'Text.Text'+  corresponding to a Year\/Month\/Day encoding.++  >>> encode_Ymd (Just ':') (Date (Year 2022) january (DayOfMonth 13))+  "2022:01:13"+-}+encode_Ymd :: Maybe Char -> Date -> Text+encode_Ymd msep = LT.toStrict . TB.toLazyText . builder_Ymd msep++{- | Given a 'Date' and a separator, construct a 'Text' 'TB.Builder'+  corresponding to a Day\/Month\/Year encoding.+-}+builder_Dmy :: Maybe Char -> Date -> TB.Builder+builder_Dmy msep (Date y m d) = case msep of+  Nothing ->+    zeroPadDayOfMonth d+      <> monthToZeroPaddedDigit m+      <> yearToZeroPaddedDigit y+  Just sep ->+    let sepBuilder = TB.singleton sep+     in zeroPadDayOfMonth d+          <> sepBuilder+          <> monthToZeroPaddedDigit m+          <> sepBuilder+          <> yearToZeroPaddedDigit y++{- | Given a 'Date' and a separator, construct a 'Text.Text'+  corresponding to a Day\/Month\/Year encoding.++  >>> encode_Dmy (Just ':') (Date (Year 2022) january (DayOfMonth 13))+  "13:01:2022"+-}+encode_Dmy :: Maybe Char -> Date -> Text+encode_Dmy msep = LT.toStrict . TB.toLazyText . builder_Dmy msep++{- | Parse a Year\/Month\/Day-encoded 'Date' that uses the+  given separator.+-}+parser_Ymd :: Maybe Char -> Parser Date+parser_Ymd msep = do+  y <- parseFixedDigits 4+  traverse_ AT.char msep+  m <- parseFixedDigits 2+  when (m < 1 || m > 12) (fail "month must be between 1 and 12")+  traverse_ AT.char msep+  d <- parseFixedDigits 2+  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+parser_Mdy msep = do+  m <- parseFixedDigits 2+  when (m < 1 || m > 12) (fail "month must be between 1 and 12")+  traverse_ AT.char msep+  d <- parseFixedDigits 2+  when (d < 1 || d > 31) (fail "day must be between 1 and 31")+  traverse_ AT.char msep+  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+parser_Dmy msep = do+  d <- parseFixedDigits 2+  when (d < 1 || d > 31) (fail "day must be between 1 and 31")+  traverse_ AT.char msep+  m <- parseFixedDigits 2+  when (m < 1 || m > 12) (fail "month must be between 1 and 12")+  traverse_ AT.char msep+  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+builderUtf8_Ymd msep (Date y m d) = case msep of+  Nothing ->+    yearToZeroPaddedDigitBS y+      <> monthToZeroPaddedDigitBS m+      <> zeroPadDayOfMonthBS d+  Just sep ->+    let sepBuilder = BB.char7 sep+     in yearToZeroPaddedDigitBS y+          <> sepBuilder+          <> monthToZeroPaddedDigitBS m+          <> sepBuilder+          <> zeroPadDayOfMonthBS d++{- | Parse a Year\/Month\/Day-encoded 'Date' that uses the+  given separator.+-}+parserUtf8_Ymd :: Maybe Char -> AB.Parser Date+parserUtf8_Ymd msep = do+  y <- parseFixedDigitsIntBS 4+  traverse_ AB.char msep+  m <- parseFixedDigitsIntBS 2+  when (m < 1 || m > 12) (fail "month must be between 1 and 12")+  traverse_ AB.char msep+  d <- parseFixedDigitsIntBS 2+  when (d < 1 || d > 31) (fail "day must be between 1 and 31")+  pure (Date (Year y) (Month $ m - 1) (DayOfMonth d))++{- | Given a 'SubsecondPrecision' and a separator, construct a+  'Text' 'TB.Builder' corresponding to an Hour\/Minute\/Second+  encoding.+-}+builder_HMS :: SubsecondPrecision -> Maybe Char -> TimeOfDay -> TB.Builder+builder_HMS sp msep (TimeOfDay h m ns) =+  indexTwoDigitTextBuilder h+    <> internalBuilder_NS sp msep m ns++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision', and a separator,+  construct a 'Text' 'TB.Builder' according to an IMS encoding.++  This differs from 'builder_IMSp' in that their is a space+  between the seconds and locale.+-}+builder_IMS_p :: MeridiemLocale Text -> SubsecondPrecision -> Maybe Char -> TimeOfDay -> TB.Builder+builder_IMS_p meridiemLocale sp msep (TimeOfDay h m ns) =+  internalBuilder_I h+    <> internalBuilder_NS sp msep m ns+    <> " "+    <> internalBuilder_p meridiemLocale h++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision', and a separator,+  construct a 'Text' 'TB.Builder' according to an IMS encoding.+-}+builder_IMSp :: MeridiemLocale Text -> SubsecondPrecision -> Maybe Char -> TimeOfDay -> TB.Builder+builder_IMSp meridiemLocale sp msep (TimeOfDay h m ns) =+  internalBuilder_I h+    <> internalBuilder_NS sp msep m ns+    <> internalBuilder_p meridiemLocale h++internalBuilder_I :: Int -> TB.Builder+internalBuilder_I h =+  indexTwoDigitTextBuilder $+    if h > 12+      then h - 12+      else+        if h == 0+          then 12+          else h++internalBuilder_p :: MeridiemLocale Text -> Int -> TB.Builder+internalBuilder_p (MeridiemLocale am pm) h =+  if h > 11+    then TB.fromText pm+    else TB.fromText am++{- | Parse an Hour\/Minute\/Second-encoded 'TimeOfDay' that uses+  the given separator.+-}+parser_HMS :: Maybe Char -> Parser TimeOfDay+parser_HMS msep = do+  h <- parseFixedDigits 2+  when (h > 23) (fail "hour must be between 0 and 23")+  traverse_ AT.char msep+  m <- parseFixedDigits 2+  when (m > 59) (fail "minute must be between 0 and 59")+  traverse_ AT.char msep+  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@+* @%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 :: Maybe Char -> Parser TimeOfDay+parser_HMS_opt_S msep = do+  h <- parseFixedDigits 2+  when (h > 23) (fail "hour must be between 0 and 23")+  traverse_ AT.char msep+  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 -> case msep of+      Just sep ->+        if c == sep+          then do+            _ <- AT.anyChar -- should be the separator+            ns <- parseSecondsAndNanoseconds+            pure (TimeOfDay h m ns)+          else pure (TimeOfDay h m 0)+      -- if there is no separator, we will try to parse the+      -- remaining part as seconds. We commit to trying to+      -- parse as seconds if we see any number as the next+      -- character.+      Nothing ->+        if isDigit c+          then do+            ns <- parseSecondsAndNanoseconds+            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+  let s = fromIntegral s' :: Int64+  when (s > 60) (fail "seconds must be between 0 and 60")+  nanoseconds <-+    ( do+        _ <- AT.char '.'+        numberOfZeroes <- countZeroes+        AT.peekChar >>= \case+          Just c | c >= '0' && c <= '9' -> do+            x <- AT.decimal+            let totalDigits = countDigits x + numberOfZeroes+                result =+                  if totalDigits == 9+                    then x+                    else+                      if totalDigits < 9+                        then x * raiseTenTo (9 - totalDigits)+                        else quot x (raiseTenTo (totalDigits - 9))+            pure (fromIntegral result)+          _ -> pure 0+    )+      <|> pure 0+  pure (s * 1000000000 + nanoseconds)++countZeroes :: AT.Parser Int+countZeroes = go 0+ where+  go !i = do+    m <- AT.peekChar+    case m of+      Nothing -> pure i+      Just c ->+        if c == '0'+          then AT.anyChar *> go (i + 1)+          else pure i++nanosecondsBuilder :: Int64 -> TB.Builder+nanosecondsBuilder w+  | w == 0 = mempty+  | w > 99999999 = "." <> TB.decimal w+  | w > 9999999 = ".0" <> TB.decimal w+  | w > 999999 = ".00" <> TB.decimal w+  | w > 99999 = ".000" <> TB.decimal w+  | w > 9999 = ".0000" <> TB.decimal w+  | w > 999 = ".00000" <> TB.decimal w+  | w > 99 = ".000000" <> TB.decimal w+  | w > 9 = ".0000000" <> TB.decimal w+  | otherwise = ".00000000" <> TB.decimal w++microsecondsBuilder :: Int64 -> TB.Builder+microsecondsBuilder w+  | w == 0 = mempty+  | w > 99999 = "." <> TB.decimal w+  | w > 9999 = ".0" <> TB.decimal w+  | w > 999 = ".00" <> TB.decimal w+  | w > 99 = ".000" <> TB.decimal w+  | w > 9 = ".0000" <> TB.decimal w+  | otherwise = ".00000" <> TB.decimal w++millisecondsBuilder :: Int64 -> TB.Builder+millisecondsBuilder w+  | w == 0 = mempty+  | w > 99 = "." <> TB.decimal w+  | w > 9 = ".0" <> TB.decimal w+  | otherwise = ".00" <> TB.decimal w++prettyNanosecondsBuilder :: SubsecondPrecision -> Int64 -> TB.Builder+prettyNanosecondsBuilder sp nano = case sp of+  SubsecondPrecisionAuto+    | milliRem == 0 -> millisecondsBuilder milli+    | microRem == 0 -> microsecondsBuilder micro+    | otherwise -> nanosecondsBuilder nano+  SubsecondPrecisionFixed d ->+    if d == 0+      then mempty+      else+        let newSubsecondPart = quot nano (raiseTenTo (9 - d))+         in "."+              <> TB.fromText (Text.replicate (d - countDigits newSubsecondPart) "0")+              <> TB.decimal newSubsecondPart+ where+  (milli, milliRem) = quotRem nano 1000000+  (micro, microRem) = quotRem nano 1000++-- | Encode a 'Timespan' as 'Text' using the given 'SubsecondPrecision'.+encodeTimespan :: SubsecondPrecision -> Timespan -> Text+encodeTimespan sp =+  LT.toStrict . TB.toLazyText . builderTimespan sp++{- | Construct a 'Text' 'TB.Builder' corresponding to an encoding+  of the given 'Timespan' using the given 'SubsecondPrecision'.+-}+builderTimespan :: SubsecondPrecision -> Timespan -> TB.Builder+builderTimespan sp (Timespan ns) =+  TB.decimal sInt64 <> prettyNanosecondsBuilder sp nsRemainder+ where+  (!sInt64, !nsRemainder) = quotRem ns 1000000000++internalBuilder_NS :: SubsecondPrecision -> Maybe Char -> Int -> Int64 -> TB.Builder+internalBuilder_NS sp msep m ns = case msep of+  Nothing ->+    indexTwoDigitTextBuilder m+      <> indexTwoDigitTextBuilder s+      <> prettyNanosecondsBuilder sp nsRemainder+  Just sep ->+    let sepBuilder = TB.singleton sep+     in sepBuilder+          <> indexTwoDigitTextBuilder m+          <> sepBuilder+          <> indexTwoDigitTextBuilder s+          <> prettyNanosecondsBuilder sp nsRemainder+ where+  (!sInt64, !nsRemainder) = quotRem ns 1000000000+  !s = fromIntegral sInt64++{- | Given a 'SubsecondPrecision' and a 'DatetimeFormat', construct a+  'Text' 'TB.Builder' corresponding to a+  Day\/Month\/Year,Hour\/Minute\/Second encoding of the given 'Datetime'.+-}+builder_DmyHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> TB.Builder+builder_DmyHMS sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =+  case msep of+    Nothing ->+      builder_Dmy mdateSep date+        <> builder_HMS sp mtimeSep time+    Just sep ->+      builder_Dmy mdateSep date+        <> TB.singleton sep+        <> builder_HMS sp mtimeSep time++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision',+  and a 'DatetimeFormat', construct a 'Text' 'TB.Builder'+  corresponding to a Day\/Month\/Year,IMS encoding of the given+  'Datetime'. This differs from 'builder_DmyIMSp' in that+  it adds a space between the locale and seconds.+-}+builder_DmyIMS_p :: MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> Datetime -> TB.Builder+builder_DmyIMS_p locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =+  builder_Dmy mdateSep date+    <> maybe mempty TB.singleton msep+    <> builder_IMS_p locale sp mtimeSep time++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision',+  and a 'DatetimeFormat', construct a 'Text' 'TB.Builder'+  corresponding to a Day\/Month\/Year,IMS encoding of the given+  'Datetime'.+-}+builder_DmyIMSp :: MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> Datetime -> TB.Builder+builder_DmyIMSp locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =+  builder_Dmy mdateSep date+    <> maybe mempty TB.singleton msep+    <> builder_IMS_p locale sp mtimeSep time++{- | Given a 'SubsecondPrecision' and 'DatetimeFormat', construct+  'Text' that corresponds to a Day\/Month\/Year,Hour\/Minute\/Second+  encoding of the given 'Datetime'.+-}+encode_DmyHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> Text+encode_DmyHMS sp format =+  LT.toStrict . TB.toLazyText . builder_DmyHMS sp format++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision', and a+  'DatetimeFormat', construct 'Text' that corresponds to a+  Day\/Month\/Year,IMS encoding of the given 'Datetime'. This+  inserts a space between the locale and seconds.+-}+encode_DmyIMS_p :: MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> Datetime -> Text+encode_DmyIMS_p a sp b = LT.toStrict . TB.toLazyText . builder_DmyIMS_p a sp b++{- | Given a 'SubsecondPrecision' and 'DatetimeFormat', construct+  'Text' that corresponds to a Year\/Month\/Day,Hour\/Minute\/Second+  encoding of the given 'Datetime'.+-}+encode_YmdHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> Text+encode_YmdHMS sp format =+  LT.toStrict . TB.toLazyText . builder_YmdHMS sp format++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision', and a+  'DatetimeFormat', construct 'Text' that corresponds to a+  Year\/Month\/Day,IMS encoding of the given 'Datetime'. This+  inserts a space between the locale and seconds.+-}+encode_YmdIMS_p :: MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> Datetime -> Text+encode_YmdIMS_p a sp b = LT.toStrict . TB.toLazyText . builder_YmdIMS_p a sp b++{- | Given a 'SubsecondPrecision' and a 'DatetimeFormat', construct+  a 'Text' 'TB.Builder' corresponding to a+  Year\/Month\/Day,Hour\/Minute\/Second encoding of the given 'Datetime'.+-}+builder_YmdHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> TB.Builder+builder_YmdHMS sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =+  case msep of+    Nothing ->+      builder_Ymd mdateSep date+        <> builder_HMS sp mtimeSep time+    Just sep ->+      builder_Ymd mdateSep date+        <> TB.singleton sep+        <> builder_HMS sp mtimeSep time++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision', and a+  'DatetimeFormat', construct a 'Text' 'TB.Builder' that+  corresponds to a Year\/Month\/Day,IMS encoding of the+  given 'Datetime'. This inserts a space between the locale+  and seconds.+-}+builder_YmdIMS_p :: MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> Datetime -> TB.Builder+builder_YmdIMS_p locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =+  builder_Ymd mdateSep date+    <> maybe mempty TB.singleton msep+    <> builder_IMS_p locale sp mtimeSep time++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision', and a+  'DatetimeFormat', construct a 'Text' 'TB.Builder' that+  corresponds to a Year\/Month\/Day,IMS encoding of the+  given 'Datetime'.+-}+builder_YmdIMSp :: MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> Datetime -> TB.Builder+builder_YmdIMSp locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =+  builder_Ymd mdateSep date+    <> maybe mempty TB.singleton msep+    <> builder_IMS_p locale sp mtimeSep time++{- | Construct a 'Text' 'TB.Builder' corresponding to the W3C+  encoding of the given 'Datetime'.++  Deprecated. This is just a poorly named alias for 'builderIso8601'.+-}+builderW3C :: Datetime -> TB.Builder+builderW3C = builderIso8601++{- | Construct a 'Text' 'TB.Builder' corresponding to the ISO-8601+  encoding of the given 'Datetime'.+-}+builderIso8601 :: Datetime -> TB.Builder+builderIso8601 = builder_YmdHMS SubsecondPrecisionAuto w3c++{- | Construct 'Text' corresponding to the ISO-8601+  encoding of the given 'Datetime'.++  >>> encodeIso8601 (datetimeFromYmdhms 2014 2 26 17 58 52)+  "2014-02-26T17:58:52"+-}+encodeIso8601 :: Datetime -> Text+encodeIso8601 = LT.toStrict . TB.toLazyText . builderIso8601++{- | Decode a Year\/Month\/Day,Hour\/Minute\/Second-encoded 'Datetime'+  from 'Text' that was encoded with the given 'DatetimeFormat'.+-}+decode_YmdHMS :: DatetimeFormat -> Text -> Maybe Datetime+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+parser_DmyHMS (DatetimeFormat mdateSep msep mtimeSep) = do+  date <- parser_Dmy mdateSep+  traverse_ AT.char msep+  time <- parser_HMS mtimeSep+  pure (Datetime date time)++{- | Parses text that is formatted as either of the following:++* @%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 :: DatetimeFormat -> Parser Datetime+parser_DmyHMS_opt_S (DatetimeFormat mdateSep msep mtimeSep) = do+  date <- parser_Dmy mdateSep+  traverse_ AT.char msep+  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)++{- | 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@++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 :: 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+parser_YmdHMS (DatetimeFormat mdateSep msep mtimeSep) = do+  date <- parser_Ymd mdateSep+  traverse_ AT.char msep+  time <- parser_HMS mtimeSep+  pure (Datetime date time)++{- | 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@++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 :: DatetimeFormat -> Parser Datetime+parser_YmdHMS_opt_S (DatetimeFormat mdateSep msep mtimeSep) = do+  date <- parser_Ymd mdateSep+  traverse_ AT.char msep+  time <- parser_HMS_opt_S mtimeSep+  pure (Datetime date time)++{- | 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@++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+---------------++-- | Given a 'SubsecondPrecision' and a separator, construct a 'ByteString' 'BB.Builder' corresponding to an Hour\/Minute\/Second encoding of the given 'TimeOfDay'.+builderUtf8_HMS :: SubsecondPrecision -> Maybe Char -> TimeOfDay -> BB.Builder+builderUtf8_HMS sp msep (TimeOfDay h m ns) =+  indexTwoDigitByteStringBuilder h+    <> internalBuilderUtf8_NS sp msep m ns++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision', and a separator, construct a 'ByteString' 'BB.Builder' corresponding to an IMS encoding of the given 'TimeOfDay'. This differs from 'builderUtf8_IMSp' in that+there is a space between the seconds and locale.+-}+builderUtf8_IMS_p :: MeridiemLocale ByteString -> SubsecondPrecision -> Maybe Char -> TimeOfDay -> BB.Builder+builderUtf8_IMS_p meridiemLocale sp msep (TimeOfDay h m ns) =+  internalBuilderUtf8_I h+    <> internalBuilderUtf8_NS sp msep m ns+    <> " "+    <> internalBuilderUtf8_p meridiemLocale h++internalBuilderUtf8_I :: Int -> BB.Builder+internalBuilderUtf8_I h =+  indexTwoDigitByteStringBuilder $+    if h > 12+      then h - 12+      else+        if h == 0+          then 12+          else h++internalBuilderUtf8_p :: MeridiemLocale ByteString -> Int -> BB.Builder+internalBuilderUtf8_p (MeridiemLocale am pm) h =+  if h > 11+    then BB.byteString pm+    else BB.byteString am++-- | Given a 'MeridiemLocale', a 'SubsecondPrecision', and a separator, construct a 'ByteString' 'BB.Builder' corresponding to an IMS encoding of the given 'TimeOfDay'.+builderUtf8_IMSp :: MeridiemLocale ByteString -> SubsecondPrecision -> Maybe Char -> TimeOfDay -> BB.Builder+builderUtf8_IMSp meridiemLocale sp msep (TimeOfDay h m ns) =+  internalBuilderUtf8_I h+    <> internalBuilderUtf8_NS sp msep m ns+    <> internalBuilderUtf8_p meridiemLocale h++{- | Parse an Hour\/Minute\/Second-encoded 'TimeOfDay' that uses+  the given separator.+-}+parserUtf8_HMS :: Maybe Char -> AB.Parser TimeOfDay+parserUtf8_HMS msep = do+  h <- parseFixedDigitsIntBS 2+  when (h > 23) (fail "hour must be between 0 and 23")+  traverse_ AB.char msep+  m <- parseFixedDigitsIntBS 2+  when (m > 59) (fail "minute must be between 0 and 59")+  traverse_ AB.char msep+  ns <- parseSecondsAndNanosecondsUtf8+  pure (TimeOfDay h m ns)++{- | Parses text that is formatted as either of the following:++* @%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.+-}+parserUtf8_HMS_opt_S :: Maybe Char -> AB.Parser TimeOfDay+parserUtf8_HMS_opt_S msep = do+  h <- parseFixedDigitsIntBS 2+  when (h > 23) (fail "hour must be between 0 and 23")+  traverse_ AB.char msep+  m <- parseFixedDigitsIntBS 2+  when (m > 59) (fail "minute must be between 0 and 59")+  mc <- AB.peekChar+  case mc of+    Nothing -> pure (TimeOfDay h m 0)+    Just c -> case msep of+      Just sep ->+        if c == sep+          then do+            _ <- AB.anyChar -- should be the separator+            ns <- parseSecondsAndNanosecondsUtf8+            pure (TimeOfDay h m ns)+          else pure (TimeOfDay h m 0)+      -- if there is no separator, we will try to parse the+      -- remaining part as seconds. We commit to trying to+      -- parse as seconds if we see any number as the next+      -- character.+      Nothing ->+        if isDigit c+          then do+            ns <- parseSecondsAndNanosecondsUtf8+            pure (TimeOfDay h m ns)+          else pure (TimeOfDay h m 0)++parseSecondsAndNanosecondsUtf8 :: AB.Parser Int64+parseSecondsAndNanosecondsUtf8 = do+  s' <- parseFixedDigitsIntBS 2+  let !s = fromIntegral s' :: Int64+  -- TODO: whoops, this should probably be gt 59, not 60+  when (s > 60) (fail "seconds must be between 0 and 60")+  nanoseconds <-+    ( do+        _ <- AB.char '.'+        numberOfZeroes <- countZeroesUtf8+        AB.peekChar >>= \case+          Just c | c >= '0' && c <= '9' -> do+            x <- AB.decimal+            let totalDigits = countDigits x + numberOfZeroes+                result =+                  if totalDigits == 9+                    then x+                    else+                      if totalDigits < 9+                        then x * raiseTenTo (9 - totalDigits)+                        else quot x (raiseTenTo (totalDigits - 9))+            pure (fromIntegral result)+          _ -> pure 0+    )+      <|> pure 0+  pure (s * 1000000000 + nanoseconds)++countZeroesUtf8 :: AB.Parser Int+countZeroesUtf8 = go 0+ where+  go !i = do+    m <- AB.peekChar+    case m of+      Nothing -> pure i+      Just c ->+        if c == '0'+          then AB.anyChar *> go (i + 1)+          else pure i++nanosecondsBuilderUtf8 :: Int64 -> BB.Builder+nanosecondsBuilderUtf8 w+  | w == 0 = mempty+  | w > 99999999 = "." <> int64Builder w+  | w > 9999999 = ".0" <> int64Builder w+  | w > 999999 = ".00" <> int64Builder w+  | w > 99999 = ".000" <> int64Builder w+  | w > 9999 = ".0000" <> int64Builder w+  | w > 999 = ".00000" <> int64Builder w+  | w > 99 = ".000000" <> int64Builder w+  | w > 9 = ".0000000" <> int64Builder w+  | otherwise = ".00000000" <> int64Builder w++microsecondsBuilderUtf8 :: Int64 -> BB.Builder+microsecondsBuilderUtf8 w+  | w == 0 = mempty+  | w > 99999 = "." <> int64Builder w+  | w > 9999 = ".0" <> int64Builder w+  | w > 999 = ".00" <> int64Builder w+  | w > 99 = ".000" <> int64Builder w+  | w > 9 = ".0000" <> int64Builder w+  | otherwise = ".00000" <> int64Builder w++millisecondsBuilderUtf8 :: Int64 -> BB.Builder+millisecondsBuilderUtf8 w+  | w == 0 = mempty+  | w > 99 = "." <> int64Builder w+  | w > 9 = ".0" <> int64Builder w+  | otherwise = ".00" <> int64Builder w++prettyNanosecondsBuilderUtf8 :: SubsecondPrecision -> Int64 -> BB.Builder+prettyNanosecondsBuilderUtf8 sp nano = case sp of+  SubsecondPrecisionAuto+    | milliRem == 0 -> millisecondsBuilderUtf8 milli+    | microRem == 0 -> microsecondsBuilderUtf8 micro+    | otherwise -> nanosecondsBuilderUtf8 nano+  SubsecondPrecisionFixed d ->+    if d == 0+      then mempty+      else+        let newSubsecondPart = quot nano (raiseTenTo (9 - d))+         in BB.char7 '.'+              <> BB.byteString (BC.replicate (d - countDigits newSubsecondPart) '0')+              <> int64Builder newSubsecondPart+ where+  (milli, milliRem) = quotRem nano 1000000+  (micro, microRem) = quotRem nano 1000++{- | Given a 'SubsecondPrecision', construct a 'ByteString' corresponding+  to an encoding of the given 'Timespan'.+-}+encodeTimespanUtf8 :: SubsecondPrecision -> Timespan -> ByteString+encodeTimespanUtf8 sp =+  LB.toStrict . BB.toLazyByteString . builderTimespanUtf8 sp++{- | Given a 'SubsecondPrecision', construct a 'ByteString' 'BB.Builder'+  corresponding to an encoding of the given 'Timespan'.+-}+builderTimespanUtf8 :: SubsecondPrecision -> Timespan -> BB.Builder+builderTimespanUtf8 sp (Timespan ns) =+  int64Builder sInt64 <> prettyNanosecondsBuilderUtf8 sp nsRemainder+ where+  (!sInt64, !nsRemainder) = quotRem ns 1000000000++int64Builder :: Int64 -> BB.Builder+int64Builder = BB.integerDec . fromIntegral++internalBuilderUtf8_NS :: SubsecondPrecision -> Maybe Char -> Int -> Int64 -> BB.Builder+internalBuilderUtf8_NS sp msep m ns = case msep of+  Nothing ->+    indexTwoDigitByteStringBuilder m+      <> indexTwoDigitByteStringBuilder s+      <> prettyNanosecondsBuilderUtf8 sp nsRemainder+  Just sep ->+    let sepBuilder = BB.char7 sep+     in sepBuilder+          <> indexTwoDigitByteStringBuilder m+          <> sepBuilder+          <> indexTwoDigitByteStringBuilder s+          <> prettyNanosecondsBuilderUtf8 sp nsRemainder+ where+  (!sInt64, !nsRemainder) = quotRem ns 1000000000+  !s = fromIntegral sInt64++{- | Given a 'SubsecondPrecision' and a 'DatetimeFormat', construct+  a 'ByteString' corresponding to a Year\/Month\/Day,Hour\/Minute\/Second+  encoding of the given 'Datetime'.+-}+encodeUtf8_YmdHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> ByteString+encodeUtf8_YmdHMS sp format =+  LB.toStrict . BB.toLazyByteString . builderUtf8_YmdHMS sp format++{- | Given a 'MeridiemLocale', a 'SubsecondPrecision', and a 'DatetimeFormat',+  construct a 'ByteString' corresponding to a Year\/Month\/Day,IMS encoding+  of the given 'Datetime'. This inserts a space between the locale and+  seconds.+-}+encodeUtf8_YmdIMS_p :: MeridiemLocale ByteString -> SubsecondPrecision -> DatetimeFormat -> Datetime -> ByteString+encodeUtf8_YmdIMS_p a sp b = LB.toStrict . BB.toLazyByteString . builderUtf8_YmdIMS_p a sp b++{- | Given a 'SubsecondPrecision' and a 'DatetimeFormat', construct+  a 'ByteString' 'BB.Builder' corresponding to a+  Year\/Month\/Day,Hour\/Minute\/Second encoding of the+  given 'Datetime'.+-}+builderUtf8_YmdHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> BB.Builder+builderUtf8_YmdHMS sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =+  case msep of+    Nothing ->+      builderUtf8_Ymd mdateSep date+        <> builderUtf8_HMS sp mtimeSep time+    Just sep ->+      builderUtf8_Ymd mdateSep date+        <> BB.char7 sep+        <> builderUtf8_HMS sp mtimeSep time++{- | Given a 'SubsecondPrecision' and a 'DatetimeFormat', construct+  a 'ByteString' 'BB.Builder' corresponding to a+  Year\/Month\/Day,IMS encoding of the given 'Datetime'. This inserts+  a space between the locale and seconds.+-}+builderUtf8_YmdIMS_p :: MeridiemLocale ByteString -> SubsecondPrecision -> DatetimeFormat -> Datetime -> BB.Builder+builderUtf8_YmdIMS_p locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =+  builderUtf8_Ymd mdateSep date+    <> maybe mempty BB.char7 msep+    <> builderUtf8_IMS_p locale sp mtimeSep time++{- | Given a 'SubsecondPrecision' and a 'DatetimeFormat', construct+  a 'ByteString' 'BB.Builder' corresponding to a+  Year\/Month\/Day,IMS encoding of the given 'Datetime'.+-}+builderUtf8_YmdIMSp :: MeridiemLocale ByteString -> SubsecondPrecision -> DatetimeFormat -> Datetime -> BB.Builder+builderUtf8_YmdIMSp locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =+  builderUtf8_Ymd mdateSep date+    <> maybe mempty BB.char7 msep+    <> builderUtf8_IMS_p locale sp mtimeSep time++{- | Construct a 'ByteString' 'BB.Builder' corresponding to+  a W3C encoding of the given 'Datetime'.+-}+builderUtf8W3C :: Datetime -> BB.Builder+builderUtf8W3C = builderUtf8_YmdHMS SubsecondPrecisionAuto (DatetimeFormat (Just '-') (Just 'T') (Just ':'))++{- | Decode a Year\/Month\/Day,Hour\/Minute\/Second-encoded 'Datetime' from+  a 'ByteString'.+-}+decodeUtf8_YmdHMS :: DatetimeFormat -> ByteString -> Maybe Datetime+decodeUtf8_YmdHMS format =+  either (const Nothing) Just . AB.parseOnly (parserUtf8_YmdHMS format)++{- | Parse a Year\/Month\/Day,Hour\/Minute\/Second-encoded 'Datetime' that was+  encoded using the given 'DatetimeFormat'.+-}+parserUtf8_YmdHMS :: DatetimeFormat -> AB.Parser Datetime+parserUtf8_YmdHMS (DatetimeFormat mdateSep msep mtimeSep) = do+  date <- parserUtf8_Ymd mdateSep+  traverse_ AB.char msep+  time <- parserUtf8_HMS mtimeSep+  pure (Datetime date time)++{- | Parses text that is formatted as either of the following:++* @%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.+-}+parserUtf8_YmdHMS_opt_S :: DatetimeFormat -> AB.Parser Datetime+parserUtf8_YmdHMS_opt_S (DatetimeFormat mdateSep msep mtimeSep) = do+  date <- parserUtf8_Ymd mdateSep+  traverse_ AB.char msep+  time <- parserUtf8_HMS_opt_S mtimeSep+  pure (Datetime date time)++{- | Parses text that is formatted as either of the following:++* @%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.+-}+decodeUtf8_YmdHMS_opt_S :: DatetimeFormat -> ByteString -> Maybe Datetime+decodeUtf8_YmdHMS_opt_S format =+  either (const Nothing) Just . AB.parseOnly (parserUtf8_YmdHMS_opt_S format)++{- | Given an 'OffsetFormat', a 'SubsecondPrecision', and+  a 'DatetimeFormat', construct a 'Text' 'TB.Builder'+  corresponding to a Year\/Month\/Day,Hour\/Minute\/Second encoding+  of the given 'OffsetDatetime'.+-}+builder_YmdHMSz :: OffsetFormat -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> TB.Builder+builder_YmdHMSz offsetFormat sp datetimeFormat (OffsetDatetime datetime offset) =+  builder_YmdHMS sp datetimeFormat datetime+    <> builderOffset offsetFormat offset++{- | Parse a Year\/Month\/Day,Hour\/Minute\/Second-encoded 'OffsetDatetime'+  that was encoded using the given 'OffsetFormat'+  and 'DatetimeFormat'.+-}+parser_YmdHMSz :: OffsetFormat -> DatetimeFormat -> Parser OffsetDatetime+parser_YmdHMSz offsetFormat datetimeFormat =+  OffsetDatetime+    <$> parser_YmdHMS datetimeFormat+    <*> parserOffset offsetFormat++{- | Given an 'OffsetFormat', a 'MeridiemLocale', a+  'SubsecondPrecision', and 'DatetimeFormat', construct a+  'Text' 'TB.Builder' corresponding to a Year\/Month\/Day,IMS-encoding+  of the given 'OffsetDatetime'.+-}+builder_YmdIMS_p_z :: OffsetFormat -> MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> TB.Builder+builder_YmdIMS_p_z offsetFormat meridiemLocale sp datetimeFormat (OffsetDatetime datetime offset) =+  builder_YmdIMS_p meridiemLocale sp datetimeFormat datetime+    <> " "+    <> builderOffset offsetFormat offset++{- | Given an 'OffsetFormat', a 'SubsecondPrecision',+  and a 'DatetimeFormat', construct 'Text' corresponding to+  the Year\/Month\/Day,Hour\/Minute\/Second-encoding of+  the given 'OffsetDatetime'.+-}+encode_YmdHMSz :: OffsetFormat -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> Text+encode_YmdHMSz offsetFormat sp datetimeFormat =+  LT.toStrict . TB.toLazyText . builder_YmdHMSz offsetFormat sp datetimeFormat++{- | Given an 'OffsetFormat', a 'SubsecondPrecision', and a+  'DatetimeFormat', construct a 'Text' 'TB.Builder' corresponding+  to the Day\/Month\/Year,Hour\/Minute\/Second-encoding of+  the given 'OffsetDatetime'.+-}+builder_DmyHMSz :: OffsetFormat -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> TB.Builder+builder_DmyHMSz offsetFormat sp datetimeFormat (OffsetDatetime datetime offset) =+  builder_DmyHMS sp datetimeFormat datetime+    <> builderOffset offsetFormat offset++{- | Parse a Day\/Month\/Year,Hour\/Minute\/Second-encoded 'OffsetDatetime'+  that was encoded using the given 'OffsetFormat'+  and 'DatetimeFormat'.+-}+parser_DmyHMSz :: OffsetFormat -> DatetimeFormat -> AT.Parser OffsetDatetime+parser_DmyHMSz offsetFormat datetimeFormat =+  OffsetDatetime+    <$> parser_DmyHMS datetimeFormat+    <*> parserOffset offsetFormat++{- | Given an 'OffsetFormat', a 'MeridiemLocale', a+  'SubsecondPrecision', and a 'DatetimeFormat', construct a 'Text'+  'TB.Builder' corresponding to the Day\/Month\/Year,IMS encoding+  of the given 'OffsetDatetime'.+-}+builder_DmyIMS_p_z :: OffsetFormat -> MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> TB.Builder+builder_DmyIMS_p_z offsetFormat meridiemLocale sp datetimeFormat (OffsetDatetime datetime offset) =+  builder_DmyIMS_p meridiemLocale sp datetimeFormat datetime+    <> " "+    <> builderOffset offsetFormat offset++{- | Given an 'OffsetFormat', a 'SubsecondPrecision', and a+  'DatetimeFormat', construct 'Text' corresponding to the+  Day\/Month\/Year,Hour\/Minute\/Second encoding of the given+  'OffsetDatetime'.+-}+encode_DmyHMSz :: OffsetFormat -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> Text+encode_DmyHMSz offsetFormat sp datetimeFormat =+  LT.toStrict . TB.toLazyText . builder_DmyHMSz offsetFormat sp datetimeFormat++{- | Construct a 'Text' 'TB.Builder' corresponding to the w3c-formatting+  of the given 'OffsetDatetime'.+-}+builderW3Cz :: OffsetDatetime -> TB.Builder+builderW3Cz =+  builder_YmdHMSz+    OffsetFormatColonOn+    SubsecondPrecisionAuto+    (DatetimeFormat (Just '-') (Just 'T') (Just ':'))++-- | Encode an 'Offset' to 'Text' using the given 'OffsetFormat'.+encodeOffset :: OffsetFormat -> Offset -> Text+encodeOffset fmt = LT.toStrict . TB.toLazyText . builderOffset fmt++{- | Construct a 'TB.Builder' corresponding to the given 'Offset'+  encoded using the given 'OffsetFormat'.+-}+builderOffset :: OffsetFormat -> Offset -> TB.Builder+builderOffset x = case x of+  OffsetFormatColonOff -> builderOffset_z+  OffsetFormatColonOn -> builderOffset_z1+  OffsetFormatSecondsPrecision -> builderOffset_z2+  OffsetFormatColonAuto -> builderOffset_z3++{- | Decode an 'Offset' from 'Text' that was encoded+  using the given 'OffsetFormat'.+-}+decodeOffset :: OffsetFormat -> Text -> Maybe Offset+decodeOffset fmt =+  either (const Nothing) Just . AT.parseOnly (parserOffset fmt <* AT.endOfInput)++-- | Parse an 'Offset' that was encoded using the given 'OffsetFormat'.+parserOffset :: OffsetFormat -> Parser Offset+parserOffset x = case x of+  OffsetFormatColonOff -> parserOffset_z+  OffsetFormatColonOn -> parserOffset_z1+  OffsetFormatSecondsPrecision -> parserOffset_z2+  OffsetFormatColonAuto -> parserOffset_z3++-- | True means positive, false means negative+parseSignedness :: Parser Bool+parseSignedness = do+  c <- AT.anyChar+  if c == '-'+    then pure False+    else+      if c == '+'+        then pure True+        else fail "while parsing offset, expected [+] or [-]"++parserOffset_z :: Parser Offset+parserOffset_z = do+  pos <- parseSignedness+  h <- parseFixedDigits 2+  m <- parseFixedDigits 2+  let !res = h * 60 + m+  pure . Offset $+    if pos+      then res+      else negate res++parserOffset_z1 :: Parser Offset+parserOffset_z1 = do+  pos <- parseSignedness+  h <- parseFixedDigits 2+  _ <- AT.char ':'+  m <- parseFixedDigits 2+  let !res = h * 60 + m+  pure . Offset $+    if pos+      then res+      else negate res++parserOffset_z2 :: AT.Parser Offset+parserOffset_z2 = do+  pos <- parseSignedness+  h <- parseFixedDigits 2+  _ <- AT.char ':'+  m <- parseFixedDigits 2+  _ <- AT.string ":00"+  let !res = h * 60 + m+  pure . Offset $+    if pos+      then res+      else negate res++{- | This is generous in what it accepts. If you give+  something like +04:00 as the offset, it will be+  allowed, even though it could be shorter.+-}+parserOffset_z3 :: AT.Parser Offset+parserOffset_z3 = do+  pos <- parseSignedness+  h <- parseFixedDigits 2+  mc <- AT.peekChar+  case mc of+    Just ':' -> do+      _ <- AT.anyChar -- should be a colon+      m <- parseFixedDigits 2+      let !res = h * 60 + m+      pure . Offset $+        if pos+          then res+          else negate res+    _ ->+      pure . Offset $+        if pos+          then h * 60+          else h * (-60)++builderOffset_z :: Offset -> TB.Builder+builderOffset_z (Offset i) =+  let (!a, !b) = divMod (abs i) 60+      !prefix = if signum i == (-1) then "-" else "+"+   in prefix+        <> indexTwoDigitTextBuilder a+        <> indexTwoDigitTextBuilder b++builderOffset_z1 :: Offset -> TB.Builder+builderOffset_z1 (Offset i) =+  let (!a, !b) = divMod (abs i) 60+      !prefix = if signum i == (-1) then "-" else "+"+   in prefix+        <> indexTwoDigitTextBuilder a+        <> ":"+        <> indexTwoDigitTextBuilder b++builderOffset_z2 :: Offset -> TB.Builder+builderOffset_z2 (Offset i) =+  let (!a, !b) = divMod (abs i) 60+      !prefix = if signum i == (-1) then "-" else "+"+   in prefix+        <> indexTwoDigitTextBuilder a+        <> ":"+        <> indexTwoDigitTextBuilder b+        <> ":00"++builderOffset_z3 :: Offset -> TB.Builder+builderOffset_z3 (Offset i) =+  let (!a, !b) = divMod (abs i) 60+      !prefix = if signum i == (-1) then "-" else "+"+   in if b == 0+        then+          prefix+            <> indexTwoDigitTextBuilder a+        else+          prefix+            <> indexTwoDigitTextBuilder a+            <> ":"+            <> indexTwoDigitTextBuilder b++{- | Given an 'OffsetFormat', a 'SubsecondPrecision', and a+  'DatetimeFormat', construct a 'ByteString' 'BB.Builder'+  corresponding to the Year\/Month\/Day,Hour\/Minute\/Second+  encoding of the given 'OffsetDatetime'.+-}+builderUtf8_YmdHMSz :: OffsetFormat -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> BB.Builder+builderUtf8_YmdHMSz offsetFormat sp datetimeFormat (OffsetDatetime datetime offset) =+  builderUtf8_YmdHMS sp datetimeFormat datetime+    <> builderOffsetUtf8 offsetFormat offset++{- | Parse a Year\/Month\/Day,Hour\/Minute\/Second-encoded 'OffsetDatetime'+  that was encoded using the given 'OffsetFormat' and+  'DatetimeFormat'.+-}+parserUtf8_YmdHMSz :: OffsetFormat -> DatetimeFormat -> AB.Parser OffsetDatetime+parserUtf8_YmdHMSz offsetFormat datetimeFormat =+  OffsetDatetime+    <$> parserUtf8_YmdHMS datetimeFormat+    <*> parserOffsetUtf8 offsetFormat++{- | Given an 'OffsetFormat', a 'MeridiemLocale, a 'SubsecondPrecision',+  and a 'DatetimeFormat', construct a 'ByteString' 'BB.Builder'+  corresponding to a Year\/Month\/Day,IMS-encoded 'OffsetDatetime'.+-}+builderUtf8_YmdIMS_p_z :: OffsetFormat -> MeridiemLocale ByteString -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> BB.Builder+builderUtf8_YmdIMS_p_z offsetFormat meridiemLocale sp datetimeFormat (OffsetDatetime datetime offset) =+  builderUtf8_YmdIMS_p meridiemLocale sp datetimeFormat datetime+    <> " "+    <> builderOffsetUtf8 offsetFormat offset++{- | Construct a 'ByteString' 'BB.Builder' corresponding to the W3C+  encoding of the given 'Datetime'.+-}+builderUtf8W3Cz :: OffsetDatetime -> BB.Builder+builderUtf8W3Cz =+  builderUtf8_YmdHMSz+    OffsetFormatColonOn+    SubsecondPrecisionAuto+    (DatetimeFormat (Just '-') (Just 'T') (Just ':'))++-- | Encode an 'Offset' as a 'ByteString' using the given 'OffsetFormat'.+encodeOffsetUtf8 :: OffsetFormat -> Offset -> ByteString+encodeOffsetUtf8 fmt = LB.toStrict . BB.toLazyByteString . builderOffsetUtf8 fmt++{- | Decode an 'Offset' from a 'ByteString' that was encoded using the given+  'OffsetFormat'.+-}+decodeOffsetUtf8 :: OffsetFormat -> ByteString -> Maybe Offset+decodeOffsetUtf8 fmt =+  either (const Nothing) Just . AB.parseOnly (parserOffsetUtf8 fmt)++{- | Construct a 'ByteString' 'BB.Builder' corresponding to the+  encoding of an 'Offset' using the given 'OffsetFormat'.+-}+builderOffsetUtf8 :: OffsetFormat -> Offset -> BB.Builder+builderOffsetUtf8 x = case x of+  OffsetFormatColonOff -> builderOffsetUtf8_z+  OffsetFormatColonOn -> builderOffsetUtf8_z1+  OffsetFormatSecondsPrecision -> builderOffsetUtf8_z2+  OffsetFormatColonAuto -> builderOffsetUtf8_z3++{- | Parse an 'Offset' that was encoded using the given+  'OffsetFormat'.+-}+parserOffsetUtf8 :: OffsetFormat -> AB.Parser Offset+parserOffsetUtf8 x = case x of+  OffsetFormatColonOff -> parserOffsetUtf8_z+  OffsetFormatColonOn -> parserOffsetUtf8_z1+  OffsetFormatSecondsPrecision -> parserOffsetUtf8_z2+  OffsetFormatColonAuto -> parserOffsetUtf8_z3++-- | True means positive, false means negative+parseSignednessUtf8 :: AB.Parser Bool+parseSignednessUtf8 = do+  c <- AB.anyChar+  if c == '-'+    then pure False+    else+      if c == '+'+        then pure True+        else fail "while parsing offset, expected [+] or [-]"++parserOffsetUtf8_z :: AB.Parser Offset+parserOffsetUtf8_z = do+  pos <- parseSignednessUtf8+  h <- parseFixedDigitsIntBS 2+  m <- parseFixedDigitsIntBS 2+  let !res = h * 60 + m+  pure . Offset $+    if pos+      then res+      else negate res++parserOffsetUtf8_z1 :: AB.Parser Offset+parserOffsetUtf8_z1 = do+  pos <- parseSignednessUtf8+  h <- parseFixedDigitsIntBS 2+  _ <- AB.char ':'+  m <- parseFixedDigitsIntBS 2+  let !res = h * 60 + m+  pure . Offset $+    if pos+      then res+      else negate res++parserOffsetUtf8_z2 :: AB.Parser Offset+parserOffsetUtf8_z2 = do+  pos <- parseSignednessUtf8+  h <- parseFixedDigitsIntBS 2+  _ <- AB.char ':'+  m <- parseFixedDigitsIntBS 2+  _ <- AB.string ":00"+  let !res = h * 60 + m+  pure . Offset $+    if pos+      then res+      else negate res++{- | This is generous in what it accepts. If you give+  something like +04:00 as the offset, it will be+  allowed, even though it could be shorter.+-}+parserOffsetUtf8_z3 :: AB.Parser Offset+parserOffsetUtf8_z3 = do+  pos <- parseSignednessUtf8+  h <- parseFixedDigitsIntBS 2+  mc <- AB.peekChar+  case mc of+    Just ':' -> do+      _ <- AB.anyChar -- should be a colon+      m <- parseFixedDigitsIntBS 2+      let !res = h * 60 + m+      pure . Offset $+        if pos+          then res+          else negate res+    _ ->+      pure . Offset $+        if pos+          then h * 60+          else h * (-60)++builderOffsetUtf8_z :: Offset -> BB.Builder+builderOffsetUtf8_z (Offset i) =+  let (!a, !b) = divMod (abs i) 60+      !prefix = if signum i == (-1) then "-" else "+"+   in prefix+        <> indexTwoDigitByteStringBuilder a+        <> indexTwoDigitByteStringBuilder b++builderOffsetUtf8_z1 :: Offset -> BB.Builder+builderOffsetUtf8_z1 (Offset i) =+  let (!a, !b) = divMod (abs i) 60+      !prefix = if signum i == (-1) then "-" else "+"+   in prefix+        <> indexTwoDigitByteStringBuilder a+        <> ":"+        <> indexTwoDigitByteStringBuilder b++builderOffsetUtf8_z2 :: Offset -> BB.Builder+builderOffsetUtf8_z2 (Offset i) =+  let (!a, !b) = divMod (abs i) 60+      !prefix = if signum i == (-1) then "-" else "+"+   in prefix+        <> indexTwoDigitByteStringBuilder a+        <> ":"+        <> indexTwoDigitByteStringBuilder b+        <> ":00"++builderOffsetUtf8_z3 :: Offset -> BB.Builder+builderOffsetUtf8_z3 (Offset i) =+  let (!a, !b) = divMod (abs i) 60+      !prefix = if signum i == (-1) then "-" else "+"+   in if b == 0+        then+          prefix+            <> indexTwoDigitByteStringBuilder a+        else+          prefix+            <> indexTwoDigitByteStringBuilder a+            <> ":"+            <> indexTwoDigitByteStringBuilder b++-- Zepto parsers++{- | Parse a 'Datetime' that was encoded using the+  given 'DatetimeFormat'.+-}+zeptoUtf8_YmdHMS :: DatetimeFormat -> Z.Parser Datetime+zeptoUtf8_YmdHMS (DatetimeFormat mdateSep msep' mtimeSep) = do+  date <- zeptoUtf8_Ymd mdateSep+  let msep = BC.singleton <$> msep'+  traverse_ Z.string msep+  time <- zeptoUtf8_HMS mtimeSep+  pure (Datetime date time)++zeptoCountZeroes :: Z.Parser Int+zeptoCountZeroes = do+  bs <- Z.takeWhile (0x30 ==)+  pure $! BC.length bs++{- | Parse a 'Date' that was encoded using+  the given separator.+-}+zeptoUtf8_Ymd :: Maybe Char -> Z.Parser Date+zeptoUtf8_Ymd msep' = do+  y <- zeptoFixedDigitsIntBS 4+  let msep = BC.singleton <$> msep'+  traverse_ Z.string msep+  m <- zeptoFixedDigitsIntBS 2+  when (m < 1 || m > 12) (fail "month must be between 1 and 12")+  traverse_ Z.string msep+  d <- zeptoFixedDigitsIntBS 2+  when (d < 1 || d > 31) (fail "day must be between 1 and 31")+  pure (Date (Year y) (Month $ m - 1) (DayOfMonth d))++{- | Parse a 'TimeOfDay' that was encoded using+  the given separator.+-}+zeptoUtf8_HMS :: Maybe Char -> Z.Parser TimeOfDay+zeptoUtf8_HMS msep' = do+  h <- zeptoFixedDigitsIntBS 2+  when (h > 23) (fail "hour must be between 0 and 23")+  let msep = BC.singleton <$> msep'+  traverse_ Z.string msep+  m <- zeptoFixedDigitsIntBS 2+  when (m > 59) (fail "minute must be between 0 and 59")+  traverse_ Z.string msep+  ns <- zeptoSecondsAndNanosecondsUtf8+  pure (TimeOfDay h m ns)++zeptoFixedDigitsIntBS :: Int -> Z.Parser Int+zeptoFixedDigitsIntBS n = do+  t <- Z.take n+  case BC.readInt t of+    Nothing -> fail "datetime decoding could not parse integral bytestring (a)"+    Just (i, r) ->+      if BC.null r+        then pure i+        else fail "datetime decoding could not parse integral bytestring (b)"++zeptoSecondsAndNanosecondsUtf8 :: Z.Parser Int64+zeptoSecondsAndNanosecondsUtf8 = do+  s' <- zeptoFixedDigitsIntBS 2+  let s = fromIntegral s' :: Int64+  when (s > 60) (fail "seconds must be between 0 and 60")+  nanoseconds <-+    ( do+        _ <- Z.string "."+        numberOfZeroes <- zeptoCountZeroes+        x <- zdecimal+        let totalDigits = countDigits x + numberOfZeroes+            result =+              if totalDigits == 9+                then x+                else+                  if totalDigits < 9+                    then x * raiseTenTo (9 - totalDigits)+                    else quot x (raiseTenTo (totalDigits - 9))+        pure (fromIntegral result)+    )+      <|> pure 0+  pure (s * 1000000000 + nanoseconds)++zdecimal :: Z.Parser Int64+zdecimal = do+  digits <- Z.takeWhile wordIsDigit+  case BC.readInt digits of+    Nothing -> fail "somehow this didn't work"+    Just (i, _) -> pure $! fromIntegral i++wordIsDigit :: Word8 -> Bool+wordIsDigit a = 0x30 <= a && a <= 0x39++-- | The 'Month' of January.+january :: Month+january = Month 0++-- | The 'Month' of February.+february :: Month+february = Month 1++-- | The 'Month' of March.+march :: Month+march = Month 2++-- | The 'Month' of April.+april :: Month+april = Month 3++-- | The 'Month' of May.+may :: Month+may = Month 4++-- | The 'Month' of June.+june :: Month+june = Month 5++-- | The 'Month' of July.+july :: Month+july = Month 6++-- | The 'Month' of August.+august :: Month+august = Month 7++-- | The 'Month' of September.+september :: Month+september = Month 8++-- | The 'Month' of October.+october :: Month+october = Month 9++-- | The 'Month' of November.+november :: Month+november = Month 10++-- | The 'Month' of December.+december :: Month+december = Month 11++-- | The 'DayOfWeek' Sunday.+sunday :: DayOfWeek+sunday = DayOfWeek 0++-- | The 'DayOfWeek' Monday.+monday :: DayOfWeek+monday = DayOfWeek 1++-- | The 'DayOfWeek' Tuesday.+tuesday :: DayOfWeek+tuesday = DayOfWeek 2++-- | The 'DayOfWeek' Wednesday.+wednesday :: DayOfWeek+wednesday = DayOfWeek 3++-- | The 'DayOfWeek' Thursday.+thursday :: DayOfWeek+thursday = DayOfWeek 4++-- | The 'DayOfWeek' Friday.+friday :: DayOfWeek+friday = DayOfWeek 5++-- | The 'DayOfWeek' Saturday.+saturday :: DayOfWeek+saturday = DayOfWeek 6++countDigits :: (Integral a) => a -> Int+countDigits v0+  | fromIntegral v64 == v0 = go 1 v64+  | otherwise = goBig 1 (fromIntegral v0)+ where+  v64 = fromIntegral v0+  goBig !k (v :: Integer)+    | v > big = goBig (k + 19) (v `quot` big)+    | otherwise = go k (fromIntegral v)+  big = 10000000000000000000+  go !k (v :: Word64)+    | v < 10 = k+    | v < 100 = k + 1+    | v < 1000 = k + 2+    | v < 1000000000000 =+        k+          + if v < 100000000+            then+              if v < 1000000+                then+                  if v < 10000+                    then 3+                    else 4 + fin v 100000+                else 6 + fin v 10000000+            else+              if v < 10000000000+                then 8 + fin v 1000000000+                else 10 + fin v 100000000000+    | otherwise = go (k + 12) (v `quot` 1000000000000)+  fin v n = if v >= n then 1 else 0++clip :: (Ord t) => t -> t -> t -> t+clip a _ x | x < a = a+clip _ b x | x > b = b+clip _ _ x = x++parseFixedDigits :: Int -> AT.Parser Int+parseFixedDigits n = do+  t <- AT.take n+  case Text.decimal t of+    Left err -> fail err+    Right (i, r) ->+      if Text.null r+        then pure i+        else fail "datetime decoding could not parse integral text"++parseFixedDigitsIntBS :: Int -> AB.Parser Int+parseFixedDigitsIntBS n = do+  t <- AB.take n+  case BC.readInt t of+    Nothing -> fail "datetime decoding could not parse integral bytestring (a)"+    Just (i, r) ->+      if BC.null r+        then pure i+        else fail "datetime decoding could not parse integral bytestring (b)"++-- Only provide positive numbers to this function.+indexTwoDigitTextBuilder :: Int -> TB.Builder+indexTwoDigitTextBuilder i =+  if i < 100+    then Vector.unsafeIndex twoDigitTextBuilder (fromIntegral i)+    else TB.decimal i++-- | Only provide positive numbers to this function.+indexTwoDigitByteStringBuilder :: Int -> BB.Builder+indexTwoDigitByteStringBuilder i =+  if i < 100+    then Vector.unsafeIndex twoDigitByteStringBuilder (fromIntegral i)+    else BB.intDec i++twoDigitByteStringBuilder :: Vector BB.Builder+twoDigitByteStringBuilder =+  Vector.fromList $+    map (BB.byteString . BC.pack) twoDigitStrings+{-# NOINLINE twoDigitByteStringBuilder #-}++twoDigitTextBuilder :: Vector TB.Builder+twoDigitTextBuilder =+  Vector.fromList $+    map (TB.fromText . Text.pack) twoDigitStrings+{-# NOINLINE twoDigitTextBuilder #-}++twoDigitStrings :: [String]+twoDigitStrings =+  [ "00"+  , "01"+  , "02"+  , "03"+  , "04"+  , "05"+  , "06"+  , "07"+  , "08"+  , "09"+  , "10"+  , "11"+  , "12"+  , "13"+  , "14"+  , "15"+  , "16"+  , "17"+  , "18"+  , "19"+  , "20"+  , "21"+  , "22"+  , "23"+  , "24"+  , "25"+  , "26"+  , "27"+  , "28"+  , "29"+  , "30"+  , "31"+  , "32"+  , "33"+  , "34"+  , "35"+  , "36"+  , "37"+  , "38"+  , "39"+  , "40"+  , "41"+  , "42"+  , "43"+  , "44"+  , "45"+  , "46"+  , "47"+  , "48"+  , "49"+  , "50"+  , "51"+  , "52"+  , "53"+  , "54"+  , "55"+  , "56"+  , "57"+  , "58"+  , "59"+  , "60"+  , "61"+  , "62"+  , "63"+  , "64"+  , "65"+  , "66"+  , "67"+  , "68"+  , "69"+  , "70"+  , "71"+  , "72"+  , "73"+  , "74"+  , "75"+  , "76"+  , "77"+  , "78"+  , "79"+  , "80"+  , "81"+  , "82"+  , "83"+  , "84"+  , "85"+  , "86"+  , "87"+  , "88"+  , "89"+  , "90"+  , "91"+  , "92"+  , "93"+  , "94"+  , "95"+  , "96"+  , "97"+  , "98"+  , "99"+  ]++raiseTenTo :: Int -> Int64+raiseTenTo i =+  if i > 15+    then 10 ^ i+    else UVector.unsafeIndex tenRaisedToSmallPowers i++tenRaisedToSmallPowers :: UVector.Vector Int64+tenRaisedToSmallPowers = UVector.fromList $ map (10 ^) [0 :: Int .. 15]++yearToZeroPaddedDigit :: Year -> TB.Builder+yearToZeroPaddedDigit (Year x)+  | x < 10 = "000" <> TB.decimal x+  | x < 100 = "00" <> TB.decimal x+  | x < 1000 = "0" <> TB.decimal x+  | otherwise = TB.decimal x++monthToZeroPaddedDigit :: Month -> TB.Builder+monthToZeroPaddedDigit (Month x) =+  indexTwoDigitTextBuilder (x + 1)++zeroPadDayOfMonth :: DayOfMonth -> TB.Builder+zeroPadDayOfMonth (DayOfMonth d) = indexTwoDigitTextBuilder d++yearToZeroPaddedDigitBS :: Year -> BB.Builder+yearToZeroPaddedDigitBS (Year x)+  | x < 10 = "000" <> BB.intDec x+  | x < 100 = "00" <> BB.intDec x+  | x < 1000 = "0" <> BB.intDec x+  | otherwise = BB.intDec x++monthToZeroPaddedDigitBS :: Month -> BB.Builder+monthToZeroPaddedDigitBS (Month x) =+  indexTwoDigitByteStringBuilder (x + 1)++zeroPadDayOfMonthBS :: DayOfMonth -> BB.Builder+zeroPadDayOfMonthBS (DayOfMonth d) = indexTwoDigitByteStringBuilder d++-- | Is the given 'Time' within the 'TimeInterval'?+within :: Time -> TimeInterval -> Bool+t `within` (TimeInterval t0 t1) = t >= t0 && t <= t1++-- | Convert a 'TimeInterval' to a 'Timespan'. This is equivalent to 'width'.+timeIntervalToTimespan :: TimeInterval -> Timespan+timeIntervalToTimespan = width++{- | The 'TimeInterval' that covers the entire range of 'Time's that Chronos supports.++  prop> \(t :: Time) -> within t whole+-}+whole :: TimeInterval+whole = TimeInterval minBound maxBound++-- | The singleton (degenerate) 'TimeInterval'.+singleton :: Time -> TimeInterval+singleton x = TimeInterval x x++-- | Get the lower bound of the 'TimeInterval'.+lowerBound :: TimeInterval -> Time+lowerBound (TimeInterval t0 _) = t0++-- | Get the upper bound of the 'TimeInterval'.+upperBound :: TimeInterval -> Time+upperBound (TimeInterval _ t1) = t1++-- | The width of the 'TimeInterval'. This is equivalent to 'timeIntervalToTimespan'.+width :: TimeInterval -> Timespan+width (TimeInterval x y) = difference y x++{- | A smart constructor for 'TimeInterval'. In general, you should prefer using this+  over the 'TimeInterval' constructor, since it maintains the invariant that+  @'lowerBound' interval '<=' 'upperBound' interval@.+-}+timeIntervalBuilder :: Time -> Time -> TimeInterval+timeIntervalBuilder x y = case compare x y of+  GT -> TimeInterval y x+  _ -> TimeInterval x y++infix 3 ...++-- | An infix 'timeIntervalBuilder'.+(...) :: Time -> Time -> TimeInterval+(...) = timeIntervalBuilder++{- | A day represented as the modified Julian date, the number of days+  since midnight on November 17, 1858.+-}+newtype Day = Day {getDay :: Int}+  deriving (Show, Read, Eq, Ord, Hashable, Enum, ToJSON, FromJSON, Storable, Prim, NFData)++instance Torsor Day Int where+  add i (Day d) = Day (d + i)+  difference (Day a) (Day b) = a - b++-- | a lens for accessing the `getDay` field.+_getDay :: (Functor f) => (Int -> f Int) -> Day -> f Day+_getDay f = fmap Day . f . getDay++-- | The day of the week.+newtype DayOfWeek = DayOfWeek {getDayOfWeek :: Int}+  deriving (Show, Read, Eq, Ord, Hashable, NFData)++-- | a lens for accessing the `getDayOfWeek` field.+_getDayOfWeek :: (Functor f) => (Int -> f Int) -> DayOfWeek -> f DayOfWeek+_getDayOfWeek f = fmap DayOfWeek . f . getDayOfWeek++-- | The day of the month.+newtype DayOfMonth = DayOfMonth {getDayOfMonth :: Int}+  deriving (Show, Read, Eq, Ord, Prim, Enum, NFData)++-- | a lens for accessing the `getDayOfMonth` field.+_getDayOfMonth :: (Functor f) => (Int -> f Int) -> DayOfMonth -> f DayOfMonth+_getDayOfMonth f = fmap DayOfMonth . f . getDayOfMonth++-- | The day of the year.+newtype DayOfYear = DayOfYear {getDayOfYear :: Int}+  deriving (Show, Read, Eq, Ord, Prim, NFData)++-- | a lens for accessing the `getDayOfYear` field.+_getDayOfYear :: (Functor f) => (Int -> f Int) -> DayOfYear -> f DayOfYear+_getDayOfYear f = fmap DayOfYear . f . getDayOfYear++-- | The month of the year.+newtype Month = Month {getMonth :: Int}+  deriving (Show, Read, Eq, Ord, Prim, NFData)++-- | a lens for accessing the `getMonth` field.+_getMonth :: (Functor f) => (Int -> f Int) -> Month -> f Month+_getMonth f = fmap Month . f . getMonth++instance Enum Month where+  fromEnum = getMonth+  toEnum = Month+  succ (Month x) =+    if x < 11+      then Month (x + 1)+      else error "Enum.succ{Month}: tried to take succ of December"+  pred (Month x) =+    if x > 0+      then Month (x - 1)+      else error "Enum.pred{Month}: tried to take pred of January"+  enumFrom x = enumFromTo x (Month 11)++-- | 'Month' starts at 0 and ends at 11 (January to December)+instance Bounded Month where+  minBound = Month 0+  maxBound = Month 11++{- | The number of years elapsed since the beginning+  of the Common Era.+-}+newtype Year = Year {getYear :: Int}+  deriving (Show, Read, Eq, Ord, NFData)++-- | a lens for accessing the `getYear` field.+_getYear :: (Functor f) => (Int -> f Int) -> Year -> f Year+_getYear f = fmap Year . f . getYear++-- | A <https://en.wikipedia.org/wiki/UTC_offset UTC offset> in minutes.+newtype Offset = Offset {getOffset :: Int}+  deriving (Show, Read, Eq, Ord, Enum, NFData)++-- | a lens for accessing the `getOffset` field.+_getOffset :: (Functor f) => (Int -> f Int) -> Offset -> f Offset+_getOffset f = fmap Offset . f . getOffset++-- | POSIX time with nanosecond resolution.+newtype Time = Time {getTime :: Int64}+  deriving (FromJSON, ToJSON, Hashable, Eq, Ord, Show, Read, Storable, Prim, Bounded, NFData)++-- | a lens for accessing the `getTime` field.+_getTime :: (Functor f) => (Int64 -> f Int64) -> Time -> f Time+_getTime f = fmap Time . f . getTime++{- | Match a 'DayOfWeek'. By `match`, we mean that a 'DayOfWeekMatch'+  is a mapping from the integer value of a 'DayOfWeek' to some value+  of type @a@. You should construct a 'DayOfWeekMatch' with+  'buildDayOfWeekMatch', and match it using 'caseDayOfWeek'.+-}+newtype DayOfWeekMatch a = DayOfWeekMatch {getDayOfWeekMatch :: Vector a}+  deriving (NFData)++{- | Match a 'Month'. By `match`, we mean that a 'MonthMatch' is+  a mapping from the integer value of a 'Month' to some value of+  type @a@. You should construct a 'MonthMatch' with+  'buildMonthMatch', and match it using 'caseMonth'.+-}+newtype MonthMatch a = MonthMatch {getMonthMatch :: Vector a}+  deriving (NFData)++{- | Like 'MonthMatch', but the matched value can have an instance of+  'UVector.Unbox'.+-}+newtype UnboxedMonthMatch a = UnboxedMonthMatch {getUnboxedMonthMatch :: UVector.Vector a}+  deriving (NFData)++{- | A timespan. This is represented internally as a number+  of nanoseconds.+-}+newtype Timespan = Timespan {getTimespan :: Int64}+  deriving (Show, Read, Eq, Ord, ToJSON, FromJSON, Additive, NFData)++-- | a lens for accessing the `getTimespan` field.+_getTimespan :: (Functor f) => (Int64 -> f Int64) -> Timespan -> f Timespan+_getTimespan f = fmap Timespan . f . getTimespan++instance Semigroup Timespan where+  (Timespan a) <> (Timespan b) = Timespan (a + b)++instance Monoid Timespan where+  mempty = Timespan 0+  mappend = (SG.<>)++instance Torsor Time Timespan where+  add (Timespan ts) (Time t) = Time (t + ts)+  difference (Time t) (Time s) = Timespan (t - s)++instance Scaling Timespan Int64 where+  scale i (Timespan ts) = Timespan (i * ts)++instance Torsor Offset Int where+  add i (Offset x) = Offset (x + i)+  difference (Offset x) (Offset y) = x - y++-- | The precision used when encoding seconds to a human-readable format.+data SubsecondPrecision+  = -- | Rounds to second, millisecond, microsecond, or nanosecond+    SubsecondPrecisionAuto+  | -- | Specify number of places after decimal+    SubsecondPrecisionFixed {-# UNPACK #-} !Int+  deriving (Eq, Ord, Show, Read)++instance NFData SubsecondPrecision where+  rnf (SubsecondPrecisionAuto) = ()+  rnf (SubsecondPrecisionFixed a) = a `deepseq` ()++-- | A date as represented by the Gregorian calendar.+data Date = Date+  { dateYear :: {-# UNPACK #-} !Year+  , dateMonth :: {-# UNPACK #-} !Month+  , dateDay :: {-# UNPACK #-} !DayOfMonth+  }+  deriving (Show, Read, Eq, Ord)++instance NFData Date where+  rnf (Date y m d) = y `deepseq` m `deepseq` d `deepseq` ()++-- | a lens for accessing the `dateYear` field.+_dateYear :: (Functor f) => (Year -> f Year) -> Date -> f Date+_dateYear f date = fmap (\y -> date {dateYear = y}) . f . dateYear $ date++-- | a lens for accessing the `dateMonth` field.+_dateMonth :: (Functor f) => (Month -> f Month) -> Date -> f Date+_dateMonth f date = fmap (\m -> date {dateMonth = m}) . f . dateMonth $ date++-- | a lens for accessing the `dateDay` field.+_dateDay :: (Functor f) => (DayOfMonth -> f DayOfMonth) -> Date -> f Date+_dateDay f date = fmap (\d -> date {dateDay = d}) . f . dateDay $ date++{- | An 'OrdinalDate' is a 'Year' and the number of days elapsed+  since the 'Year' began.+-}+data OrdinalDate = OrdinalDate+  { ordinalDateYear :: {-# UNPACK #-} !Year+  , ordinalDateDayOfYear :: {-# UNPACK #-} !DayOfYear+  }+  deriving (Show, Read, Eq, Ord)++instance NFData OrdinalDate where+  rnf (OrdinalDate y d) = y `deepseq` d `deepseq` ()++-- | a lens for accessing the `ordinalDateYear` field.+_ordinalDateYear :: (Functor f) => (Year -> f Year) -> OrdinalDate -> f OrdinalDate+_ordinalDateYear f date = fmap (\y -> date {ordinalDateYear = y}) . f . ordinalDateYear $ date++-- | a lens for accessing the `ordinalDateDayOfYear` field.+_ordinalDateDayOfYear :: (Functor f) => (DayOfYear -> f DayOfYear) -> OrdinalDate -> f OrdinalDate+_ordinalDateDayOfYear f date =+  fmap (\d -> date {ordinalDateDayOfYear = d}) . f . ordinalDateDayOfYear $ date++{- | A month and the day of the month. This does not actually represent+  a specific date, since this recurs every year.+-}+data MonthDate = MonthDate+  { monthDateMonth :: {-# UNPACK #-} !Month+  , monthDateDay :: {-# UNPACK #-} !DayOfMonth+  }+  deriving (Show, Read, Eq, Ord)++instance NFData MonthDate where+  rnf (MonthDate m d) = m `deepseq` d `deepseq` ()++-- | a lens for accessing the `monthDateMonth` field.+_monthDateMonth :: (Functor f) => (Month -> f Month) -> MonthDate -> f MonthDate+_monthDateMonth f date = fmap (\m -> date {monthDateMonth = m}) . f . monthDateMonth $ date++-- | a lens for accessing the `monthDateDay` field.+_monthDateDay :: (Functor f) => (DayOfMonth -> f DayOfMonth) -> MonthDate -> f MonthDate+_monthDateDay f date = fmap (\d -> date {monthDateDay = d}) . f . monthDateDay $ date++{- | A 'Date' as represented by the Gregorian calendar+  and a 'TimeOfDay'.+  While the 'ToJSON' instance encodes with a hyphen separator, the+  'FromJSON' instance allows any non-digit character to act as+  separator, using the lenient parser.+-}+data Datetime = Datetime+  { datetimeDate :: {-# UNPACK #-} !Date+  , datetimeTime :: {-# UNPACK #-} !TimeOfDay+  }+  deriving (Show, Read, Eq, Ord)++instance NFData Datetime where+  rnf (Datetime d t) = d `deepseq` t `deepseq` ()++-- | a lens for accessing the `datetimeDate` field.+_datetimeDate :: (Functor f) => (Date -> f Date) -> Datetime -> f Datetime+_datetimeDate f date = fmap (\y -> date {datetimeDate = y}) . f . datetimeDate $ date++-- | a lens for accessing the `datetimeTime` field.+_datetimeTime :: (Functor f) => (TimeOfDay -> f TimeOfDay) -> Datetime -> f Datetime+_datetimeTime f date = fmap (\t -> date {datetimeTime = t}) . f . datetimeTime $ date++-- | A 'Datetime' with a time zone 'Offset'.+data OffsetDatetime = OffsetDatetime+  { offsetDatetimeDatetime :: {-# UNPACK #-} !Datetime+  , offsetDatetimeOffset :: {-# UNPACK #-} !Offset+  }+  deriving (Show, Read, Eq, Ord)++instance NFData OffsetDatetime where+  rnf (OffsetDatetime dt o) = dt `deepseq` o `deepseq` ()++-- | a lens for accessing the `offsetDatetimeDatetime` field.+_offsetDatetimeDatetime ::+  (Functor f) => (Datetime -> f Datetime) -> OffsetDatetime -> f OffsetDatetime+_offsetDatetimeDatetime f date =+  fmap (\d -> date {offsetDatetimeDatetime = d}) . f . offsetDatetimeDatetime $ date++-- | a lens for accessing the `offsetDatetimeOffset` field.+_offsetDatetimeOffset ::+  (Functor f) => (Offset -> f Offset) -> OffsetDatetime -> f OffsetDatetime+_offsetDatetimeOffset f date =+  fmap (\y -> date {offsetDatetimeOffset = y}) . f . offsetDatetimeOffset $ date++-- | A time of day with nanosecond resolution.+data TimeOfDay = TimeOfDay+  { timeOfDayHour :: {-# UNPACK #-} !Int+  , timeOfDayMinute :: {-# UNPACK #-} !Int+  , timeOfDayNanoseconds :: {-# UNPACK #-} !Int64+  }+  deriving (Show, Read, Eq, Ord)++instance NFData TimeOfDay where+  rnf (TimeOfDay h m s) = h `deepseq` m `deepseq` s `deepseq` ()++-- | a lens for accessing the `timeOfDayHour` field.+_timeOfDayHour ::+  (Functor f) => (Int -> f Int) -> TimeOfDay -> f TimeOfDay+_timeOfDayHour f time =+  fmap (\h -> time {timeOfDayHour = h}) . f . timeOfDayHour $ time++-- | a lens for accessing the `timeOfDayMinute` field.+_timeOfDayMinute ::+  (Functor f) => (Int -> f Int) -> TimeOfDay -> f TimeOfDay+_timeOfDayMinute f time =+  fmap (\m -> time {timeOfDayMinute = m}) . f . timeOfDayMinute $ time++-- | a lens for accessing the `timeOfDayNanoseconds` field.+_timeOfDayNanoseconds ::+  (Functor f) => (Int64 -> f Int64) -> TimeOfDay -> f TimeOfDay+_timeOfDayNanoseconds f time =+  fmap (\n -> time {timeOfDayNanoseconds = n}) . f . timeOfDayNanoseconds $ time++{- | The format of a 'Datetime'. In particular+  this provides separators for parts of the 'Datetime'+  and nothing else.+-}+data DatetimeFormat = DatetimeFormat+  { datetimeFormatDateSeparator :: !(Maybe Char)+  -- ^ Separator in the date+  , datetimeFormatSeparator :: !(Maybe Char)+  -- ^ Separator between date and time+  , datetimeFormatTimeSeparator :: !(Maybe Char)+  -- ^ Separator in the time+  }+  deriving (Show, Read, Eq, Ord)++instance NFData DatetimeFormat where+  rnf (DatetimeFormat s1 s2 s3) = s1 `deepseq` s2 `deepseq` s3 `deepseq` ()++-- | Formatting settings for a timezone offset.+data OffsetFormat+  = -- | @%z@ (e.g., -0400)+    OffsetFormatColonOff+  | -- | @%:z@ (e.g., -04:00)+    OffsetFormatColonOn+  | -- | @%::z@ (e.g., -04:00:00)+    OffsetFormatSecondsPrecision+  | -- | @%:::z@ (e.g., -04, +05:30)+    OffsetFormatColonAuto+  deriving (Show, Read, Eq, Ord, Enum, Bounded, Generic)++instance NFData OffsetFormat where+  rnf !_ = ()++{- | Locale-specific formatting for weekdays and months. The+  type variable will likely be instantiated to @Text@+  or @ByteString@.+-}+data DatetimeLocale a = DatetimeLocale+  { datetimeLocaleDaysOfWeekFull :: !(DayOfWeekMatch a)+  -- ^ full weekdays starting with Sunday, 7 elements+  , datetimeLocaleDaysOfWeekAbbreviated :: !(DayOfWeekMatch a)+  -- ^ abbreviated weekdays starting with Sunday, 7 elements+  , datetimeLocaleMonthsFull :: !(MonthMatch a)+  -- ^ full months starting with January, 12 elements+  , datetimeLocaleMonthsAbbreviated :: !(MonthMatch a)+  -- ^ abbreviated months starting with January, 12 elements+  }++instance (NFData a) => NFData (DatetimeLocale a) where+  rnf (DatetimeLocale d1 d2 m1 m2) =+    d1 `deepseq` d2 `deepseq` m1 `deepseq` m2 `deepseq` ()++{- | A TimeInterval represents a start and end time.+  It can sometimes be more ergonomic than the 'Torsor' API when+  you only care about whether or not a 'Time' is within a certain range.++  To construct a 'TimeInterval', it is best to use 'timeIntervalBuilder',+  which maintains the invariant that @'lowerBound' interval '<=' 'upperBound' interval@+  (all functions that act on 'TimeInterval's assume this invariant).+-}+data TimeInterval = TimeInterval {-# UNPACK #-} !Time {-# UNPACK #-} !Time+  deriving (Read, Show, Eq, Ord, Bounded)++instance NFData TimeInterval where+  rnf (TimeInterval t1 t2) = t1 `deepseq` t2 `deepseq` ()++-- | Locale-specific formatting for AM and PM.+data MeridiemLocale a = MeridiemLocale+  { meridiemLocaleAm :: !a+  , meridiemLocalePm :: !a+  }+  deriving (Read, Show, Eq, Ord)++instance (NFData a) => NFData (MeridiemLocale a) where+  rnf (MeridiemLocale am pm) = am `deepseq` pm `deepseq` ()++newtype instance UVector.MVector s Month = MV_Month (PVector.MVector s Month)+newtype instance UVector.Vector Month = V_Month (PVector.Vector Month)++instance UVector.Unbox Month++instance MGVector.MVector UVector.MVector Month where+  {-# INLINE basicLength #-}+  {-# INLINE basicUnsafeSlice #-}+  {-# INLINE basicOverlaps #-}+  {-# INLINE basicUnsafeNew #-}+  {-# INLINE basicInitialize #-}+  {-# INLINE basicUnsafeReplicate #-}+  {-# INLINE basicUnsafeRead #-}+  {-# INLINE basicUnsafeWrite #-}+  {-# INLINE basicClear #-}+  {-# INLINE basicSet #-}+  {-# INLINE basicUnsafeCopy #-}+  {-# INLINE basicUnsafeGrow #-}+  basicLength (MV_Month v) = MGVector.basicLength v+  basicUnsafeSlice i n (MV_Month v) = MV_Month $ MGVector.basicUnsafeSlice i n v+  basicOverlaps (MV_Month v1) (MV_Month v2) = MGVector.basicOverlaps v1 v2+  basicUnsafeNew n = MV_Month `liftM` MGVector.basicUnsafeNew n+  basicInitialize (MV_Month v) = MGVector.basicInitialize v+  basicUnsafeReplicate n x = MV_Month `liftM` MGVector.basicUnsafeReplicate n x+  basicUnsafeRead (MV_Month v) i = MGVector.basicUnsafeRead v i+  basicUnsafeWrite (MV_Month v) i x = MGVector.basicUnsafeWrite v i x+  basicClear (MV_Month v) = MGVector.basicClear v+  basicSet (MV_Month v) x = MGVector.basicSet v x+  basicUnsafeCopy (MV_Month v1) (MV_Month v2) = MGVector.basicUnsafeCopy v1 v2+  basicUnsafeMove (MV_Month v1) (MV_Month v2) = MGVector.basicUnsafeMove v1 v2+  basicUnsafeGrow (MV_Month v) n = MV_Month `liftM` MGVector.basicUnsafeGrow v n++instance GVector.Vector UVector.Vector Month where+  {-# INLINE basicUnsafeFreeze #-}+  {-# INLINE basicUnsafeThaw #-}+  {-# INLINE basicLength #-}+  {-# INLINE basicUnsafeSlice #-}+  {-# INLINE basicUnsafeIndexM #-}+  {-# INLINE elemseq #-}+  basicUnsafeFreeze (MV_Month v) = V_Month `liftM` GVector.basicUnsafeFreeze v+  basicUnsafeThaw (V_Month v) = MV_Month `liftM` GVector.basicUnsafeThaw v+  basicLength (V_Month v) = GVector.basicLength v+  basicUnsafeSlice i n (V_Month v) = V_Month $ GVector.basicUnsafeSlice i n v+  basicUnsafeIndexM (V_Month v) i = GVector.basicUnsafeIndexM v i+  basicUnsafeCopy (MV_Month mv) (V_Month v) = GVector.basicUnsafeCopy mv v+  elemseq _ = seq++newtype instance UVector.MVector s DayOfMonth = MV_DayOfMonth (PVector.MVector s DayOfMonth)+newtype instance UVector.Vector DayOfMonth = V_DayOfMonth (PVector.Vector DayOfMonth)++instance UVector.Unbox DayOfMonth++instance MGVector.MVector UVector.MVector DayOfMonth where+  {-# INLINE basicLength #-}+  {-# INLINE basicUnsafeSlice #-}+  {-# INLINE basicOverlaps #-}+  {-# INLINE basicUnsafeNew #-}+  {-# INLINE basicInitialize #-}+  {-# INLINE basicUnsafeReplicate #-}+  {-# INLINE basicUnsafeRead #-}+  {-# INLINE basicUnsafeWrite #-}+  {-# INLINE basicClear #-}+  {-# INLINE basicSet #-}+  {-# INLINE basicUnsafeCopy #-}+  {-# INLINE basicUnsafeGrow #-}+  basicLength (MV_DayOfMonth v) = MGVector.basicLength v+  basicUnsafeSlice i n (MV_DayOfMonth v) = MV_DayOfMonth $ MGVector.basicUnsafeSlice i n v+  basicOverlaps (MV_DayOfMonth v1) (MV_DayOfMonth v2) = MGVector.basicOverlaps v1 v2+  basicUnsafeNew n = MV_DayOfMonth `liftM` MGVector.basicUnsafeNew n+  basicInitialize (MV_DayOfMonth v) = MGVector.basicInitialize v+  basicUnsafeReplicate n x = MV_DayOfMonth `liftM` MGVector.basicUnsafeReplicate n x+  basicUnsafeRead (MV_DayOfMonth v) i = MGVector.basicUnsafeRead v i+  basicUnsafeWrite (MV_DayOfMonth v) i x = MGVector.basicUnsafeWrite v i x+  basicClear (MV_DayOfMonth v) = MGVector.basicClear v+  basicSet (MV_DayOfMonth v) x = MGVector.basicSet v x+  basicUnsafeCopy (MV_DayOfMonth v1) (MV_DayOfMonth v2) = MGVector.basicUnsafeCopy v1 v2+  basicUnsafeMove (MV_DayOfMonth v1) (MV_DayOfMonth v2) = MGVector.basicUnsafeMove v1 v2+  basicUnsafeGrow (MV_DayOfMonth v) n = MV_DayOfMonth `liftM` MGVector.basicUnsafeGrow v n++instance GVector.Vector UVector.Vector DayOfMonth where+  {-# INLINE basicUnsafeFreeze #-}+  {-# INLINE basicUnsafeThaw #-}+  {-# INLINE basicLength #-}+  {-# INLINE basicUnsafeSlice #-}+  {-# INLINE basicUnsafeIndexM #-}+  {-# INLINE elemseq #-}+  basicUnsafeFreeze (MV_DayOfMonth v) = V_DayOfMonth `liftM` GVector.basicUnsafeFreeze v+  basicUnsafeThaw (V_DayOfMonth v) = MV_DayOfMonth `liftM` GVector.basicUnsafeThaw v+  basicLength (V_DayOfMonth v) = GVector.basicLength v+  basicUnsafeSlice i n (V_DayOfMonth v) = V_DayOfMonth $ GVector.basicUnsafeSlice i n v+  basicUnsafeIndexM (V_DayOfMonth v) i = GVector.basicUnsafeIndexM v i+  basicUnsafeCopy (MV_DayOfMonth mv) (V_DayOfMonth v) = GVector.basicUnsafeCopy mv v+  elemseq _ = seq++------------------------+-- The Torsor and Enum instances for Date and OrdinalDate+-- are both bad. This only causes problems for dates+-- at least a million years in the future. Some of this+-- badness is caused by pragmatism, and some of it is caused by+-- my own laziness.+--+-- The badness that comes from pragmatism:+--   - Int technically is not a good delta for Date. Date+--     has too many inhabitants. If we subtract the lowest+--     Date from the highest Date, we get something too+--     big to fit in a machine integer.+--   - There is no good way to write fromEnum or toEnum for+--     Date. Again, Date has more inhabitants than Int, so+--     it simply cannot be done.+-- The badness that comes from laziness:+--   - Technically, we should still be able to add deltas to+--     Dates that do not fit in machine integers. We should+--     also be able to correctly subtract Dates to cannot+--     fit in machine integers.+--   - For similar reasons, the Enum functions succ, pred,+--     enumFromThen, enumFromThenTo, etc. could all have+--     better definitions than the default ones currently+--     used.+-- If, for some reason, anyone ever wants to fix the badness+-- that comes from laziness, all+-- you really have to do is define a version of dateToDay,+-- dayToDate, ordinalDateToDay, and dayToOrdinalDate+-- that uses something bigger instead of Day. Maybe something like+-- (Int,Word) or (Int,Word,Word). I'm not exactly sure how+-- big it would need to be to work correctly. Then you could+-- handle deltas of two very far off days correctly, provided+-- that the two days weren't also super far from each other.+--+------------------------+instance Torsor Date Int where+  add i d = dayToDate (add i (dateToDay d))+  difference a b = difference (dateToDay a) (dateToDay b)++instance Torsor OrdinalDate Int where+  add i d = dayToOrdinalDate (add i (ordinalDateToDay d))+  difference a b = difference (ordinalDateToDay a) (ordinalDateToDay b)++instance Enum Date where+  fromEnum d = fromEnum (dateToDay d)+  toEnum i = dayToDate (toEnum i)++instance Enum OrdinalDate where+  fromEnum d = fromEnum (ordinalDateToDay d)+  toEnum i = dayToOrdinalDate (toEnum i)++instance ToJSON Datetime where+  toJSON = AE.String . encode_YmdHMS SubsecondPrecisionAuto hyphen+  toEncoding x = AEE.unsafeToEncoding (BB.char7 '"' SG.<> builderUtf8_YmdHMS SubsecondPrecisionAuto hyphen x SG.<> BB.char7 '"')++instance FromJSON Datetime where+  parseJSON =+    AE.withText "Datetime" aesonParserDatetime++aesonParserDatetime :: Text -> AET.Parser Datetime+aesonParserDatetime =+  either (const (fail "could not parse Datetime")) pure . AT.parseOnly (parser_lenient <* AT.endOfInput)++instance ToJSON Offset where+  toJSON = AE.String . encodeOffset OffsetFormatColonOn+  toEncoding x = AEE.unsafeToEncoding (BB.char7 '"' SG.<> builderOffsetUtf8 OffsetFormatColonOn x SG.<> BB.char7 '"')++instance FromJSON Offset where+  parseJSON = AE.withText "Offset" aesonParserOffset++instance ToJSONKey Offset where+  toJSONKey =+    AE.ToJSONKeyText+      (keyFromText . encodeOffset OffsetFormatColonOn)+      (\x -> AEE.unsafeToEncoding (BB.char7 '"' SG.<> builderOffsetUtf8 OffsetFormatColonOn x SG.<> BB.char7 '"'))+   where+#if MIN_VERSION_aeson(2,0,0)+      keyFromText = AK.fromText+#else+      keyFromText = id+#endif++instance FromJSONKey Offset where+  fromJSONKey = AE.FromJSONKeyTextParser aesonParserOffset++aesonParserOffset :: Text -> AET.Parser Offset+aesonParserOffset t = case decodeOffset OffsetFormatColonOn t of+  Nothing -> fail "could not parse Offset"+  Just x -> pure x++{- | Holds all of the parts encoded by a 'Time'.+  Can be used for formatting if what is presently in the API+  does not suffice.+-}+data TimeParts = TimeParts+  { timePartsDay :: !Int+  -- ^ days 0-31+  , timePartsMonth :: !Int+  -- ^ months 0-11+  , timePartsYear :: !Int+  , timePartsHour :: !Int+  -- ^ hours 0-23+  , timePartsMinute :: !Int+  -- ^ minutes 0-59+  , timePartsSecond :: !Int+  -- ^ seconds 0-59+  , timePartsSubsecond :: !Int+  -- ^ fraction of a second with nanosecond resolution+  , timePartsOffset :: !Int+  }+  deriving (Eq, Read, Show)++instance NFData TimeParts where+  rnf (TimeParts d mo y h m s ss o) =+    d `deepseq` mo `deepseq` y `deepseq` h `deepseq` m `deepseq` s `deepseq` ss `deepseq` o `deepseq` ()++-- | Deconstruct a 'Time' into its 'TimeParts'.+timeParts :: Offset -> Time -> TimeParts+timeParts o0 t0 =+  let+    OffsetDatetime (Datetime dt t) o = timeToOffsetDatetime o0 t0+    Date y mo d = dt+    TimeOfDay h mi s = t+    (wholeSeconds, subsecond) = divMod s 100000000+   in+    TimeParts+      { timePartsDay = fromIntegral (getDayOfMonth d)+      , timePartsMonth = fromIntegral (getMonth mo)+      , timePartsYear = fromIntegral (getYear y)+      , timePartsHour = h+      , timePartsMinute = mi+      , timePartsSecond = fromIntegral wholeSeconds+      , timePartsSubsecond = fromIntegral subsecond+      , timePartsOffset = getOffset o+      }++{- | Decode an ISO-8601-encode datetime. The encoded time must be suffixed+by either @Z@ or @+00:00@ or @+00@.+-}+decodeIso8601Zulu :: Text -> Maybe Chronos.Datetime+{-# INLINE decodeIso8601Zulu #-}+decodeIso8601Zulu !t =+  BVP.parseBytesMaybe parserIso8601Zulu (Utf8.fromText t)++{- | Decode an ISO-8601-encode datetime. The encoded time must be suffixed+by either @Z@ or @+00:00@ or @+00@.+-}+decodeShortTextIso8601Zulu :: ShortText -> Maybe Chronos.Datetime+{-# INLINE decodeShortTextIso8601Zulu #-}+decodeShortTextIso8601Zulu !t =+  BVP.parseBytesMaybe parserIso8601Zulu (Utf8.fromShortText t)++parserIso8601Zulu :: BVP.Parser () s Chronos.Datetime+{-# NOINLINE parserIso8601Zulu #-}+parserIso8601Zulu = do+  d <- parserUtf8BytesIso8601Zoneless 'T'+  remaining <- BVP.remaining+  case Bytes.length remaining of+    1 | Bytes.unsafeIndex remaining 0 == 0x5A -> pure d+    3 | Bytes.equalsCString (Ptr "+00"#) remaining -> pure d+    6 | Bytes.equalsCString (Ptr "+00:00"#) remaining -> pure d+    _ -> BVP.fail ()++{- | Decode an ISO-8601-encode datetime. The encoded time must not be suffixed+by an offset. Any offset (e.g. @-05:00@, @+00:00@, @Z@) will cause a decode+failure.+-}+decodeShortTextIso8601Zoneless :: ShortText -> Maybe Chronos.Datetime+decodeShortTextIso8601Zoneless !t =+  decodeUtf8BytesIso8601Zoneless+    (Bytes.fromShortByteString (TS.toShortByteString t))++{- | Decode an ISO-8601-encode datetime. The encoded time must include an offset+(e.g. @-05:00@, @+00:00@, @Z@).+-}+decodeShortTextIso8601 :: ShortText -> Maybe Chronos.OffsetDatetime+decodeShortTextIso8601 !t =+  decodeUtf8BytesIso8601+    (Bytes.fromShortByteString (TS.toShortByteString t))++-- | Decode an ISO-8601-encode datetime.+decodeUtf8BytesIso8601Zoneless :: Bytes -> Maybe Chronos.Datetime+decodeUtf8BytesIso8601Zoneless !b =+  BVP.parseBytesMaybe (parserUtf8BytesIso8601Zoneless 'T' <* BVP.endOfInput ()) b++{- | Decode a datetime that is nearly ISO-8601-encoded but uses a space+instead of a T to separate the date and the time. For example:+@2022-10-29 14:00:05@.+-}+decodeUtf8BytesIso8601ZonelessSpaced :: Bytes -> Maybe Chronos.Datetime+decodeUtf8BytesIso8601ZonelessSpaced !b =+  BVP.parseBytesMaybe (parserUtf8BytesIso8601Zoneless ' ' <* BVP.endOfInput ()) b++decodeUtf8BytesIso8601 :: Bytes -> Maybe Chronos.OffsetDatetime+decodeUtf8BytesIso8601 !b =+  BVP.parseBytesMaybe (parserUtf8BytesIso8601 <* BVP.endOfInput ()) b++parserUtf8BytesIso8601Zoneless :: Char -> BVP.Parser () s Chronos.Datetime+{-# NOINLINE parserUtf8BytesIso8601Zoneless #-}+parserUtf8BytesIso8601Zoneless !sep = do+  year <- Latin.decWord ()+  Latin.char () '-'+  month' <- Latin.decWord ()+  let !month = month' - 1+  when (month >= 12) (BVP.fail ())+  Latin.char () '-'+  dayWord <- Latin.decWord ()+  when (dayWord > 31) (BVP.fail ())+  let !date =+        Chronos.Date+          (Chronos.Year (fromIntegral year))+          (Chronos.Month (fromIntegral month))+          (Chronos.DayOfMonth (fromIntegral dayWord))+  Latin.char () sep+  hourWord <- Latin.decWord8 ()+  when (hourWord > 23) (BVP.fail ())+  Latin.char () ':'+  minuteWord <- Latin.decWord8 ()+  when (minuteWord > 59) (BVP.fail ())+  Latin.char () ':'+  sec <- Latin.decWord8 ()+  when (sec > 59) (BVP.fail ())+  !nanos <-+    Latin.trySatisfy (== '.') >>= \case+      True -> do+        (n, w) <- BVP.measure (Latin.decWord64 ())+        when (n > 9) (BVP.fail ())+        let go !acc !b = case b of+              0 -> acc+              _ -> go (acc * 10) (b - 1)+            !ns = go w (9 - n)+        pure ns+      False -> pure 0+  let !td =+        Chronos.TimeOfDay+          (fromIntegral hourWord)+          (fromIntegral minuteWord)+          (fromIntegral @Word64 @Int64 (fromIntegral sec * 1000000000 + nanos))+  pure $! Chronos.Datetime date td++{- | Consume an ISO-8601-encoded datetime with offset. This will consume any of+the following:++> 2021-12-05T23:01:09Z+> 2021-12-05T23:01:09.000Z+> 2021-12-05T23:01:09.123456789Z+> 2021-12-05T23:01:09+05:00+> 2021-12-05T23:01:09.357-11:00+-}+parserUtf8BytesIso8601 :: BVP.Parser () s Chronos.OffsetDatetime+{-# NOINLINE parserUtf8BytesIso8601 #-}+parserUtf8BytesIso8601 = do+  dt <- parserUtf8BytesIso8601Zoneless 'T'+  off <-+    Latin.any () >>= \case+      'Z' -> pure 0+      '+' -> parserBytesOffset+      '-' -> do+        !off <- parserBytesOffset+        pure (negate off)+      _ -> BVP.fail ()+  pure $! Chronos.OffsetDatetime dt (Chronos.Offset off)++-- Should consume exactly five characters: HH:MM. However, the implementation+-- is more generous.+parserBytesOffset :: BVP.Parser () s Int+parserBytesOffset = do+  h <- Latin.decWord8 ()+  Latin.char () ':'+  m <- Latin.decWord8 ()+  let !r = ((fromIntegral @Word8 @Int h) * 60) + fromIntegral @Word8 @Int m+  pure r++encodeShortTextIso8601Zulu :: Datetime -> ShortText+{-# NOINLINE encodeShortTextIso8601Zulu #-}+encodeShortTextIso8601Zulu !dt =+  let !(ByteArray x) =+        Bounded.run+          Nat.constant+          ( boundedBuilderUtf8BytesIso8601Zoneless dt+              `Bounded.append` Bounded.ascii 'Z'+          )+   in TS.fromShortByteStringUnsafe (SBS.SBS x)++encodeShortTextIso8601Zoneless :: Datetime -> ShortText+{-# NOINLINE encodeShortTextIso8601Zoneless #-}+encodeShortTextIso8601Zoneless !dt =+  let !(ByteArray x) =+        Bounded.run+          Nat.constant+          (boundedBuilderUtf8BytesIso8601Zoneless dt)+   in TS.fromShortByteStringUnsafe (SBS.SBS x)++encodeShortTextIso8601 :: OffsetDatetime -> ShortText+{-# NOINLINE encodeShortTextIso8601 #-}+encodeShortTextIso8601 offdt =+  let !(ByteArray x) =+        Bounded.run+          Nat.constant+          (boundedBuilderUtf8BytesIso8601 offdt)+   in TS.fromShortByteStringUnsafe (SBS.SBS x)++boundedBuilderUtf8BytesIso8601 :: OffsetDatetime -> Bounded.Builder 50+boundedBuilderUtf8BytesIso8601 (OffsetDatetime dt off) =+  ( boundedBuilderUtf8BytesIso8601Zoneless dt+      `Bounded.append` boundedBuilderOffset off+  )++{- | Encode a datetime with ISO-8601. The result does not include any+indication of a time zone. If the subsecond part is zero, it is suppressed.+Examples of output:++> 2021-01-05T23:00:51+> 2021-01-05T23:00:52.123+> 2021-01-05T23:00:53.674094347+-}+boundedBuilderUtf8BytesIso8601Zoneless :: Datetime -> Bounded.Builder 44+boundedBuilderUtf8BytesIso8601Zoneless (Datetime (Date (Year y) (Month mth) (DayOfMonth d)) (TimeOfDay h mt sns)) =+  let (s, ns) = quotRem sns 1_000_000_000+   in Bounded.wordDec (fromIntegral y)+        `Bounded.append` Bounded.ascii '-'+        `Bounded.append` Bounded.wordPaddedDec2 (fromIntegral (mth + 1))+        `Bounded.append` Bounded.ascii '-'+        `Bounded.append` Bounded.wordPaddedDec2 (fromIntegral d)+        `Bounded.append` Bounded.ascii 'T'+        `Bounded.append` Bounded.wordPaddedDec2 (fromIntegral h)+        `Bounded.append` Bounded.ascii ':'+        `Bounded.append` Bounded.wordPaddedDec2 (fromIntegral mt)+        `Bounded.append` Bounded.ascii ':'+        `Bounded.append` Bounded.wordPaddedDec2 (fromIntegral s)+        `Bounded.append` ( case ns of+                             0 -> Bounded.weaken @0 @10 Lte.constant Bounded.empty+                             _ ->+                               let (ms, us) = quotRem ns 1_000_000+                                in case us of+                                     0 ->+                                       Bounded.weaken @4 @10+                                         Lte.constant+                                         ( Bounded.ascii '.'+                                             `Bounded.append` Bounded.wordPaddedDec3 (fromIntegral ms)+                                         )+                                     _ ->+                                       Bounded.ascii '.'+                                         `Bounded.append` Bounded.wordPaddedDec9 (fromIntegral ns)+                         )++boundedBuilderOffset :: Offset -> Bounded.Builder 6+boundedBuilderOffset (Offset mins) = case mins of+  0 -> Bounded.weaken @1 @6 Lte.constant (Bounded.ascii 'Z')+  _ ->+    let !absMins = fromIntegral @Int @Word (abs mins)+        !absHrs = quot absMins 60+        !absMinutes = rem absMins 60+     in Bounded.ascii (bool '-' '+' (mins > 0))+          `Bounded.append` Bounded.wordPaddedDec2 absHrs+          `Bounded.append` Bounded.ascii ':'+          `Bounded.append` Bounded.wordPaddedDec2 absMinutes
− src/Chronos/Calendar.hs
@@ -1,106 +0,0 @@-module Chronos.Calendar-  ( -- * Enumerations-    months-  , weekdays-    -- * Pattern Matching-  , month-  , dayOfWeek-    -- * Days of Week-  , sunday-  , monday-  , tuesday-  , wednesday-  , thursday-  , friday-  , saturday-    -- * Months-  , january-  , february-  , march-  , april-  , may-  , june-  , july-  , august-  , september-  , october-  , november-  , december-  ) where--import Chronos.Types-import qualified Data.Vector as Vector--month :: a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> Month -> a-month a b c d e f g h i j k l =-  let v = Vector.fromList [a,b,c,d,e,f,g,h,i,j,k,l]-   in \(Month ix) -> Vector.unsafeIndex v (fromIntegral ix)--dayOfWeek :: a -> a -> a -> a -> a -> a -> a -> DayOfWeek -> a-dayOfWeek a b c d e f g =-  let v = Vector.fromList [a,b,c,d,e,f,g]-   in \(DayOfWeek ix) -> Vector.unsafeIndex v (fromIntegral ix)--months :: [Month]-months = map Month [0..11]--weekdays :: [DayOfWeek]-weekdays = map DayOfWeek [0..6]--january :: Month-january = Month 0--february :: Month-february = Month 1--march :: Month-march = Month 2--april :: Month-april = Month 3--may :: Month-may = Month 4--june :: Month-june = Month 5--july :: Month-july = Month 6--august :: Month-august = Month 7--september :: Month-september = Month 8--october :: Month-october = Month 9--november :: Month-november = Month 10--december :: Month-december = Month 11--sunday :: DayOfWeek-sunday = DayOfWeek 0--monday :: DayOfWeek-monday = DayOfWeek 1--tuesday :: DayOfWeek-tuesday = DayOfWeek 2--wednesday :: DayOfWeek-wednesday = DayOfWeek 3--thursday :: DayOfWeek-thursday = DayOfWeek 4--friday :: DayOfWeek-friday = DayOfWeek 5--saturday :: DayOfWeek-saturday = DayOfWeek 6-
− src/Chronos/Date/ByteString/Char7.hs
@@ -1,47 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Chronos.Date.ByteString.Char7 where--import Chronos.Types-import Data.ByteString (ByteString)-import Data.ByteString.Builder (Builder)-import Data.Vector (Vector)-import Data.Monoid-import Data.Attoparsec.ByteString.Char8 (Parser)-import Control.Monad-import Data.Foldable-import qualified Chronos.Internal.Format as Format-import qualified Chronos.Internal as I-import qualified Data.ByteString.Char8 as ByteString-import qualified Data.Attoparsec.ByteString.Char8 as Atto-import qualified Data.Vector as Vector-import qualified Data.ByteString.Builder as Builder---- | This could be written much more efficiently since we know the---   exact size the resulting 'ByteString' will be.-builder_Ymd :: Maybe Char -> Date -> Builder-builder_Ymd msep (Date (Year y) m d) = case msep of-  Nothing ->-       Builder.intDec y-    <> Format.monthToZeroPaddedDigitBS m-    <> Format.zeroPadDayOfMonthBS d-  Just sep -> let sepBuilder = Builder.char7 sep in-       Builder.intDec y-    <> sepBuilder-    <> Format.monthToZeroPaddedDigitBS m-    <> sepBuilder-    <> Format.zeroPadDayOfMonthBS d--parser_Ymd :: Maybe Char -> Parser Date-parser_Ymd msep = do-  y <- I.parseFixedDigitsIntBS 4-  traverse_ Atto.char msep-  m <- I.parseFixedDigitsIntBS 2-  when (m < 1 || m > 12) (fail "month must be between 1 and 12")-  traverse_ Atto.char msep-  d <- I.parseFixedDigitsIntBS 2-  when (d < 1 || d > 31) (fail "day must be between 1 and 31")-  return (Date (Year y) (Month $ m - 1) (DayOfMonth d))---
− src/Chronos/Date/Text.hs
@@ -1,48 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Chronos.Date.Text where--import Chronos.Types-import Data.Text (Text)-import Data.Text.Lazy.Builder (Builder)-import Data.Vector (Vector)-import Data.Monoid-import Data.Attoparsec.Text (Parser)-import Control.Monad-import Data.Foldable-import qualified Chronos.Internal.Format as Format-import qualified Chronos.Internal as I-import qualified Data.Text as Text-import qualified Data.Text.Read as Text-import qualified Data.Attoparsec.Text as Atto-import qualified Data.Vector as Vector-import qualified Data.Text.Lazy.Builder as Builder-import qualified Data.Text.Lazy.Builder.Int as Builder---- | This could be written much more efficiently since we know the---   exact size the resulting 'Text' will be.-builder_Ymd :: Maybe Char -> Date -> Builder-builder_Ymd msep (Date (Year y) m d) = case msep of-  Nothing ->-       Builder.decimal y-    <> Format.monthToZeroPaddedDigit m-    <> Format.zeroPadDayOfMonth d-  Just sep -> let sepBuilder = Builder.singleton sep in-       Builder.decimal y-    <> sepBuilder-    <> Format.monthToZeroPaddedDigit m-    <> sepBuilder-    <> Format.zeroPadDayOfMonth d--parser_Ymd :: Maybe Char -> Parser Date-parser_Ymd msep = do-  y <- I.parseFixedDigits 4-  traverse_ Atto.char msep-  m <- I.parseFixedDigits 2-  when (m < 1 || m > 12) (fail "month must be between 1 and 12")-  traverse_ Atto.char msep-  d <- I.parseFixedDigits 2-  when (d < 1 || d > 31) (fail "day must be between 1 and 31")-  return (Date (Year y) (Month $ m - 1) (DayOfMonth d))--
− src/Chronos/Datetime.hs
@@ -1,25 +0,0 @@-module Chronos.Datetime where--import Chronos.Types-import qualified Chronos.Internal.Conversion as Conv--fromYmdhms :: Int -> Int -> Int -> Int -> Int -> Int -> Datetime-fromYmdhms y m d h m' s = Datetime-  (Date-     (Year $ fromIntegral y)-     (Month mx)-     (DayOfMonth $ fromIntegral d)-  )-  (TimeOfDay-     (fromIntegral h)-     (fromIntegral m')-     (fromIntegral s * 1000000000)-  )-  where-  mx = if m >= 1 && m <= 12-    then fromIntegral (m - 1)-    else error "fromYmdhms: month must be between 1 and 12"----- toOffsetDatetime :: Offset -> Datetime -> OffsetDatetime--- toOffsetDatetime =
− src/Chronos/Datetime/ByteString/Char7.hs
@@ -1,80 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Chronos.Datetime.ByteString.Char7 where--import Chronos.Types-import Data.ByteString (ByteString)-import Data.ByteString.Builder (Builder)-import Data.Vector (Vector)-import Data.Monoid-import Data.Attoparsec.ByteString (Parser)-import Control.Monad-import Data.Foldable-import qualified Chronos.Date.ByteString.Char7 as Date-import qualified Chronos.TimeOfDay.ByteString.Char7 as TimeOfDay-import qualified Data.ByteString.Char8 as ByteString-import qualified Data.ByteString.Lazy.Char8 as LByteString-import qualified Data.Attoparsec.ByteString.Char8 as Atto-import qualified Data.Vector as Vector-import qualified Data.ByteString.Lazy.Builder as Builder--encode_YmdHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> ByteString-encode_YmdHMS sp format =-  LByteString.toStrict . Builder.toLazyByteString . builder_YmdHMS sp format--encode_YmdIMS_p :: MeridiemLocale ByteString -> SubsecondPrecision -> DatetimeFormat -> Datetime -> ByteString-encode_YmdIMS_p a sp b = LByteString.toStrict . Builder.toLazyByteString . builder_YmdIMS_p a sp b---- | This could be written much more efficiently since we know the---   exact size the resulting 'ByteString' will be.-builder_YmdHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> Builder-builder_YmdHMS sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =-  case msep of-    Nothing -> Date.builder_Ymd mdateSep date-            <> TimeOfDay.builder_HMS sp mtimeSep time-    Just sep -> Date.builder_Ymd mdateSep date-             <> Builder.char7 sep-             <> TimeOfDay.builder_HMS sp mtimeSep time--builder_YmdIMS_p :: MeridiemLocale ByteString -> SubsecondPrecision -> DatetimeFormat -> Datetime -> Builder-builder_YmdIMS_p locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =-     Date.builder_Ymd mdateSep date-  <> maybe mempty Builder.char7 msep-  <> TimeOfDay.builder_IMS_p locale sp mtimeSep time--builder_YmdIMSp :: MeridiemLocale ByteString -> SubsecondPrecision -> DatetimeFormat -> Datetime -> Builder-builder_YmdIMSp locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =-     Date.builder_Ymd mdateSep date-  <> maybe mempty Builder.char7 msep-  <> TimeOfDay.builder_IMS_p locale sp mtimeSep time---builderW3 :: Datetime -> Builder-builderW3 = builder_YmdHMS SubsecondPrecisionAuto (DatetimeFormat (Just '-') (Just 'T') (Just ':'))--decode_YmdHMS :: DatetimeFormat -> ByteString -> Maybe Datetime-decode_YmdHMS format =-  either (const Nothing) Just . Atto.parseOnly (parser_YmdHMS format)--parser_YmdHMS :: DatetimeFormat -> Parser Datetime-parser_YmdHMS (DatetimeFormat mdateSep msep mtimeSep) = do-  date <- Date.parser_Ymd mdateSep-  traverse_ Atto.char msep-  time <- TimeOfDay.parser_HMS mtimeSep-  return (Datetime date time)--parser_YmdHMS_opt_S :: DatetimeFormat -> Parser Datetime-parser_YmdHMS_opt_S (DatetimeFormat mdateSep msep mtimeSep) = do-  date <- Date.parser_Ymd mdateSep-  traverse_ Atto.char msep-  time <- TimeOfDay.parser_HMS_opt_S mtimeSep-  return (Datetime date time)--decode_YmdHMS_opt_S :: DatetimeFormat -> ByteString -> Maybe Datetime-decode_YmdHMS_opt_S format =-  either (const Nothing) Just . Atto.parseOnly (parser_YmdHMS_opt_S format)-----
− src/Chronos/Datetime/Text.hs
@@ -1,81 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Chronos.Datetime.Text where--import Chronos.Types-import Data.Text (Text)-import Data.Text.Lazy.Builder (Builder)-import Data.Vector (Vector)-import Data.Monoid-import Data.Attoparsec.Text (Parser)-import Control.Monad-import Data.Foldable-import qualified Chronos.Date.Text as Date-import qualified Chronos.TimeOfDay.Text as TimeOfDay-import qualified Data.Text as Text-import qualified Data.Text.Read as Text-import qualified Data.Attoparsec.Text as Atto-import qualified Data.Vector as Vector-import qualified Data.Text.Lazy as LText-import qualified Data.Text.Lazy.Builder as Builder-import qualified Data.Text.Lazy.Builder.Int as Builder--encode_YmdHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> Text-encode_YmdHMS sp format =-  LText.toStrict . Builder.toLazyText . builder_YmdHMS sp format--encode_YmdIMS_p :: MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> Datetime -> Text-encode_YmdIMS_p a sp b = LText.toStrict . Builder.toLazyText . builder_YmdIMS_p a sp b---- | This could be written much more efficiently since we know the---   exact size the resulting 'Text' will be.-builder_YmdHMS :: SubsecondPrecision -> DatetimeFormat -> Datetime -> Builder-builder_YmdHMS sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =-  case msep of-    Nothing -> Date.builder_Ymd mdateSep date-            <> TimeOfDay.builder_HMS sp mtimeSep time-    Just sep -> Date.builder_Ymd mdateSep date-             <> Builder.singleton sep-             <> TimeOfDay.builder_HMS sp mtimeSep time--builder_YmdIMS_p :: MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> Datetime -> Builder-builder_YmdIMS_p locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =-     Date.builder_Ymd mdateSep date-  <> maybe mempty Builder.singleton msep-  <> TimeOfDay.builder_IMS_p locale sp mtimeSep time--builder_YmdIMSp :: MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> Datetime -> Builder-builder_YmdIMSp locale sp (DatetimeFormat mdateSep msep mtimeSep) (Datetime date time) =-     Date.builder_Ymd mdateSep date-  <> maybe mempty Builder.singleton msep-  <> TimeOfDay.builder_IMS_p locale sp mtimeSep time---builderW3 :: Datetime -> Builder-builderW3 = builder_YmdHMS SubsecondPrecisionAuto (DatetimeFormat (Just '-') (Just 'T') (Just ':'))--decode_YmdHMS :: DatetimeFormat -> Text -> Maybe Datetime-decode_YmdHMS format =-  either (const Nothing) Just . Atto.parseOnly (parser_YmdHMS format)--parser_YmdHMS :: DatetimeFormat -> Parser Datetime-parser_YmdHMS (DatetimeFormat mdateSep msep mtimeSep) = do-  date <- Date.parser_Ymd mdateSep-  traverse_ Atto.char msep-  time <- TimeOfDay.parser_HMS mtimeSep-  return (Datetime date time)--parser_YmdHMS_opt_S :: DatetimeFormat -> Parser Datetime-parser_YmdHMS_opt_S (DatetimeFormat mdateSep msep mtimeSep) = do-  date <- Date.parser_Ymd mdateSep-  traverse_ Atto.char msep-  time <- TimeOfDay.parser_HMS_opt_S mtimeSep-  return (Datetime date time)--decode_YmdHMS_opt_S :: DatetimeFormat -> Text -> Maybe Datetime-decode_YmdHMS_opt_S format =-  either (const Nothing) Just . Atto.parseOnly (parser_YmdHMS_opt_S format)----
− src/Chronos/Day.hs
@@ -1,41 +0,0 @@-module Chronos.Day-  ( add-  , diff-  , today-  , tomorrow-  , yesterday-  , toDate-  , fromDate-  ) where--import Chronos.Types-import Data.Int-import Chronos.Internal.Conversion as Conv-import qualified Chronos.Posix as Posix---- | Offset by the given number of days. To accomplish---   subtraction, provide a negative number.-add :: Int -> Day -> Day-add = Conv.addDay---- | Take the difference between two days.-diff :: Day -> Day -> Int-diff = Conv.diffDay---- | Gets the current POSIX day. This does not take the user\'s---   time zone into account.-today :: IO Day-today = fmap Posix.truncateToDay Posix.now--tomorrow :: IO Day-tomorrow = fmap (add 1 . Posix.truncateToDay) Posix.now--yesterday :: IO Day-yesterday = fmap (add (-1) . Posix.truncateToDay) Posix.now--fromDate :: Date -> Day-fromDate = Conv.dateToDay--toDate :: Day -> Date-toDate = Conv.dayToDate-
− src/Chronos/Internal.hs
@@ -1,121 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Chronos.Internal where--import Data.Attoparsec.Text (Parser)-import Data.Vector (Vector)-import Data.Text.Lazy.Builder (Builder)-import Data.Word-import Data.Int-import qualified Data.ByteString.Builder as BBuilder-import qualified Data.ByteString.Char8 as BC8-import qualified Data.Attoparsec.ByteString.Char8 as AB-import qualified Data.Vector.Unboxed as UVector-import qualified Data.Attoparsec.Text as Atto-import qualified Data.Text.Lazy.Builder as Builder-import qualified Data.Text.Lazy.Builder.Int as Builder-import qualified Data.Text.Read as Text-import qualified Data.Text as Text-import qualified Data.Vector as Vector--parseFixedDigits :: Int -> Parser Int-parseFixedDigits n = do-  t <- Atto.take n-  case Text.decimal t of-    Left err -> fail err-    Right (i,r) -> if Text.null r-      then return i-      else fail "datetime decoding could not parse integral text"-{-# INLINE parseFixedDigits #-}--parseFixedDigitsIntBS :: Int -> AB.Parser Int-parseFixedDigitsIntBS n = do-  t <- AB.take n-  case BC8.readInt t of-    Nothing -> fail "datetime decoding could not parse integral bytestring (a)"-    Just (i,r) -> if BC8.null r-      then return i-      else fail "datetime decoding could not parse integral bytestring (b)"-{-# INLINE parseFixedDigitsIntBS #-}--raiseTenTo :: Int -> Int64-raiseTenTo i = if i > 15-  then 10 ^ i-  else UVector.unsafeIndex tenRaisedToSmallPowers i--tenRaisedToSmallPowers :: UVector.Vector Int64-tenRaisedToSmallPowers = UVector.fromList $ map (10 ^) [0 :: Int ..15]---- | Only provide positive numbers to this function.-indexTwoDigitTextBuilder :: Int -> Builder-indexTwoDigitTextBuilder i = if i < 100-  then Vector.unsafeIndex twoDigitTextBuilder (fromIntegral i)-  else Builder.decimal i-{-# INLINE indexTwoDigitTextBuilder #-}---- | Only provide positive numbers to this function.-indexTwoDigitByteStringBuilder :: Int -> BBuilder.Builder-indexTwoDigitByteStringBuilder i = if i < 100-  then Vector.unsafeIndex twoDigitByteStringBuilder (fromIntegral i)-  else BBuilder.intDec i-{-# INLINE indexTwoDigitByteStringBuilder #-}--twoDigitByteStringBuilder :: Vector BBuilder.Builder-twoDigitByteStringBuilder = Vector.fromList-  $ map (BBuilder.byteString . BC8.pack) twoDigitStrings-{-# NOINLINE twoDigitByteStringBuilder #-}--twoDigitTextBuilder :: Vector Builder-twoDigitTextBuilder = Vector.fromList-  $ map (Builder.fromText . Text.pack) twoDigitStrings-{-# NOINLINE twoDigitTextBuilder #-}--twoDigitStrings :: [String]-twoDigitStrings =-  [ "00","01","02","03","04","05","06","07","08","09"-  , "10","11","12","13","14","15","16","17","18","19"-  , "20","21","22","23","24","25","26","27","28","29"-  , "30","31","32","33","34","35","36","37","38","39"-  , "40","41","42","43","44","45","46","47","48","49"-  , "50","51","52","53","54","55","56","57","58","59"-  , "60","61","62","63","64","65","66","67","68","69"-  , "70","71","72","73","74","75","76","77","78","79"-  , "80","81","82","83","84","85","86","87","88","89"-  , "90","91","92","93","94","95","96","97","98","99"-  ]--countDigits :: (Integral a) => a -> Int-{-# INLINE countDigits #-}-countDigits v0-  | fromIntegral v64 == v0 = go 1 v64-  | otherwise              = goBig 1 (fromIntegral v0)-  where v64 = fromIntegral v0-        goBig !k (v :: Integer)-           | v > big   = goBig (k + 19) (v `quot` big)-           | otherwise = go k (fromIntegral v)-        big = 10000000000000000000-        go !k (v :: Word64)-           | v < 10    = k-           | v < 100   = k + 1-           | v < 1000  = k + 2-           | v < 1000000000000 =-               k + if v < 100000000-                   then if v < 1000000-                        then if v < 10000-                             then 3-                             else 4 + fin v 100000-                        else 6 + fin v 10000000-                   else if v < 10000000000-                        then 8 + fin v 1000000000-                        else 10 + fin v 100000000000-           | otherwise = go (k + 12) (v `quot` 1000000000000)-        fin v n = if v >= n then 1 else 0--clip :: (Ord t) => t -> t -> t -> t-clip a _ x | x < a = a-clip _ b x | x > b = b-clip _ _ x = x-{-# INLINE clip #-}-
src/Chronos/Internal/CTimespec.hs view
@@ -1,18 +1,34 @@ {-# LANGUAGE CPP #-} +#if defined(ghcjs_HOST_OS)+{-# LANGUAGE JavaScriptFFI #-}+#endif++{-# OPTIONS_HADDOCK hide #-} -- for doctests+ module Chronos.Internal.CTimespec-  ( getPosixNanoseconds+  (+#ifndef mingw32_HOST_OS+    getPosixNanoseconds+#ifndef ghcjs_HOST_OS+  , CTimespec(..)+#endif+#endif   ) where  import Foreign import Foreign.C -#ifdef ghcjs_HOST_OS+#if defined(ghcjs_HOST_OS) --- Fix this at some point.+foreign import javascript unsafe "Date.now()" currentSeconds :: IO Double getPosixNanoseconds :: IO Int64-getPosixNanoseconds = return 0+getPosixNanoseconds = do+  x <- currentSeconds+  pure $ fromIntegral $ 1000000 * (round x) +#elif defined(mingw32_HOST_OS)+ #else  data CTimespec = CTimespec@@ -31,8 +47,13 @@         (\hsc_ptr -> pokeByteOff hsc_ptr 0) p s         (\hsc_ptr -> pokeByteOff hsc_ptr 8) p ns +#ifdef darwin_HOST_OS+foreign import ccall unsafe "cbits/hs-time.c clock_gettime"+    clock_gettime :: Int32 -> Ptr CTimespec -> IO CInt+#else foreign import ccall unsafe "time.h clock_gettime"     clock_gettime :: Int32 -> Ptr CTimespec -> IO CInt+#endif  -- | Get the current POSIX time from the system clock. getPosixNanoseconds :: IO Int64@@ -41,9 +62,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---
− src/Chronos/Internal/Conversion.hs
@@ -1,223 +0,0 @@-{-# LANGUAGE BangPatterns #-}--module Chronos.Internal.Conversion where--import Chronos.Types-import Data.Word-import Data.Int-import qualified Chronos.Internal as I-import qualified Data.Vector as Vector-import qualified Data.Vector.Unboxed as UVector--dayLengthInt64 :: Int64-dayLengthInt64 = 86400000000000--nanosecondsInMinute :: Int64-nanosecondsInMinute = 60000000000---- | The first argument in the resulting tuple in a day---   adjustment. It should be either -1, 0, or 1, as no---   offset should ever exceed 24 hours.-offsetTimeOfDay :: Offset -> TimeOfDay -> (Int, TimeOfDay)-offsetTimeOfDay (Offset offset) (TimeOfDay h m s) =-  (dayAdjustment,TimeOfDay h'' m'' s)-  where-  (!dayAdjustment, !h'') = divMod h' 24-  (!hourAdjustment, !m'') = divMod m' 60-  m' = m + offset-  h' = h + hourAdjustment--nanosecondsSinceMidnightToTimeOfDay :: Int64 -> TimeOfDay-nanosecondsSinceMidnightToTimeOfDay ns =-  if ns >= dayLengthInt64-    then TimeOfDay 23 59 (nanosecondsInMinute + (ns - dayLengthInt64))-    else TimeOfDay h' m' ns'-  where-  (!mInt64,!ns') = quotRem ns nanosecondsInMinute-  !m = fromIntegral mInt64-  (!h',!m')  = quotRem m 60--timeOfDayToNanosecondsSinceMidnight :: TimeOfDay -> Int64-timeOfDayToNanosecondsSinceMidnight (TimeOfDay h m ns) =-  fromIntegral h * 3600000000000 + fromIntegral m * 60000000000 + ns--dayToDate :: Day -> Date-dayToDate day = Date year month dayOfMonth-  where-  OrdinalDate year yd = dayToOrdinalDate day-  MonthDate month dayOfMonth = dayOfYearToMonthAndDay (isLeapYear year) yd---- datetimeToOffsetDatetime :: Offset -> Datetime -> OffsetDatetime--- datetimeToOffsetDatetime offset--utcTimeToOffsetDatetime :: Offset -> UtcTime -> OffsetDatetime-utcTimeToOffsetDatetime offset (UtcTime (Day d) nanoseconds) =-  let (!dayAdjustment,!tod) = offsetTimeOfDay offset (nanosecondsSinceMidnightToTimeOfDay nanoseconds)-      !date = dayToDate (Day (d + dayAdjustment))-   in OffsetDatetime (Datetime date tod) offset--utcTimeToDatetime :: UtcTime -> Datetime-utcTimeToDatetime (UtcTime d nanoseconds) =-  let !tod = nanosecondsSinceMidnightToTimeOfDay nanoseconds-      !date = dayToDate d-   in Datetime date tod--datetimeToUtcTime :: Datetime -> UtcTime-datetimeToUtcTime (Datetime date timeOfDay) =-  UtcTime (dateToDay date) (timeOfDayToNanosecondsSinceMidnight timeOfDay)--offsetDatetimeToUtcTime :: OffsetDatetime -> UtcTime-offsetDatetimeToUtcTime (OffsetDatetime (Datetime date timeOfDay) (Offset off)) =-  let (!dayAdjustment,!tod) = offsetTimeOfDay (Offset $ negate off) timeOfDay-      !(Day !day) = dateToDay date-   in UtcTime-        (Day (day + dayAdjustment))-        (timeOfDayToNanosecondsSinceMidnight tod)--dateToDay :: Date -> Day-dateToDay (Date y m d) = ordinalDateToDay $ OrdinalDate y-  (monthDateToDayOfYear (isLeapYear y) (MonthDate m d))--monthDateToDayOfYear :: Bool -> MonthDate -> DayOfYear-monthDateToDayOfYear isLeap (MonthDate month@(Month m) (DayOfMonth dayOfMonth)) =-  DayOfYear ((div (367 * (fromIntegral m + 1) - 362) 12) + k + day')-  where-  day' = fromIntegral $ I.clip 1 (monthLength isLeap month) dayOfMonth-  k = if month < Month 2 then 0 else if isLeap then -1 else -2--ordinalDateToDay :: OrdinalDate -> Day-ordinalDateToDay (OrdinalDate year@(Year y') day) = Day mjd where-  y = y' - 1-  mjd = (fromIntegral . getDayOfYear $-           (I.clip (DayOfYear 1) (if isLeapYear year then DayOfYear 366 else DayOfYear 365) day)-        )-      + (365 * y)-      + (div y 4) - (div y 100)-      + (div y 400) - 678576--isLeapYear :: Year -> Bool-isLeapYear (Year year) = (mod year 4 == 0) && ((mod year 400 == 0) || not (mod year 100 == 0))--dayOfYearToMonthAndDay :: Bool -> DayOfYear -> MonthDate-dayOfYearToMonthAndDay isLeap dayOfYear =-  let (!upperBound,!monthTable,!dayTable) =-        if isLeap-          then (DayOfYear 366, leapYearDayOfYearMonthTable, leapYearDayOfYearDayOfMonthTable)-          else (DayOfYear 365, normalYearDayOfYearMonthTable, normalYearDayOfYearDayOfMonthTable)-      DayOfYear clippedDay = I.clip (DayOfYear 1) upperBound dayOfYear-      clippedDayInt = fromIntegral clippedDay :: Int-      month = UVector.unsafeIndex monthTable clippedDayInt-      day = UVector.unsafeIndex dayTable clippedDayInt-   in MonthDate month day--dayToOrdinalDate :: Day -> OrdinalDate-dayToOrdinalDate (Day mjd) = OrdinalDate (Year $ fromIntegral year) (DayOfYear $ fromIntegral yd) where-  a = (fromIntegral mjd :: Int64) + 678575-  quadcent = div a 146097-  b = mod a 146097-  cent = min (div b 36524) 3-  c = b - (cent * 36524)-  quad = div c 1461-  d = mod c 1461-  y = min (div d 365) 3-  yd = (d - (y * 365) + 1)-  year = quadcent * 400 + cent * 100 + quad * 4 + y + 1--internalBuildDayOfWeekMatch :: a -> a -> a -> a -> a -> a -> a -> DayOfWeekMatch a-internalBuildDayOfWeekMatch a b c d e f g =-  DayOfWeekMatch (Vector.fromList [a,b,c,d,e,f,g])--internalBuildMonthMatch :: a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> MonthMatch a-internalBuildMonthMatch a b c d e f g h i j k l =-  MonthMatch (Vector.fromList [a,b,c,d,e,f,g,h,i,j,k,l])--internalMatchMonth :: MonthMatch a -> Month -> a-internalMatchMonth (MonthMatch v) (Month ix) = Vector.unsafeIndex v (fromIntegral ix)--monthLength :: Bool -> Month -> Int-monthLength isLeap m = if isLeap-  then internalMatchMonth leapYearMonthLength m-  else internalMatchMonth leapYearMonthLength m--leapYearMonthLength :: MonthMatch Int-leapYearMonthLength = internalBuildMonthMatch 31 29 31 30 31 30 31 31 30 31 30 31--normalYearMonthLength :: MonthMatch Int-normalYearMonthLength = internalBuildMonthMatch 31 30 31 30 31 30 31 31 30 31 30 31--leapYearDayOfYearDayOfMonthTable :: UVector.Vector DayOfMonth-leapYearDayOfYearDayOfMonthTable = UVector.fromList $ (DayOfMonth 1:) $ concat-  [ enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 29)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 30)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 30)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 30)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 30)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  ]-{-# NOINLINE leapYearDayOfYearDayOfMonthTable #-}--normalYearDayOfYearDayOfMonthTable :: UVector.Vector DayOfMonth-normalYearDayOfYearDayOfMonthTable = UVector.fromList $ (DayOfMonth 1:) $concat-  [ enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 28)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 30)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 30)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 30)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 30)-  , enumFromTo (DayOfMonth 1) (DayOfMonth 31)-  ]-{-# NOINLINE normalYearDayOfYearDayOfMonthTable #-}--leapYearDayOfYearMonthTable :: UVector.Vector Month-leapYearDayOfYearMonthTable = UVector.fromList $ (Month 0:) $ concat-  [ replicate 31 (Month 0)-  , replicate 29 (Month 1)-  , replicate 31 (Month 2)-  , replicate 30 (Month 3)-  , replicate 31 (Month 4)-  , replicate 30 (Month 5)-  , replicate 31 (Month 6)-  , replicate 31 (Month 7)-  , replicate 30 (Month 8)-  , replicate 31 (Month 9)-  , replicate 30 (Month 10)-  , replicate 31 (Month 11)-  ]-{-# NOINLINE leapYearDayOfYearMonthTable #-}--normalYearDayOfYearMonthTable :: UVector.Vector Month-normalYearDayOfYearMonthTable = UVector.fromList $ (Month 0:) $ concat-  [ replicate 31 (Month 0)-  , replicate 28 (Month 1)-  , replicate 31 (Month 2)-  , replicate 30 (Month 3)-  , replicate 31 (Month 4)-  , replicate 30 (Month 5)-  , replicate 31 (Month 6)-  , replicate 31 (Month 7)-  , replicate 30 (Month 8)-  , replicate 31 (Month 9)-  , replicate 30 (Month 10)-  , replicate 31 (Month 11)-  ]-{-# NOINLINE normalYearDayOfYearMonthTable #-}--addDay :: Int -> Day -> Day-addDay a (Day b) = Day (a + b)-{-# INLINE addDay #-}--diffDay :: Day -> Day -> Int-diffDay (Day a) (Day b) = a - b-{-# INLINE diffDay #-}-
− src/Chronos/Internal/Format.hs
@@ -1,33 +0,0 @@-module Chronos.Internal.Format where--import Chronos.Types-import Data.Text (Text)-import Data.Text.Lazy.Builder (Builder)-import Data.Vector (Vector)-import Data.Monoid-import Data.Attoparsec.Text (Parser)-import Control.Monad-import Data.Foldable-import qualified Data.ByteString.Builder as BBuilder-import qualified Chronos.Internal as I-import qualified Data.Text as Text-import qualified Data.Text.Read as Text-import qualified Data.Attoparsec.Text as Atto-import qualified Data.Vector as Vector-import qualified Data.Text.Lazy.Builder as Builder-import qualified Data.Text.Lazy.Builder.Int as Builder--monthToZeroPaddedDigit :: Month -> Builder-monthToZeroPaddedDigit (Month x) =-  I.indexTwoDigitTextBuilder (x + 1)--zeroPadDayOfMonth :: DayOfMonth -> Builder-zeroPadDayOfMonth (DayOfMonth d) = I.indexTwoDigitTextBuilder d--monthToZeroPaddedDigitBS :: Month -> BBuilder.Builder-monthToZeroPaddedDigitBS (Month x) =-  I.indexTwoDigitByteStringBuilder (x + 1)--zeroPadDayOfMonthBS :: DayOfMonth -> BBuilder.Builder-zeroPadDayOfMonthBS (DayOfMonth d) = I.indexTwoDigitByteStringBuilder d-
+ src/Chronos/Locale/English.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE OverloadedStrings #-}++{- | This module provides some english locale helpers.+    It is very incomplete. Please send a pull request+    to https://github.com/byteverse/chronos if you need+    additions to this API.+-}+module Chronos.Locale.English+  ( lower+  , upper+  , lowerDots+  , upperDots+  , abbreviated+  , unabbreviated+  ) where++import Chronos (buildMonthMatch)+import Chronos.Types+import Data.Text (Text)++{- $setup++>>> :set -XOverloadedStrings+>>> import Chronos (january, august, december, october, caseMonth)+-}++-- | Lowercase "am"/"pm".+lower :: MeridiemLocale Text+lower = MeridiemLocale "am" "pm"++-- | Uppercase "AM"/"PM".+upper :: MeridiemLocale Text+upper = MeridiemLocale "AM" "PM"++-- | Lowercase "a.m."/"p.m."+lowerDots :: MeridiemLocale Text+lowerDots = MeridiemLocale "a.m." "p.m."++-- | Uppercase "A.M."/"P.M."+upperDots :: MeridiemLocale Text+upperDots = MeridiemLocale "A.M." "P.M."++{- | Unabbreviated 'Month's of the year.++  >>> caseMonth unabbreviated january+  "January"++  >>> caseMonth unabbreviated december+  "December"+-}+unabbreviated :: MonthMatch Text+unabbreviated =+  buildMonthMatch+    "January"+    "February"+    "March"+    "April"+    "May"+    "June"+    "July"+    "August"+    "September"+    "October"+    "November"+    "December"++{- | Abbreviated 'Month's of the year.++  >>> caseMonth abbreviated october+  "Oct"++  >>> caseMonth abbreviated august+  "Aug"+-}+abbreviated :: MonthMatch Text+abbreviated =+  buildMonthMatch+    "Jan"+    "Feb"+    "Mar"+    "Apr"+    "May"+    "Jun"+    "Jul"+    "Aug"+    "Sep"+    "Oct"+    "Nov"+    "Dec"
− src/Chronos/Locale/English/Text.hs
@@ -1,43 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Chronos.Locale.English.Text where--import Chronos.Types-import qualified Chronos.Month as Month-import Data.Text (Text)--datetimeW3 :: DatetimeFormat-datetimeW3 = DatetimeFormat (Just '-') (Just 'T') (Just ':')--datetimeSlash :: DatetimeFormat-datetimeSlash = DatetimeFormat (Just '/') (Just ' ') (Just ':')--datetimeHyphen :: DatetimeFormat-datetimeHyphen = DatetimeFormat (Just '-') (Just ' ') (Just ':')--datetimeCompact :: DatetimeFormat-datetimeCompact = DatetimeFormat Nothing (Just 'T') Nothing--meridiemLower :: MeridiemLocale Text-meridiemLower = MeridiemLocale "am" "pm"--meridiemUpper :: MeridiemLocale Text-meridiemUpper = MeridiemLocale "AM" "PM"--meridiemLowerDotted :: MeridiemLocale Text-meridiemLowerDotted = MeridiemLocale "a.m." "p.m."--meridiemUpperDotted :: MeridiemLocale Text-meridiemUpperDotted = MeridiemLocale "A.M." "P.M."--monthFull :: MonthMatch Text-monthFull = Month.match-  "January" "February" "March" "April"-  "May" "June" "July" "August"-  "September" "October" "November" "December"--monthAbbreviated :: MonthMatch Text-monthAbbreviated = Month.match-  "Jan" "Feb" "Mar" "Apr" "May" "Jun"-  "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"-
− src/Chronos/Match.hs
@@ -1,15 +0,0 @@-module Chronos.Match where--import Chronos.Types-import qualified Data.Vector as Vector--buildDayOfWeekMatch :: a -> a -> a -> a -> a -> a -> a -> DayOfWeekMatch a-buildDayOfWeekMatch a b c d e f g =-  DayOfWeekMatch (Vector.fromList [a,b,c,d,e,f,g])--buildMonthMatch :: a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> MonthMatch a-buildMonthMatch a b c d e f g h i j k l =-  MonthMatch (Vector.fromList [a,b,c,d,e,f,g,h,i,j,k,l])---
− src/Chronos/Month.hs
@@ -1,16 +0,0 @@-module Chronos.Month where--import Chronos.Types-import qualified Chronos.Internal.Conversion as Conv--month :: a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> Month -> a-month a b c d e f g h i j k l =-  let theMatch = match a b c d e f g h i j k l-   in \m -> deconstruct theMatch m--match :: a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> MonthMatch a-match = Conv.internalBuildMonthMatch--deconstruct :: MonthMatch a -> Month -> a-deconstruct = Conv.internalMatchMonth-
− src/Chronos/Nanoseconds.hs
@@ -1,57 +0,0 @@-{-| Functions that operate on 'Nanoseconds'. All of these functions have-    trivial implementations and are provided for convenience. This module-    is intended to be imported qualified.--}--module Chronos.Nanoseconds-  ( -- * Arithmetic-    add-  , scale-  , negate-    -- * From Duration-    -- $fromduration-  , fromSeconds-  , fromMinutes-  , fromHours-  , fromDays-  , fromWeeks-  ) where--import Prelude hiding (negate)-import qualified Prelude as Prelude-import Data.Int-import Chronos.Types--add :: Nanoseconds -> Nanoseconds -> Nanoseconds-add (Nanoseconds a) (Nanoseconds b) = Nanoseconds (a + b)--negate :: Nanoseconds -> Nanoseconds-negate (Nanoseconds a) = Nanoseconds (Prelude.negate a)--scale :: Int64 -> Nanoseconds -> Nanoseconds-scale i (Nanoseconds x) = Nanoseconds (i * x)--{- $fromduration--These functions are at times convenient, but on a fundamental level,-all of them except for 'fromSeconds' are incorrect. If we account for-leap seconds, we must acknowledge that not all minutes contain 60 seconds.-Some contain 61 seconds, and in the future some may contain 59 seconds.---}--fromSeconds :: Int64 -> Nanoseconds-fromSeconds = Nanoseconds . (1000000000 *)--fromMinutes :: Int64 -> Nanoseconds-fromMinutes = Nanoseconds . (60000000000 *)--fromHours :: Int64 -> Nanoseconds-fromHours = Nanoseconds . (3600000000000 *)--fromDays :: Int64 -> Nanoseconds-fromDays = Nanoseconds . (86400000000000 *)--fromWeeks :: Int64 -> Nanoseconds-fromWeeks = Nanoseconds . (604800000000000 *)-
− src/Chronos/OffsetDatetime/ByteString/Char7.hs
@@ -1,169 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE BangPatterns #-}---- | The naming conventions for offsets that are used in---   function names are as follows:------   * @%z@ - @z@ +hhmm numeric time zone (e.g., -0400)---   * @%:z@ - @z1@ +hh:mm numeric time zone (e.g., -04:00)---   * @%::z@ - @z2@ +hh:mm:ss numeric time zone (e.g., -04:00:00)---   * @%:::z@ - @z3@ numeric time zone with : to necessary precision (e.g., -04, +05:30)--module Chronos.OffsetDatetime.ByteString.Char7 where--import Chronos.Types-import Data.ByteString (ByteString)-import Data.ByteString.Builder (Builder)-import Data.Vector (Vector)-import Data.Monoid-import Data.Attoparsec.ByteString (Parser)-import Control.Monad-import Data.Foldable-import Data.Int-import qualified Chronos.Internal as I-import qualified Chronos.Datetime.ByteString.Char7 as Datetime-import qualified Chronos.TimeOfDay.ByteString.Char7 as TimeOfDay-import qualified Data.ByteString.Char8 as ByteString-import qualified Data.Attoparsec.ByteString.Char8 as Atto-import qualified Data.Vector as Vector-import qualified Data.ByteString.Builder as Builder--builder_YmdHMSz :: OffsetFormat -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> Builder-builder_YmdHMSz offsetFormat sp datetimeFormat (OffsetDatetime datetime offset) =-     Datetime.builder_YmdHMS sp datetimeFormat datetime-  <> offsetBuilder offsetFormat offset--parser_YmdHMSz :: OffsetFormat -> DatetimeFormat -> Parser OffsetDatetime-parser_YmdHMSz offsetFormat datetimeFormat = OffsetDatetime-  <$> Datetime.parser_YmdHMS datetimeFormat-  <*> offsetParser offsetFormat--builder_YmdIMS_p_z :: OffsetFormat -> MeridiemLocale ByteString -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> Builder-builder_YmdIMS_p_z offsetFormat meridiemLocale sp datetimeFormat (OffsetDatetime datetime offset) =-     Datetime.builder_YmdIMS_p meridiemLocale sp datetimeFormat datetime-  <> " "-  <> offsetBuilder offsetFormat offset--builderW3 :: OffsetDatetime -> Builder-builderW3 = builder_YmdHMSz-  OffsetFormatColonOn-  SubsecondPrecisionAuto-  (DatetimeFormat (Just '-') (Just 'T') (Just ':'))--offsetBuilder :: OffsetFormat -> Offset -> Builder-offsetBuilder x = case x of-  OffsetFormatColonOff -> buildOffset_z-  OffsetFormatColonOn -> buildOffset_z1-  OffsetFormatSecondsPrecision -> buildOffset_z2-  OffsetFormatColonAuto -> buildOffset_z3--offsetParser :: OffsetFormat -> Parser Offset-offsetParser x = case x of-  OffsetFormatColonOff -> parseOffset_z-  OffsetFormatColonOn -> parseOffset_z1-  OffsetFormatSecondsPrecision -> parseOffset_z2-  OffsetFormatColonAuto -> parseOffset_z3---- | True means positive, false means negative-parseSignedness :: Parser Bool-parseSignedness = do-  c <- Atto.anyChar-  if c == '-'-    then return False-    else if c == '+'-      then return True-      else fail "while parsing offset, expected [+] or [-]"-{-# INLINE parseSignedness #-}--parseOffset_z :: Parser Offset-parseOffset_z = do-  pos <- parseSignedness-  h <- I.parseFixedDigitsIntBS 2-  m <- I.parseFixedDigitsIntBS 2-  let !res = h * 60 + m-  return . Offset $ if pos-    then res-    else negate res--parseOffset_z1 :: Parser Offset-parseOffset_z1 = do-  pos <- parseSignedness-  h <- I.parseFixedDigitsIntBS 2-  _ <- Atto.char ':'-  m <- I.parseFixedDigitsIntBS 2-  let !res = h * 60 + m-  return . Offset $ if pos-    then res-    else negate res--parseOffset_z2 :: Parser Offset-parseOffset_z2 = do-  pos <- parseSignedness-  h <- I.parseFixedDigitsIntBS 2-  _ <- Atto.char ':'-  m <- I.parseFixedDigitsIntBS 2-  _ <- Atto.string ":00"-  let !res = h * 60 + m-  return . Offset $ if pos-    then res-    else negate res---- | This is generous in what it accepts. If you give---   something like +04:00 as the offset, it will be---   allowed, even though it could be shorter.-parseOffset_z3 :: Parser Offset-parseOffset_z3 = do-  pos <- parseSignedness-  h <- I.parseFixedDigitsIntBS 2-  mc <- Atto.peekChar-  case mc of-    Just ':' -> do-      _ <- Atto.anyChar -- should be a colon-      m <- I.parseFixedDigitsIntBS 2-      let !res = h * 60 + m-      return . Offset $ if pos-        then res-        else negate res-    _ -> return . Offset $ if pos-      then h * 60-      else h * (-60)--buildOffset_z :: Offset -> Builder-buildOffset_z (Offset i) =-  let (!a,!b) = divMod (abs i) 60-      !prefix = if signum i == (-1) then "-" else "+"-   in prefix-      <> I.indexTwoDigitByteStringBuilder a-      <> I.indexTwoDigitByteStringBuilder b--buildOffset_z1 :: Offset -> Builder-buildOffset_z1 (Offset i) =-  let (!a,!b) = divMod (abs i) 60-      !prefix = if signum i == (-1) then "-" else "+"-   in prefix-      <> I.indexTwoDigitByteStringBuilder a-      <> ":"-      <> I.indexTwoDigitByteStringBuilder b--buildOffset_z2 :: Offset -> Builder-buildOffset_z2 (Offset i) =-  let (!a,!b) = divMod (abs i) 60-      !prefix = if signum i == (-1) then "-" else "+"-   in prefix-      <> I.indexTwoDigitByteStringBuilder a-      <> ":"-      <> I.indexTwoDigitByteStringBuilder b-      <> ":00"--buildOffset_z3 :: Offset -> Builder-buildOffset_z3 (Offset i) =-  let (!a,!b) = divMod (abs i) 60-      !prefix = if signum i == (-1) then "-" else "+"-   in if b == 0-        then prefix-          <> I.indexTwoDigitByteStringBuilder a-        else prefix-          <> I.indexTwoDigitByteStringBuilder a-          <> ":"-          <> I.indexTwoDigitByteStringBuilder b-
− src/Chronos/OffsetDatetime/Text.hs
@@ -1,170 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE BangPatterns #-}---- | The naming conventions for offsets that are used in---   function names are as follows:------   * @%z@ - @z@ +hhmm numeric time zone (e.g., -0400)---   * @%:z@ - @z1@ +hh:mm numeric time zone (e.g., -04:00)---   * @%::z@ - @z2@ +hh:mm:ss numeric time zone (e.g., -04:00:00)---   * @%:::z@ - @z3@ numeric time zone with : to necessary precision (e.g., -04, +05:30)--module Chronos.OffsetDatetime.Text where--import Chronos.Types-import Data.Text (Text)-import Data.Text.Lazy.Builder (Builder)-import Data.Vector (Vector)-import Data.Monoid-import Data.Attoparsec.Text (Parser)-import Control.Monad-import Data.Foldable-import Data.Int-import qualified Chronos.Internal as I-import qualified Chronos.Datetime.Text as Datetime-import qualified Chronos.TimeOfDay.Text as TimeOfDay-import qualified Data.Text as Text-import qualified Data.Text.Read as Text-import qualified Data.Attoparsec.Text as Atto-import qualified Data.Vector as Vector-import qualified Data.Text.Lazy.Builder as Builder-import qualified Data.Text.Lazy.Builder.Int as Builder--builder_YmdHMSz :: OffsetFormat -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> Builder-builder_YmdHMSz offsetFormat sp datetimeFormat (OffsetDatetime datetime offset) =-     Datetime.builder_YmdHMS sp datetimeFormat datetime-  <> offsetBuilder offsetFormat offset--parser_YmdHMSz :: OffsetFormat -> DatetimeFormat -> Parser OffsetDatetime-parser_YmdHMSz offsetFormat datetimeFormat = OffsetDatetime-  <$> Datetime.parser_YmdHMS datetimeFormat-  <*> offsetParser offsetFormat--builder_YmdIMS_p_z :: OffsetFormat -> MeridiemLocale Text -> SubsecondPrecision -> DatetimeFormat -> OffsetDatetime -> Builder-builder_YmdIMS_p_z offsetFormat meridiemLocale sp datetimeFormat (OffsetDatetime datetime offset) =-     Datetime.builder_YmdIMS_p meridiemLocale sp datetimeFormat datetime-  <> " "-  <> offsetBuilder offsetFormat offset--builderW3 :: OffsetDatetime -> Builder-builderW3 = builder_YmdHMSz-  OffsetFormatColonOn-  SubsecondPrecisionAuto-  (DatetimeFormat (Just '-') (Just 'T') (Just ':'))--offsetBuilder :: OffsetFormat -> Offset -> Builder-offsetBuilder x = case x of-  OffsetFormatColonOff -> buildOffset_z-  OffsetFormatColonOn -> buildOffset_z1-  OffsetFormatSecondsPrecision -> buildOffset_z2-  OffsetFormatColonAuto -> buildOffset_z3--offsetParser :: OffsetFormat -> Parser Offset-offsetParser x = case x of-  OffsetFormatColonOff -> parseOffset_z-  OffsetFormatColonOn -> parseOffset_z1-  OffsetFormatSecondsPrecision -> parseOffset_z2-  OffsetFormatColonAuto -> parseOffset_z3---- | True means positive, false means negative-parseSignedness :: Parser Bool-parseSignedness = do-  c <- Atto.anyChar-  if c == '-'-    then return False-    else if c == '+'-      then return True-      else fail "while parsing offset, expected [+] or [-]"-{-# INLINE parseSignedness #-}--parseOffset_z :: Parser Offset-parseOffset_z = do-  pos <- parseSignedness-  h <- I.parseFixedDigits 2-  m <- I.parseFixedDigits 2-  let !res = h * 60 + m-  return . Offset $ if pos-    then res-    else negate res--parseOffset_z1 :: Parser Offset-parseOffset_z1 = do-  pos <- parseSignedness-  h <- I.parseFixedDigits 2-  _ <- Atto.char ':'-  m <- I.parseFixedDigits 2-  let !res = h * 60 + m-  return . Offset $ if pos-    then res-    else negate res--parseOffset_z2 :: Parser Offset-parseOffset_z2 = do-  pos <- parseSignedness-  h <- I.parseFixedDigits 2-  _ <- Atto.char ':'-  m <- I.parseFixedDigits 2-  _ <- Atto.string ":00"-  let !res = h * 60 + m-  return . Offset $ if pos-    then res-    else negate res---- | This is generous in what it accepts. If you give---   something like +04:00 as the offset, it will be---   allowed, even though it could be shorter.-parseOffset_z3 :: Parser Offset-parseOffset_z3 = do-  pos <- parseSignedness-  h <- I.parseFixedDigits 2-  mc <- Atto.peekChar-  case mc of-    Just ':' -> do-      _ <- Atto.anyChar -- should be a colon-      m <- I.parseFixedDigits 2-      let !res = h * 60 + m-      return . Offset $ if pos-        then res-        else negate res-    _ -> return . Offset $ if pos-      then h * 60-      else h * (-60)--buildOffset_z :: Offset -> Builder-buildOffset_z (Offset i) =-  let (!a,!b) = divMod (abs i) 60-      !prefix = if signum i == (-1) then "-" else "+"-   in prefix-      <> I.indexTwoDigitTextBuilder a-      <> I.indexTwoDigitTextBuilder b--buildOffset_z1 :: Offset -> Builder-buildOffset_z1 (Offset i) =-  let (!a,!b) = divMod (abs i) 60-      !prefix = if signum i == (-1) then "-" else "+"-   in prefix-      <> I.indexTwoDigitTextBuilder a-      <> ":"-      <> I.indexTwoDigitTextBuilder b--buildOffset_z2 :: Offset -> Builder-buildOffset_z2 (Offset i) =-  let (!a,!b) = divMod (abs i) 60-      !prefix = if signum i == (-1) then "-" else "+"-   in prefix-      <> I.indexTwoDigitTextBuilder a-      <> ":"-      <> I.indexTwoDigitTextBuilder b-      <> ":00"--buildOffset_z3 :: Offset -> Builder-buildOffset_z3 (Offset i) =-  let (!a,!b) = divMod (abs i) 60-      !prefix = if signum i == (-1) then "-" else "+"-   in if b == 0-        then prefix-          <> I.indexTwoDigitTextBuilder a-        else prefix-          <> I.indexTwoDigitTextBuilder a-          <> ":"-          <> I.indexTwoDigitTextBuilder b
− src/Chronos/Posix.hs
@@ -1,87 +0,0 @@-{-# LANGUAGE BangPatterns #-}--module Chronos.Posix-  ( epoch-  , epochDay-  , dayLength-  , add-  , diff-  , now-  , toUtc-  , fromUtc-  , toDatetime-  , toOffsetDatetime-  , fromDatetime-  , fromOffsetDatetime-  , truncateToDay-  , fromDay-  ) where--import Chronos.Types-import Chronos.Internal.CTimespec-import Chronos.Internal.Conversion as Conv-import Foreign.C.Types (CLong(..),CTime(..))-import Data.Word-import Data.Int-import qualified Chronos.Nanoseconds as Nanoseconds--epoch :: PosixTime-epoch = PosixTime 0--epochDay :: Day-epochDay = Day 40587--dayLength :: Nanoseconds-dayLength = Nanoseconds 86400000000000--add :: Nanoseconds -> PosixTime -> PosixTime-add (Nanoseconds a) (PosixTime b) = PosixTime (a + b)--diff :: PosixTime -> PosixTime -> Nanoseconds-diff (PosixTime a) (PosixTime b) = Nanoseconds (a - b)---- | This probably needs to be wrapped in a bunch of CPP like---   the one in @time@ is.-now :: IO PosixTime-now = fmap PosixTime getPosixNanoseconds---- | This may be wrong for dates before what we count as the---   first modified julian day.-toUtc :: PosixTime -> UtcTime-toUtc (PosixTime i) = let (d,t) = divMod i (getNanoseconds dayLength)- in UtcTime (Conv.addDay (fromIntegral d) epochDay) (fromIntegral t)--fromUtc :: UtcTime -> PosixTime-fromUtc (UtcTime d ns') = PosixTime $ getNanoseconds $ Nanoseconds.add-  (Nanoseconds.scale (fromIntegral (Conv.diffDay d epochDay)) dayLength)-  (if ns > dayLength then dayLength else ns)-  where ns = Nanoseconds (fromIntegral ns')--toDatetime :: PosixTime -> Datetime-toDatetime = Conv.utcTimeToDatetime . toUtc--toOffsetDatetime :: Offset -> PosixTime -> OffsetDatetime-toOffsetDatetime offset = Conv.utcTimeToOffsetDatetime offset . toUtc--fromDatetime :: Datetime -> PosixTime-fromDatetime = fromUtc . Conv.datetimeToUtcTime--fromOffsetDatetime :: OffsetDatetime -> PosixTime-fromOffsetDatetime = fromUtc . Conv.offsetDatetimeToUtcTime--truncateToDay :: PosixTime -> Day-truncateToDay (PosixTime i) = Day (fromIntegral (div i 86400000000000) + 40587)---- | The 'PosixTime' at midnight on the given day.-fromDay :: Day -> PosixTime-fromDay (Day d) = PosixTime (fromIntegral (d - 40587) * 86400000000000)---- | Convert a 'PosixTime' to a UTC 'Datetime'.--- toDatetime :: PosixTime -> Datetime--- toDatetime (PosixTime i) = let (d,t) = divMod i (getNanoseconds dayLength)---  in UtcTime (Day.add (fromIntegral d) epochDay) (fromIntegral t)---- ctimespecToPosixSeconds :: CTimespec -> PosixTime--- ctimespecToPosixSeconds (MkCTimespec (CTime s) (CLong ns)) =---   PosixTime ((s * 1000000000) + ns)-
− src/Chronos/Tai.hs
@@ -1,13 +0,0 @@-module Chronos.Tai where--import Chronos.Types--epoch :: TaiTime-epoch = TaiTime 0--add :: Nanoseconds -> TaiTime -> TaiTime-add (Nanoseconds a) (TaiTime b) = TaiTime (a + b)--diff :: TaiTime -> TaiTime -> Nanoseconds-diff (TaiTime a) (TaiTime b) = Nanoseconds (a - b)-
− src/Chronos/TimeOfDay/ByteString/Char7.hs
@@ -1,206 +0,0 @@-{-| Functions for encoding 'TimeOfDay' to 'ByteString'. Any encoding-    that is a superset of ASCII is compatible with the functions in-    this module. This includes UTF-8 and ISO-8859-15 but does not-    include UTF-16 or UTF-32.--}--{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE BangPatterns #-}--module Chronos.TimeOfDay.ByteString.Char7 where--import Chronos.Types-import Data.ByteString (ByteString)-import Data.ByteString.Builder (Builder)-import Data.Vector (Vector)-import Data.Monoid-import Data.Attoparsec.ByteString.Char8 (Parser)-import Control.Monad-import Control.Applicative-import Data.Foldable-import Data.Word-import Data.Int-import Data.Char (isDigit)-import qualified Data.ByteString.Char8 as ByteString-import qualified Chronos.Internal as I-import qualified Data.Text as Text-import qualified Data.Text.Read as Text-import qualified Data.Attoparsec.ByteString.Char8 as Atto-import qualified Data.Vector as Vector-import qualified Data.ByteString.Builder as Builder---- | This could be written much more efficiently since we know the---   exact size the resulting 'Text' will be.-builder_HMS :: SubsecondPrecision -> Maybe Char -> TimeOfDay -> Builder-builder_HMS sp msep (TimeOfDay h m ns) =-     I.indexTwoDigitByteStringBuilder h-  <> internalBuilder_NS sp msep m ns--builder_IMS_p :: MeridiemLocale ByteString -> SubsecondPrecision -> Maybe Char -> TimeOfDay -> Builder-builder_IMS_p meridiemLocale sp msep (TimeOfDay h m ns) =-     internalBuilder_I h-  <> internalBuilder_NS sp msep h ns-  <> " "-  <> internalBuilder_p meridiemLocale h--internalBuilder_I :: Int -> Builder-internalBuilder_I h =-  I.indexTwoDigitByteStringBuilder $ if h > 12-    then h - 12-    else if h == 0-      then 12-      else h--internalBuilder_p :: MeridiemLocale ByteString -> Int -> Builder-internalBuilder_p (MeridiemLocale am pm) h = if h > 11-  then Builder.byteString pm-  else Builder.byteString am--builder_IMSp :: MeridiemLocale ByteString -> SubsecondPrecision -> Maybe Char -> TimeOfDay -> Builder-builder_IMSp meridiemLocale sp msep (TimeOfDay h m ns) =-     internalBuilder_I h-  <> internalBuilder_NS sp msep h ns-  <> internalBuilder_p meridiemLocale h--parser_HMS :: Maybe Char -> Parser TimeOfDay-parser_HMS msep = do-  h <- I.parseFixedDigitsIntBS 2-  when (h > 23) (fail "hour must be between 0 and 23")-  traverse_ Atto.char msep-  m <- I.parseFixedDigitsIntBS 2-  when (m > 59) (fail "minute must be between 0 and 59")-  traverse_ Atto.char msep-  ns <- parseSecondsAndNanoseconds-  return (TimeOfDay h m ns)---- | Parses text that is formatted as either of the following:------ * @%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 :: Maybe Char -> Parser TimeOfDay-parser_HMS_opt_S msep = do-  h <- I.parseFixedDigitsIntBS 2-  when (h > 23) (fail "hour must be between 0 and 23")-  traverse_ Atto.char msep-  m <- I.parseFixedDigitsIntBS 2-  when (m > 59) (fail "minute must be between 0 and 59")-  mc <- Atto.peekChar-  case mc of-    Nothing -> return (TimeOfDay h m 0)-    Just c -> case msep of-      Just sep -> if c == sep-        then do-          _ <- Atto.anyChar -- should be the separator-          ns <- parseSecondsAndNanoseconds-          return (TimeOfDay h m ns)-        else return (TimeOfDay h m 0)-      -- if there is no separator, we will try to parse the-      -- remaining part as seconds. We commit to trying to-      -- parse as seconds if we see any number as the next-      -- character.-      Nothing -> if isDigit c-        then do-          ns <- parseSecondsAndNanoseconds-          return (TimeOfDay h m ns)-        else return (TimeOfDay h m 0)--parseSecondsAndNanoseconds :: Parser Int64-parseSecondsAndNanoseconds = do-  s' <- I.parseFixedDigitsIntBS 2-  let !s = fromIntegral s' :: Int64-  when (s > 60) (fail "seconds must be between 0 and 60")-  let nanoseconds = 1-  nanoseconds <--    ( do _ <- Atto.char '.'-         numberOfZeroes <- countZeroes-         x <- Atto.decimal-         let totalDigits = I.countDigits x + numberOfZeroes-             result = if totalDigits == 9-               then x-               else if totalDigits < 9-                 then x * I.raiseTenTo (9 - totalDigits)-                 else quot x (I.raiseTenTo (totalDigits - 9))-         return (fromIntegral result)-    ) <|> return 0-  return (s * 1000000000 + nanoseconds)--countZeroes :: Parser Int-countZeroes = go 0 where-  go !i = do-    m <- Atto.peekChar-    case m of-      Nothing -> return i-      Just c -> if c == '0'-        then Atto.anyChar *> go (i + 1)-        else return i--nanosecondsBuilder :: Int64 -> Builder-nanosecondsBuilder w-  | w == 0 = mempty-  | w > 99999999 = "." <> int64Builder w-  | w > 9999999 = ".0" <> int64Builder w-  | w > 999999 = ".00" <> int64Builder w-  | w > 99999 = ".000" <> int64Builder w-  | w > 9999 = ".0000" <> int64Builder w-  | w > 999 = ".00000" <> int64Builder w-  | w > 99 = ".000000" <> int64Builder w-  | w > 9 = ".0000000" <> int64Builder w-  | otherwise = ".00000000" <> int64Builder w--microsecondsBuilder :: Int64 -> Builder-microsecondsBuilder w-  | w == 0 = mempty-  | w > 99999 = "." <> int64Builder w-  | w > 9999 = ".0" <> int64Builder w-  | w > 999 = ".00" <> int64Builder w-  | w > 99 = ".000" <> int64Builder w-  | w > 9 = ".0000" <> int64Builder w-  | otherwise = ".00000" <> int64Builder w--millisecondsBuilder :: Int64 -> Builder-millisecondsBuilder w-  | w == 0 = mempty-  | w > 99 = "." <> int64Builder w-  | w > 9 = ".0" <> int64Builder w-  | otherwise = ".00" <> int64Builder w--prettyNanosecondsBuilder :: SubsecondPrecision -> Int64 -> Builder-prettyNanosecondsBuilder sp nano = case sp of-  SubsecondPrecisionAuto-    | milliRem == 0 -> millisecondsBuilder milli-    | microRem == 0 -> microsecondsBuilder micro-    | otherwise -> nanosecondsBuilder nano-  SubsecondPrecisionFixed d -> if d == 0-    then mempty-    else-      let newSubsecondPart = quot nano (I.raiseTenTo (9 - d))-       in "."-          <> Builder.byteString (ByteString.replicate (d - I.countDigits newSubsecondPart) '0')-          <> int64Builder newSubsecondPart-  where-  (milli,milliRem) = quotRem nano 1000000-  (micro,microRem) = quotRem nano 1000--int64Builder :: Int64 -> Builder-int64Builder = Builder.integerDec . fromIntegral--internalBuilder_NS :: SubsecondPrecision -> Maybe Char -> Int -> Int64 -> Builder-internalBuilder_NS sp msep m ns = case msep of-  Nothing -> I.indexTwoDigitByteStringBuilder m-          <> I.indexTwoDigitByteStringBuilder s-          <> prettyNanosecondsBuilder sp nsRemainder-  Just sep -> let sepBuilder = Builder.char7 sep in-             sepBuilder-          <> I.indexTwoDigitByteStringBuilder m-          <> sepBuilder-          <> I.indexTwoDigitByteStringBuilder s-          <> prettyNanosecondsBuilder sp nsRemainder-  where-  (!sInt64,!nsRemainder) = quotRem ns 1000000000-  !s = fromIntegral sInt64--
− src/Chronos/TimeOfDay/Text.hs
@@ -1,195 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE BangPatterns #-}--module Chronos.TimeOfDay.Text where--import Chronos.Types-import Data.Text (Text)-import Data.Text.Lazy.Builder (Builder)-import Data.Vector (Vector)-import Data.Monoid-import Data.Attoparsec.Text (Parser)-import Control.Monad-import Control.Applicative-import Data.Foldable-import Data.Word-import Data.Int-import Data.Char (isDigit)-import qualified Chronos.Internal as I-import qualified Data.Text as Text-import qualified Data.Text.Read as Text-import qualified Data.Attoparsec.Text as Atto-import qualified Data.Vector as Vector-import qualified Data.Text.Lazy.Builder as Builder-import qualified Data.Text.Lazy.Builder.Int as Builder---- | This could be written much more efficiently since we know the---   exact size the resulting 'Text' will be.-builder_HMS :: SubsecondPrecision -> Maybe Char -> TimeOfDay -> Builder-builder_HMS sp msep (TimeOfDay h m ns) =-     I.indexTwoDigitTextBuilder h-  <> internalBuilder_NS sp msep m ns--builder_IMS_p :: MeridiemLocale Text -> SubsecondPrecision -> Maybe Char -> TimeOfDay -> Builder-builder_IMS_p meridiemLocale sp msep (TimeOfDay h m ns) =-     internalBuilder_I h-  <> internalBuilder_NS sp msep h ns-  <> " "-  <> internalBuilder_p meridiemLocale h--internalBuilder_I :: Int -> Builder-internalBuilder_I h =-  I.indexTwoDigitTextBuilder $ if h > 12-    then h - 12-    else if h == 0-      then 12-      else h--internalBuilder_p :: MeridiemLocale Text -> Int -> Builder-internalBuilder_p (MeridiemLocale am pm) h = if h > 11-  then Builder.fromText pm-  else Builder.fromText am--builder_IMSp :: MeridiemLocale Text -> SubsecondPrecision -> Maybe Char -> TimeOfDay -> Builder-builder_IMSp meridiemLocale sp msep (TimeOfDay h m ns) =-     internalBuilder_I h-  <> internalBuilder_NS sp msep h ns-  <> internalBuilder_p meridiemLocale h--parser_HMS :: Maybe Char -> Parser TimeOfDay-parser_HMS msep = do-  h <- I.parseFixedDigits 2-  when (h > 23) (fail "hour must be between 0 and 23")-  traverse_ Atto.char msep-  m <- I.parseFixedDigits 2-  when (m > 59) (fail "minute must be between 0 and 59")-  traverse_ Atto.char msep-  ns <- parseSecondsAndNanoseconds-  return (TimeOfDay h m ns)---- | Parses text that is formatted as either of the following:------ * @%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 :: Maybe Char -> Parser TimeOfDay-parser_HMS_opt_S msep = do-  h <- I.parseFixedDigits 2-  when (h > 23) (fail "hour must be between 0 and 23")-  traverse_ Atto.char msep-  m <- I.parseFixedDigits 2-  when (m > 59) (fail "minute must be between 0 and 59")-  mc <- Atto.peekChar-  case mc of-    Nothing -> return (TimeOfDay h m 0)-    Just c -> case msep of-      Just sep -> if c == sep-        then do-          _ <- Atto.anyChar -- should be the separator-          ns <- parseSecondsAndNanoseconds-          return (TimeOfDay h m ns)-        else return (TimeOfDay h m 0)-      -- if there is no separator, we will try to parse the-      -- remaining part as seconds. We commit to trying to-      -- parse as seconds if we see any number as the next-      -- character.-      Nothing -> if isDigit c-        then do-          ns <- parseSecondsAndNanoseconds-          return (TimeOfDay h m ns)-        else return (TimeOfDay h m 0)--parseSecondsAndNanoseconds :: Parser Int64-parseSecondsAndNanoseconds = do-  s' <- I.parseFixedDigits 2-  let s = fromIntegral s' :: Int64-  when (s > 60) (fail "seconds must be between 0 and 60")-  nanoseconds <--    ( do _ <- Atto.char '.'-         numberOfZeroes <- countZeroes-         x <- Atto.decimal-         let totalDigits = I.countDigits x + numberOfZeroes-             result = if totalDigits == 9-               then x-               else if totalDigits < 9-                 then x * I.raiseTenTo (9 - totalDigits)-                 else quot x (I.raiseTenTo (totalDigits - 9))-         return (fromIntegral result)-    ) <|> return 0-  return (s * 1000000000 + nanoseconds)--countZeroes :: Parser Int-countZeroes = go 0 where-  go !i = do-    m <- Atto.peekChar-    case m of-      Nothing -> return i-      Just c -> if c == '0'-        then Atto.anyChar *> go (i + 1)-        else return i--nanosecondsBuilder :: Int64 -> Builder-nanosecondsBuilder w-  | w == 0 = mempty-  | w > 99999999 = "." <> Builder.decimal w-  | w > 9999999 = ".0" <> Builder.decimal w-  | w > 999999 = ".00" <> Builder.decimal w-  | w > 99999 = ".000" <> Builder.decimal w-  | w > 9999 = ".0000" <> Builder.decimal w-  | w > 999 = ".00000" <> Builder.decimal w-  | w > 99 = ".000000" <> Builder.decimal w-  | w > 9 = ".0000000" <> Builder.decimal w-  | otherwise = ".00000000" <> Builder.decimal w--microsecondsBuilder :: Int64 -> Builder-microsecondsBuilder w-  | w == 0 = mempty-  | w > 99999 = "." <> Builder.decimal w-  | w > 9999 = ".0" <> Builder.decimal w-  | w > 999 = ".00" <> Builder.decimal w-  | w > 99 = ".000" <> Builder.decimal w-  | w > 9 = ".0000" <> Builder.decimal w-  | otherwise = ".00000" <> Builder.decimal w--millisecondsBuilder :: Int64 -> Builder-millisecondsBuilder w-  | w == 0 = mempty-  | w > 99 = "." <> Builder.decimal w-  | w > 9 = ".0" <> Builder.decimal w-  | otherwise = ".00" <> Builder.decimal w--prettyNanosecondsBuilder :: SubsecondPrecision -> Int64 -> Builder-prettyNanosecondsBuilder sp nano = case sp of-  SubsecondPrecisionAuto-    | milliRem == 0 -> millisecondsBuilder milli-    | microRem == 0 -> microsecondsBuilder micro-    | otherwise -> nanosecondsBuilder nano-  SubsecondPrecisionFixed d -> if d == 0-    then mempty-    else-      let newSubsecondPart = quot nano (I.raiseTenTo (9 - d))-       in "."-          <> Builder.fromText (Text.replicate (d - I.countDigits newSubsecondPart) "0")-          <> Builder.decimal newSubsecondPart-  where-  (milli,milliRem) = quotRem nano 1000000-  (micro,microRem) = quotRem nano 1000--internalBuilder_NS :: SubsecondPrecision -> Maybe Char -> Int -> Int64 -> Builder-internalBuilder_NS sp msep m ns = case msep of-  Nothing -> I.indexTwoDigitTextBuilder m-          <> I.indexTwoDigitTextBuilder s-          <> prettyNanosecondsBuilder sp nsRemainder-  Just sep -> let sepBuilder = Builder.singleton sep in-             sepBuilder-          <> I.indexTwoDigitTextBuilder m-          <> sepBuilder-          <> I.indexTwoDigitTextBuilder s-          <> prettyNanosecondsBuilder sp nsRemainder-  where-  (!sInt64,!nsRemainder) = quotRem ns 1000000000-  !s = fromIntegral sInt64-
src/Chronos/Types.hs view
@@ -1,11 +1,3 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UnboxedTuples #-}-{-# LANGUAGE DeriveGeneric #-}--{-# OPTIONS_GHC -Wall -Werror #-}- {- | Data types for representing different date and time-related      information. @@ -25,286 +17,40 @@      1680 and 2260. All other types in this library correctly represent time      a million years before or after 1970. -     The vector unbox instances, not yet available, will store+     The vector unbox instances store      data in a reasonably compact manner. For example, the instance      for 'Day' has three unboxed vectors: 'Int' for the year, 'Int8'      for the month, and 'Int8' for the day. This only causes      corruption of data if the user is trying to use out-of-bounds      values for the month and the day. Users are advised to not      use the data types provided here to model non-existent times.- -}- module Chronos.Types-  ( Day(..)-  , DayOfWeek(..)-  , DayOfMonth(..)-  , DayOfYear(..)-  , Month(..)-  , Year(..)-  , Offset(..)-  , TaiTime(..)-  , PosixTime(..)-  , UtcTime(..)-  , DayOfWeekMatch(..)-  , MonthMatch(..)-  , UnboxedMonthMatch(..)-  , Nanoseconds(..)-  , SubsecondPrecision(..)-  , Date(..)-  , OrdinalDate(..)-  , MonthDate(..)-  , Datetime(..)-  , OffsetDatetime(..)-  , TimeOfDay(..)-  , DatetimeFormat(..)-  , OffsetFormat(..)-  , DatetimeLocale(..)-  , MeridiemLocale(..)+  ( Day (..)+  , DayOfWeek (..)+  , DayOfMonth (..)+  , DayOfYear (..)+  , Month (..)+  , Year (..)+  , Offset (..)+  , Time (..)+  , DayOfWeekMatch (..)+  , MonthMatch (..)+  , UnboxedMonthMatch (..)+  , Timespan (..)+  , SubsecondPrecision (..)+  , Date (..)+  , OrdinalDate (..)+  , MonthDate (..)+  , Datetime (..)+  , OffsetDatetime (..)+  , TimeOfDay (..)+  , DatetimeFormat (..)+  , OffsetFormat (..)+  , DatetimeLocale (..)+  , MeridiemLocale (..)+  , TimeInterval (..)+  , TimeParts (..)   ) where -import Data.Int-import Data.Vector (Vector)-import Data.Aeson (FromJSON,ToJSON)-import Data.Hashable (Hashable)-import Data.Primitive-import Control.Monad-import GHC.Generics (Generic)-import qualified Data.Vector.Generic as GVector-import qualified Data.Vector.Unboxed as UVector-import qualified Data.Vector.Primitive as PVector-import qualified Data.Vector.Generic.Mutable as MGVector---- | A day represented as the modified Julian date, the number of days---   since midnight on November 17, 1858.-newtype Day = Day { getDay :: Int }-  deriving (Show,Read,Eq,Ord,Hashable,Enum,ToJSON,FromJSON)---- | The day of the week.-newtype DayOfWeek = DayOfWeek { getDayOfWeek :: Int }-  deriving (Show,Read,Eq,Ord,Hashable)---- | The day of the month.-newtype DayOfMonth = DayOfMonth { getDayOfMonth :: Int }-  deriving (Show,Read,Eq,Ord,Prim,Enum)---- | The day of the year.-newtype DayOfYear = DayOfYear { getDayOfYear :: Int }-  deriving (Show,Read,Eq,Ord,Prim)---- | The month of the year.-newtype Month = Month { getMonth :: Int }-  deriving (Show,Read,Eq,Ord,Prim)--instance Bounded Month where-  minBound = Month 0-  maxBound = Month 11---- | The number of years elapsed since the beginning---   of the Common Era.-newtype Year = Year { getYear :: Int }-  deriving (Show,Read,Eq,Ord)--newtype Offset = Offset { getOffset :: Int }-  deriving (Show,Read,Eq,Ord)---- This is a Modified Julian Day.--- newtype Date = Date { getDate :: Int32 }---- | TAI time with nanosecond resolution.-newtype TaiTime = TaiTime { getTaiTime :: Int64 }-  deriving (FromJSON,ToJSON,Hashable,Eq,Ord,Show,Read)---- | POSIX time with nanosecond resolution.-newtype PosixTime = PosixTime { getPosixTime :: Int64 }-  deriving (FromJSON,ToJSON,Hashable,Eq,Ord,Show,Read)--newtype DayOfWeekMatch a = DayOfWeekMatch { getDayOfWeekMatch :: Vector a }--newtype MonthMatch a = MonthMatch { getMonthMatch :: Vector a }--newtype UnboxedMonthMatch a = UnboxedMonthMatch { getUnboxedMonthMatch :: UVector.Vector a }---- | A nanosecond duration.-newtype Nanoseconds = Nanoseconds { getNanoseconds :: Int64 }-  deriving (Show,Read,Eq,Ord,ToJSON,FromJSON)---- | The precision used when encoding seconds to a human-readable format.-data SubsecondPrecision-  = SubsecondPrecisionAuto -- ^ Rounds to second, millisecond, microsecond, or nanosecond-  | SubsecondPrecisionFixed {-# UNPACK #-} !Int -- ^ Specify number of places after decimal---- | A date as represented by the Gregorian calendar.-data Date = Date-  { dateYear  :: {-# UNPACK #-} !Year-  , dateMonth :: {-# UNPACK #-} !Month-  , dateDay   :: {-# UNPACK #-} !DayOfMonth-  } deriving (Show,Read,Eq,Ord)---- | The year and number of days elapsed since the beginning it began.-data OrdinalDate = OrdinalDate-  { ordinalDateYear :: {-# UNPACK #-} !Year-  , ordinalDateDayOfYear :: {-# UNPACK #-} !DayOfYear-  } deriving (Show,Read,Eq,Ord)---- | A month and the day of the month. This does not actually represent---   a specific date, since this recurs every year.-data MonthDate = MonthDate-  { monthDateMonth :: {-# UNPACK #-} !Month-  , monthDateDay :: {-# UNPACK #-} !DayOfMonth-  } deriving (Show,Read,Eq,Ord)---- | A date as represented by the Gregorian calendar---   and a time of day.-data Datetime = Datetime-  { datetimeDate :: {-# UNPACK #-} !Date-  , datetimeTime :: {-# UNPACK #-} !TimeOfDay-  } deriving (Show,Read,Eq,Ord)--data OffsetDatetime = OffsetDatetime-  { offsetDatetimeDatetime :: {-# UNPACK #-} !Datetime-  , offsetDatetimeOffset :: {-# UNPACK #-} !Offset-  } deriving (Show,Read,Eq,Ord)---- | A time of day with nanosecond resolution.-data TimeOfDay = TimeOfDay-  { timeOfDayHour :: {-# UNPACK #-} !Int-  , timeOfDayMinute :: {-# UNPACK #-} !Int-  , timeOfDayNanoseconds :: {-# UNPACK #-} !Int64-  } deriving (Show,Read,Eq,Ord)--data UtcTime = UtcTime-  { utcTimeDate :: {-# UNPACK #-} !Day-  , utcTimeNanoseconds :: {-# UNPACK #-} !Int64-  } deriving (Show,Read,Eq,Ord)--data DatetimeFormat = DatetimeFormat-  { datetimeFormatDateSeparator :: !(Maybe Char)-    -- ^ Separator in the date-  , datetimeFormatSeparator :: !(Maybe Char)-    -- ^ Separator between date and time-  , datetimeFormatTimeSeparator :: !(Maybe Char)-    -- ^ Separator in the time-  } deriving (Show,Read,Eq,Ord)---- | Formatting settings for a timezone offset.-data OffsetFormat-  = OffsetFormatColonOff -- ^ @%z@ (e.g., -0400)-  | OffsetFormatColonOn -- ^ @%:z@ (e.g., -04:00)-  | OffsetFormatSecondsPrecision -- ^ @%::z@ (e.g., -04:00:00)-  | OffsetFormatColonAuto -- ^ @%:::z@ (e.g., -04, +05:30)-  deriving (Show,Read,Eq,Ord,Enum,Bounded,Generic)---- | Locale-specific formatting for weekdays and months. The---   type variable will likely be instantiated to @Text@---   or @ByteString@.-data DatetimeLocale a = DatetimeLocale-  { datetimeLocaleDaysOfWeekFull :: !(DayOfWeekMatch a)-    -- ^ full weekdays starting with Sunday, 7 elements-  , datetimeLocaleDaysOfWeekAbbreviated :: !(DayOfWeekMatch a)-    -- ^ abbreviated weekdays starting with Sunday, 7 elements-  , datetimeLocaleMonthsFull :: !(MonthMatch a)-    -- ^ full months starting with January, 12 elements-  , datetimeLocaleMonthsAbbreviated :: !(MonthMatch a)-    -- ^ abbreviated months starting with January, 12 elements-  }---- | Locale-specific formatting for AM and PM.-data MeridiemLocale a = MeridiemLocale-  { meridiemLocaleAm :: !a-  , meridiemLocalePm :: !a-  } deriving (Read,Show,Eq,Ord)--newtype instance UVector.MVector s Month = MV_Month (PVector.MVector s Month)-newtype instance UVector.Vector Month = V_Month (PVector.Vector Month)--instance UVector.Unbox Month--instance MGVector.MVector UVector.MVector Month where-  {-# INLINE basicLength #-}-  {-# INLINE basicUnsafeSlice #-}-  {-# INLINE basicOverlaps #-}-  {-# INLINE basicUnsafeNew #-}-  {-# INLINE basicInitialize #-}-  {-# INLINE basicUnsafeReplicate #-}-  {-# INLINE basicUnsafeRead #-}-  {-# INLINE basicUnsafeWrite #-}-  {-# INLINE basicClear #-}-  {-# INLINE basicSet #-}-  {-# INLINE basicUnsafeCopy #-}-  {-# INLINE basicUnsafeGrow #-}-  basicLength (MV_Month v) = MGVector.basicLength v-  basicUnsafeSlice i n (MV_Month v) = MV_Month $ MGVector.basicUnsafeSlice i n v-  basicOverlaps (MV_Month v1) (MV_Month v2) = MGVector.basicOverlaps v1 v2-  basicUnsafeNew n = MV_Month `liftM` MGVector.basicUnsafeNew n-  basicInitialize (MV_Month v) = MGVector.basicInitialize v-  basicUnsafeReplicate n x = MV_Month `liftM` MGVector.basicUnsafeReplicate n x-  basicUnsafeRead (MV_Month v) i = MGVector.basicUnsafeRead v i-  basicUnsafeWrite (MV_Month v) i x = MGVector.basicUnsafeWrite v i x-  basicClear (MV_Month v) = MGVector.basicClear v-  basicSet (MV_Month v) x = MGVector.basicSet v x-  basicUnsafeCopy (MV_Month v1) (MV_Month v2) = MGVector.basicUnsafeCopy v1 v2-  basicUnsafeMove (MV_Month v1) (MV_Month v2) = MGVector.basicUnsafeMove v1 v2-  basicUnsafeGrow (MV_Month v) n = MV_Month `liftM` MGVector.basicUnsafeGrow v n--instance GVector.Vector UVector.Vector Month where-  {-# INLINE basicUnsafeFreeze #-}-  {-# INLINE basicUnsafeThaw #-}-  {-# INLINE basicLength #-}-  {-# INLINE basicUnsafeSlice #-}-  {-# INLINE basicUnsafeIndexM #-}-  {-# INLINE elemseq #-}-  basicUnsafeFreeze (MV_Month v) = V_Month `liftM` GVector.basicUnsafeFreeze v-  basicUnsafeThaw (V_Month v) = MV_Month `liftM` GVector.basicUnsafeThaw v-  basicLength (V_Month v) = GVector.basicLength v-  basicUnsafeSlice i n (V_Month v) = V_Month $ GVector.basicUnsafeSlice i n v-  basicUnsafeIndexM (V_Month v) i = GVector.basicUnsafeIndexM v i-  basicUnsafeCopy (MV_Month mv) (V_Month v) = GVector.basicUnsafeCopy mv v-  elemseq _ = seq--newtype instance UVector.MVector s DayOfMonth = MV_DayOfMonth (PVector.MVector s DayOfMonth)-newtype instance UVector.Vector DayOfMonth = V_DayOfMonth (PVector.Vector DayOfMonth)--instance UVector.Unbox DayOfMonth--instance MGVector.MVector UVector.MVector DayOfMonth where-  {-# INLINE basicLength #-}-  {-# INLINE basicUnsafeSlice #-}-  {-# INLINE basicOverlaps #-}-  {-# INLINE basicUnsafeNew #-}-  {-# INLINE basicInitialize #-}-  {-# INLINE basicUnsafeReplicate #-}-  {-# INLINE basicUnsafeRead #-}-  {-# INLINE basicUnsafeWrite #-}-  {-# INLINE basicClear #-}-  {-# INLINE basicSet #-}-  {-# INLINE basicUnsafeCopy #-}-  {-# INLINE basicUnsafeGrow #-}-  basicLength (MV_DayOfMonth v) = MGVector.basicLength v-  basicUnsafeSlice i n (MV_DayOfMonth v) = MV_DayOfMonth $ MGVector.basicUnsafeSlice i n v-  basicOverlaps (MV_DayOfMonth v1) (MV_DayOfMonth v2) = MGVector.basicOverlaps v1 v2-  basicUnsafeNew n = MV_DayOfMonth `liftM` MGVector.basicUnsafeNew n-  basicInitialize (MV_DayOfMonth v) = MGVector.basicInitialize v-  basicUnsafeReplicate n x = MV_DayOfMonth `liftM` MGVector.basicUnsafeReplicate n x-  basicUnsafeRead (MV_DayOfMonth v) i = MGVector.basicUnsafeRead v i-  basicUnsafeWrite (MV_DayOfMonth v) i x = MGVector.basicUnsafeWrite v i x-  basicClear (MV_DayOfMonth v) = MGVector.basicClear v-  basicSet (MV_DayOfMonth v) x = MGVector.basicSet v x-  basicUnsafeCopy (MV_DayOfMonth v1) (MV_DayOfMonth v2) = MGVector.basicUnsafeCopy v1 v2-  basicUnsafeMove (MV_DayOfMonth v1) (MV_DayOfMonth v2) = MGVector.basicUnsafeMove v1 v2-  basicUnsafeGrow (MV_DayOfMonth v) n = MV_DayOfMonth `liftM` MGVector.basicUnsafeGrow v n--instance GVector.Vector UVector.Vector DayOfMonth where-  {-# INLINE basicUnsafeFreeze #-}-  {-# INLINE basicUnsafeThaw #-}-  {-# INLINE basicLength #-}-  {-# INLINE basicUnsafeSlice #-}-  {-# INLINE basicUnsafeIndexM #-}-  {-# INLINE elemseq #-}-  basicUnsafeFreeze (MV_DayOfMonth v) = V_DayOfMonth `liftM` GVector.basicUnsafeFreeze v-  basicUnsafeThaw (V_DayOfMonth v) = MV_DayOfMonth `liftM` GVector.basicUnsafeThaw v-  basicLength (V_DayOfMonth v) = GVector.basicLength v-  basicUnsafeSlice i n (V_DayOfMonth v) = V_DayOfMonth $ GVector.basicUnsafeSlice i n v-  basicUnsafeIndexM (V_DayOfMonth v) i = GVector.basicUnsafeIndexM v i-  basicUnsafeCopy (MV_DayOfMonth mv) (V_DayOfMonth v) = GVector.basicUnsafeCopy mv v-  elemseq _ = seq+import Chronos
+ src/cbits/hs-time.c view
@@ -0,0 +1,22 @@+#ifdef __MACH__+#include <Availability.h>+#include <sys/time.h>++#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED++#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1012+//clock_gettime is not implemented on OSX+int clock_gettime(int clk_id, struct timespec* t) {+  struct timeval now;+  int rv = gettimeofday(&now, NULL);+  if (rv) return rv;+  t->tv_sec  = now.tv_sec;+  t->tv_nsec = now.tv_usec * 1000;+  return 0;+}+#else+#endif++#endif++#endif
test/Spec.hs view
@@ -1,324 +1,926 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeFamilies #-}--module Main (main) where--import Chronos.Types-import Data.List                            (intercalate)-import Test.QuickCheck                      (Gen, Arbitrary(..), choose, arbitraryBoundedEnum, genericShrink, elements)-import Test.QuickCheck.Property             (failed,succeeded,Result(..))-import Test.Framework                       (defaultMain, defaultMainWithOpts, testGroup, Test)-import qualified Test.Framework             as TF-import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.Framework.Providers.HUnit       (testCase)-import Test.HUnit                           (Assertion,(@?=),assertBool)--import Data.Text (Text)-import Data.ByteString (ByteString)-import Data.Text.Lazy.Builder (Builder)-import qualified Chronos.Internal.Conversion as Conv-import qualified Chronos.Calendar as Month-import qualified Chronos.Posix as Posix-import qualified Data.Text as Text-import qualified Data.Text.Lazy as LText-import qualified Data.Text.Lazy.Builder as Builder-import qualified Data.ByteString.Builder as BBuilder-import qualified Data.ByteString.Lazy as LByteString-import qualified Data.Attoparsec.Text as Atto-import qualified Data.Attoparsec.ByteString as AttoBS-import qualified Chronos.TimeOfDay.Text as TimeOfDayText-import qualified Chronos.TimeOfDay.ByteString.Char7 as TimeOfDayByteString-import qualified Chronos.Datetime.Text as DatetimeText-import qualified Chronos.OffsetDatetime.Text as OffsetDatetimeText-import qualified Chronos.Date.Text as DateText---- We increase the default number of property-based tests (provided--- by quickcheck) to 1000. Some of the encoding and decoding functions--- we test have special behavior when the minute is zero, which only--- happens in 1/60 of the generated scenarios. If we only generate 100--- scenarios, there is a 18.62% chance that none of these will have zero--- as the minute. If we increase this to 1000, that probability drops to--- almost nothing.-main :: IO ()-main = defaultMainWithOpts tests mempty-  { TF.ropt_test_options = Just mempty-    { TF.topt_maximum_generated_tests = Just 1000-    }-  }--tests :: [Test]-tests =-  [ testGroup "Time of Day"-    [ testGroup "Text"-      [ testGroup "Text Parsing Spec Tests"-        [ testCase "No Separator + microseconds"-            (timeOfDayParse Nothing "165956.246052" (TimeOfDay 16 59 56246052000))-        , testCase "Separator + microseconds"-            (timeOfDayParse (Just ':') "16:59:56.246052" (TimeOfDay 16 59 56246052000))-        , testCase "Separator + milliseconds"-            (timeOfDayParse (Just ':') "05:00:58.675" (TimeOfDay 05 00 58675000000))-        , testCase "Separator + deciseconds"-            (timeOfDayParse (Just ':') "05:00:58.9" (TimeOfDay 05 00 58900000000))-        , testCase "Separator + no subseconds"-            (timeOfDayParse (Just ':') "23:08:01" (TimeOfDay 23 8 1000000000))-        , testCase "Separator + nanoseconds"-            (timeOfDayParse (Just ':') "05:00:58.111222999" (TimeOfDay 05 00 58111222999))-        , testCase "Separator + 10e-18 seconds (truncate)"-            (timeOfDayParse (Just ':') "05:00:58.111222333444555666" (TimeOfDay 05 00 58111222333))-        , testCase "Separator + opt seconds (absent)"-            (parseMatch (TimeOfDayText.parser_HMS_opt_S (Just ':')) "00:01" (TimeOfDay 0 1 0))-        , testCase "Separator + opt seconds (present)"-            (parseMatch (TimeOfDayText.parser_HMS_opt_S (Just ':')) "00:01:05" (TimeOfDay 0 1 5000000000))-        , testCase "No Separator + opt seconds (absent)"-            (parseMatch (TimeOfDayText.parser_HMS_opt_S Nothing) "0001" (TimeOfDay 0 1 0))-        , testCase "No Separator + opt seconds (present)"-            (parseMatch (TimeOfDayText.parser_HMS_opt_S Nothing) "000105" (TimeOfDay 0 1 5000000000))-        ]-      , testGroup "Text Builder Spec Tests"-        [ testCase "No Separator + microseconds"-            (timeOfDayBuilder (SubsecondPrecisionFixed 6) Nothing "165956.246052" (TimeOfDay 16 59 56246052000))-        , testCase "Separator + microseconds"-            (timeOfDayBuilder (SubsecondPrecisionFixed 6) (Just ':') "16:59:56.246052" (TimeOfDay 16 59 56246052000))-        , testCase "Separator + no subseconds"-            (timeOfDayBuilder (SubsecondPrecisionFixed 0) (Just ':') "23:08:01" (TimeOfDay 23 8 1000000000))-        ]-      , testProperty "Text Builder Parser Isomorphism (H:M:S)" $ propEncodeDecodeIso-          (LText.toStrict . Builder.toLazyText . TimeOfDayText.builder_HMS (SubsecondPrecisionFixed 9) (Just ':'))-          (either (const Nothing) Just . Atto.parseOnly (TimeOfDayText.parser_HMS (Just ':')))-      ]-    , testGroup "ByteString"-      [ testGroup "Parser Spec Tests"-        [ testCase "No Separator + microseconds"-            (bsTimeOfDayParse Nothing "165956.246052" (TimeOfDay 16 59 56246052000))-        , testCase "Separator + microseconds"-            (bsTimeOfDayParse (Just ':') "16:59:56.246052" (TimeOfDay 16 59 56246052000))-        , testCase "Separator + milliseconds"-            (bsTimeOfDayParse (Just ':') "05:00:58.675" (TimeOfDay 05 00 58675000000))-        , testCase "Separator + deciseconds"-            (bsTimeOfDayParse (Just ':') "05:00:58.9" (TimeOfDay 05 00 58900000000))-        , testCase "Separator + no subseconds"-            (bsTimeOfDayParse (Just ':') "23:08:01" (TimeOfDay 23 8 1000000000))-        , testCase "Separator + nanoseconds"-            (bsTimeOfDayParse (Just ':') "05:00:58.111222999" (TimeOfDay 05 00 58111222999))-        , testCase "Separator + 10e-18 seconds (truncate)"-            (bsTimeOfDayParse (Just ':') "05:00:58.111222333444555666" (TimeOfDay 05 00 58111222333))-        , testCase "Separator + opt seconds (absent)"-            (bsParseMatch (TimeOfDayByteString.parser_HMS_opt_S (Just ':')) "00:01" (TimeOfDay 0 1 0))-        , testCase "Separator + opt seconds (present)"-            (bsParseMatch (TimeOfDayByteString.parser_HMS_opt_S (Just ':')) "00:01:05" (TimeOfDay 0 1 5000000000))-        , testCase "No Separator + opt seconds (absent)"-            (bsParseMatch (TimeOfDayByteString.parser_HMS_opt_S Nothing) "0001" (TimeOfDay 0 1 0))-        , testCase "No Separator + opt seconds (present)"-            (bsParseMatch (TimeOfDayByteString.parser_HMS_opt_S Nothing) "000105" (TimeOfDay 0 1 5000000000))-        ]-      , testGroup "Builder Spec Tests"-        [ testCase "No Separator + microseconds"-            (bsTimeOfDayBuilder (SubsecondPrecisionFixed 6) Nothing "165956.246052" (TimeOfDay 16 59 56246052000))-        , testCase "Separator + microseconds"-            (bsTimeOfDayBuilder (SubsecondPrecisionFixed 6) (Just ':') "16:59:56.246052" (TimeOfDay 16 59 56246052000))-        , testCase "Separator + no subseconds"-            (bsTimeOfDayBuilder (SubsecondPrecisionFixed 0) (Just ':') "23:08:01" (TimeOfDay 23 8 1000000000))-        ]-      , testProperty "Builder Parser Isomorphism (H:M:S)" $ propEncodeDecodeIso-          (LByteString.toStrict . BBuilder.toLazyByteString . TimeOfDayByteString.builder_HMS (SubsecondPrecisionFixed 9) (Just ':'))-          (either (const Nothing) Just . AttoBS.parseOnly (TimeOfDayByteString.parser_HMS (Just ':')))-      ]-    ]-  , testGroup "Date"-    [ testGroup "Parser Spec Tests" $-      [ testCase "No Separator"-          (dateParse Nothing "20160101" (Date (Year 2016) (Month 0) (DayOfMonth 1)))-      , testCase "Separator 1"-          (dateParse (Just '-') "2016-01-01" (Date (Year 2016) (Month 0) (DayOfMonth 1)))-      , testCase "Separator 2"-          (dateParse (Just '-') "1876-09-27" (Date (Year 1876) (Month 8) (DayOfMonth 27)))-      ]-    , testGroup "Builder Spec Tests" $-      [ testCase "No Separator"-          (dateBuilder Nothing "20160101" (Date (Year 2016) (Month 0) (DayOfMonth 1)))-      , testCase "Separator 1"-          (dateBuilder (Just '-') "2016-01-01" (Date (Year 2016) (Month 0) (DayOfMonth 1)))-      , testCase "Separator 2"-          (dateBuilder (Just '-') "1876-09-27" (Date (Year 1876) (Month 8) (DayOfMonth 27)))-      ]-    , testProperty "Builder Parser Isomorphism (Y-m-d)" $ propEncodeDecodeIso-        (LText.toStrict . Builder.toLazyText . DateText.builder_Ymd (Just '-'))-        (either (const Nothing) Just . Atto.parseOnly (DateText.parser_Ymd (Just '-')))-    ]-  , testGroup "Datetime"-    [ testProperty "Builder Parser Isomorphism (Y-m-dTH:M:S)" $ propEncodeDecodeIsoSettings-        (\format -> LText.toStrict . Builder.toLazyText . DatetimeText.builder_YmdHMS (SubsecondPrecisionFixed 9) format)-        (\format -> either (const Nothing) Just . Atto.parseOnly (DatetimeText.parser_YmdHMS format))-    , testProperty "Builder Parser Isomorphism (YmdHMS)" $ propEncodeDecodeIso-        (LText.toStrict . Builder.toLazyText . DatetimeText.builder_YmdHMS (SubsecondPrecisionFixed 9) (DatetimeFormat Nothing Nothing Nothing))-        (either (const Nothing) Just . Atto.parseOnly (DatetimeText.parser_YmdHMS (DatetimeFormat Nothing Nothing Nothing)))-    ]-  , testGroup "Offset Datetime"-    [ testGroup "Builder Spec Tests" $-      [ testCase "W3C" $ matchBuilder "1997-07-16T19:20:30.450+01:00" $-          OffsetDatetimeText.builderW3 $ OffsetDatetime-            ( Datetime-              ( Date (Year 1997) Month.july (DayOfMonth 16) )-              ( TimeOfDay 19 20 30450000000 )-            ) (Offset 60)-      ]-    , testProperty "Builder Parser Isomorphism (YmdHMSz)" $ propEncodeDecodeIsoSettings-        (\(offsetFormat,datetimeFormat) offsetDatetime ->-            LText.toStrict $ Builder.toLazyText $-              OffsetDatetimeText.builder_YmdHMSz offsetFormat (SubsecondPrecisionFixed 9) datetimeFormat offsetDatetime-        )-        (\(offsetFormat,datetimeFormat) input ->-            either (const Nothing) Just $ flip Atto.parseOnly input $-              OffsetDatetimeText.parser_YmdHMSz offsetFormat datetimeFormat-        )-    ]-  , testGroup "Posix Time"-    [ testCase "Get now" $ do-        now <- Posix.now-        assertBool "Current time is the beginning of the epoch." (now /= Posix.epoch)-    ]-  , testGroup "Conversion"-    [ testGroup "POSIX to UTC"-      [ -- Technically, this should fail, but it is very unlikely that quickcheck-        -- will pick a leapsecond. Actually, since we are going from posix to utc-        -- first, I think that this cannot fail.-        testProperty "Isomorphism" $ propEncodeDecodeFullIso Posix.toUtc Posix.fromUtc-      ]-    , testGroup "UTC to Datetime"-      [ testProperty "Isomorphism"-          $ propEncodeDecodeFullIso Conv.utcTimeToDatetime Conv.datetimeToUtcTime-      ]-    , testGroup "POSIX to Datetime"-      [ testCase "Epoch" $ Posix.toDatetime (PosixTime 0)-          @?= Datetime (Date (Year 1970) Month.january (DayOfMonth 1))-                       (TimeOfDay 0 0 0)-      , testCase "Billion Seconds" $ Posix.toDatetime (PosixTime $ 10 ^ 18)-          @?= Datetime (Date (Year 2001) Month.september (DayOfMonth 9))-                       (TimeOfDay 1 46 (40 * 10 ^ 9))-      , testProperty "Isomorphism" $ propEncodeDecodeFullIso Posix.toDatetime Posix.fromDatetime-      ]-    ]-  ]--failure :: String -> Result-failure msg = failed-  { reason = msg-  , theException = Nothing-  }--propEncodeDecodeFullIso :: (Eq a,Show a,Show b) => (a -> b) -> (b -> a) -> a -> Result-propEncodeDecodeFullIso f g a =-  let fa = f a-      gfa = g fa-   in if gfa == a-        then succeeded-        else failure $ concat-          [ "x:       ", show a, "\n"-          , "f(x):    ", show fa, "\n"-          , "g(f(x)): ", show gfa, "\n"-          ]--propEncodeDecodeIso :: Eq a => (a -> b) -> (b -> Maybe a) -> a -> Bool-propEncodeDecodeIso f g a = g (f a) == Just a--propEncodeDecodeIsoSettings :: (Eq a,Show a,Show b,Show e)-  => (e -> a -> b) -> (e -> b -> Maybe a) -> e -> a -> Result-propEncodeDecodeIsoSettings f g e a =-  let fa = f e a-      gfa = g e fa-   in if gfa == Just a-        then succeeded-        else failure $ concat-          [ "env:     ", show e, "\n"-          , "x:       ", show a, "\n"-          , "f(x):    ", show fa, "\n"-          , "g(f(x)): ", show gfa, "\n"-          ]--parseMatch :: (Eq a, Show a) => Atto.Parser a -> Text -> a -> Assertion-parseMatch p t expected = do-  Atto.parseOnly (p <* Atto.endOfInput) t-  @?= Right expected--bsParseMatch :: (Eq a, Show a) => AttoBS.Parser a -> ByteString -> a -> Assertion-bsParseMatch p t expected = do-  AttoBS.parseOnly (p <* AttoBS.endOfInput) t-  @?= Right expected--timeOfDayParse :: Maybe Char -> Text -> TimeOfDay -> Assertion-timeOfDayParse m t expected =-  Atto.parseOnly (TimeOfDayText.parser_HMS m <* Atto.endOfInput) t-  @?= Right expected--bsTimeOfDayParse :: Maybe Char -> ByteString -> TimeOfDay -> Assertion-bsTimeOfDayParse m t expected =-  AttoBS.parseOnly (TimeOfDayByteString.parser_HMS m <* AttoBS.endOfInput) t-  @?= Right expected---timeOfDayBuilder :: SubsecondPrecision -> Maybe Char -> Text -> TimeOfDay -> Assertion-timeOfDayBuilder sp m expected tod =-  LText.toStrict (Builder.toLazyText (TimeOfDayText.builder_HMS sp m tod))-  @?= expected--bsTimeOfDayBuilder :: SubsecondPrecision -> Maybe Char -> Text -> TimeOfDay -> Assertion-bsTimeOfDayBuilder sp m expected tod =-  LText.toStrict (Builder.toLazyText (TimeOfDayText.builder_HMS sp m tod))-  @?= expected--dateParse :: Maybe Char -> Text -> Date -> Assertion-dateParse m t expected =-  Atto.parseOnly (DateText.parser_Ymd m <* Atto.endOfInput) t-  @?= Right expected--dateBuilder :: Maybe Char -> Text -> Date -> Assertion-dateBuilder m expected tod =-  LText.toStrict (Builder.toLazyText (DateText.builder_Ymd m tod))-  @?= expected--matchBuilder :: Text -> Builder -> Assertion-matchBuilder a b = LText.toStrict (Builder.toLazyText b) @?= a--instance Arbitrary TimeOfDay where-  arbitrary = TimeOfDay-    <$> choose (0,23)-    <*> choose (0,59)-    -- never use leap seconds for property-based tests-    <*> choose (0,60000000000 - 1)--instance Arbitrary Date where-  arbitrary = Date-    <$> fmap Year (choose (1800,2100))-    <*> fmap Month (choose (0,11))-    <*> fmap DayOfMonth (choose (1,28))--instance Arbitrary Datetime where-  arbitrary = Datetime <$> arbitrary <*> arbitrary--instance Arbitrary UtcTime where-  arbitrary = UtcTime-    <$> fmap Day (choose (-100000,100000))-    <*> choose (0,24 * 60 * 60 * 1000000000 - 1)--instance Arbitrary OffsetDatetime where-  arbitrary = OffsetDatetime-    <$> arbitrary-    <*> arbitrary--instance Arbitrary DatetimeFormat where-  arbitrary = DatetimeFormat-    <$> arbitrary-    <*> elements [Nothing, Just '/', Just ':', Just '-']-    <*> arbitrary--instance Arbitrary OffsetFormat where-  arbitrary = arbitraryBoundedEnum-  shrink = genericShrink--deriving instance Arbitrary PosixTime--instance Arbitrary Offset where-  arbitrary = fmap Offset (choose ((-24) * 60, 24 * 60))-+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-orphans #-}++module Main (main) where++import qualified Chronos as C+import Chronos.Types+import qualified Data.Aeson as AE+import qualified Data.Attoparsec.ByteString as AttoBS+import qualified Data.Attoparsec.Text as Atto+import Data.ByteString (ByteString)+import qualified Data.ByteString.Builder as BBuilder+import qualified Data.ByteString.Lazy as LByteString+import Data.Int (Int64)+import Data.Text (Text)+import qualified Data.Text.Lazy as LText+import Data.Text.Lazy.Builder (Builder)+import qualified Data.Text.Lazy.Builder as Builder+import Test.Framework (Test, defaultMainWithOpts, testGroup)+import qualified Test.Framework as TF+import qualified Test.Framework.Providers.HUnit as PH+import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.HUnit (Assertion, assertBool, (@?=))+import Test.QuickCheck (Arbitrary (..), arbitraryBoundedEnum, choose, chooseInt, discard, elements, genericShrink, suchThat)+import Test.QuickCheck.Property (Result (..), failed, succeeded)+import qualified Torsor as T++-- We increase the default number of property-based tests (provided+-- by quickcheck) to 1000. Some of the encoding and decoding functions+-- we test have special behavior when the minute is zero, which only+-- happens in 1/60 of the generated scenarios. If we only generate 100+-- scenarios, there is a 18.62% chance that none of these will have zero+-- as the minute. If we increase this to 1000, that probability drops to+-- almost nothing.+main :: IO ()+main =+  defaultMainWithOpts+    tests+    mempty+      { TF.ropt_test_options =+          Just+            mempty+              { TF.topt_maximum_generated_tests = Just 1000+              , TF.topt_maximum_unsuitable_generated_tests = Just 10000+              }+      }++tests :: [Test]+tests =+  [ testGroup+      "Time of Day"+      [ testGroup+          "Text"+          [ testGroup+              "Text Parsing Spec Tests"+              [ PH.testCase+                  "No Separator + microseconds"+                  (timeOfDayParse Nothing "165956.246052" (TimeOfDay 16 59 56246052000))+              , PH.testCase+                  "Separator + microseconds"+                  (timeOfDayParse (Just ':') "16:59:56.246052" (TimeOfDay 16 59 56246052000))+              , PH.testCase+                  "Separator + milliseconds"+                  (timeOfDayParse (Just ':') "05:00:58.675" (TimeOfDay 05 00 58675000000))+              , PH.testCase+                  "Separator + deciseconds"+                  (timeOfDayParse (Just ':') "05:00:58.9" (TimeOfDay 05 00 58900000000))+              , PH.testCase+                  "Separator + no subseconds"+                  (timeOfDayParse (Just ':') "23:08:01" (TimeOfDay 23 8 1000000000))+              , PH.testCase+                  "Separator + nanoseconds"+                  (timeOfDayParse (Just ':') "05:00:58.111222999" (TimeOfDay 05 00 58111222999))+              , PH.testCase+                  "Separator + 10e-18 seconds (truncate)"+                  (timeOfDayParse (Just ':') "05:00:58.111222333444555666" (TimeOfDay 05 00 58111222333))+              , PH.testCase+                  "Separator + opt seconds (absent)"+                  (parseMatch (C.parser_HMS_opt_S (Just ':')) "00:01" (TimeOfDay 0 1 0))+              , PH.testCase+                  "Separator + opt seconds (present)"+                  (parseMatch (C.parser_HMS_opt_S (Just ':')) "00:01:05" (TimeOfDay 0 1 5000000000))+              , PH.testCase+                  "No Separator + opt seconds (absent)"+                  (parseMatch (C.parser_HMS_opt_S Nothing) "0001" (TimeOfDay 0 1 0))+              , PH.testCase+                  "No Separator + opt seconds (present)"+                  (parseMatch (C.parser_HMS_opt_S Nothing) "000105" (TimeOfDay 0 1 5000000000))+              ]+          , testGroup+              "Text Builder Spec Tests"+              [ PH.testCase+                  "No Separator + microseconds"+                  (timeOfDayBuilder (SubsecondPrecisionFixed 6) Nothing "165956.246052" (TimeOfDay 16 59 56246052000))+              , PH.testCase+                  "Separator + microseconds"+                  (timeOfDayBuilder (SubsecondPrecisionFixed 6) (Just ':') "16:59:56.246052" (TimeOfDay 16 59 56246052000))+              , PH.testCase+                  "Separator + no subseconds"+                  (timeOfDayBuilder (SubsecondPrecisionFixed 0) (Just ':') "23:08:01" (TimeOfDay 23 8 1000000000))+              ]+          , testProperty "Text Builder Parser Isomorphism (H:M:S)" $+              propEncodeDecodeIso+                (LText.toStrict . Builder.toLazyText . C.builder_HMS (SubsecondPrecisionFixed 9) (Just ':'))+                (either (const Nothing) Just . Atto.parseOnly (C.parser_HMS (Just ':')))+          ]+      , testGroup+          "ByteString"+          [ testGroup+              "Parser Spec Tests"+              [ PH.testCase+                  "No Separator + microseconds"+                  (bsTimeOfDayParse Nothing "165956.246052" (TimeOfDay 16 59 56246052000))+              , PH.testCase+                  "Separator + microseconds"+                  (bsTimeOfDayParse (Just ':') "16:59:56.246052" (TimeOfDay 16 59 56246052000))+              , PH.testCase+                  "Separator + milliseconds"+                  (bsTimeOfDayParse (Just ':') "05:00:58.675" (TimeOfDay 05 00 58675000000))+              , PH.testCase+                  "Separator + deciseconds"+                  (bsTimeOfDayParse (Just ':') "05:00:58.9" (TimeOfDay 05 00 58900000000))+              , PH.testCase+                  "Separator + no subseconds"+                  (bsTimeOfDayParse (Just ':') "23:08:01" (TimeOfDay 23 8 1000000000))+              , PH.testCase+                  "Separator + nanoseconds"+                  (bsTimeOfDayParse (Just ':') "05:00:58.111222999" (TimeOfDay 05 00 58111222999))+              , PH.testCase+                  "Separator + 10e-18 seconds (truncate)"+                  (bsTimeOfDayParse (Just ':') "05:00:58.111222333444555666" (TimeOfDay 05 00 58111222333))+              , PH.testCase+                  "Separator + opt seconds (absent)"+                  (bsParseMatch (C.parserUtf8_HMS_opt_S (Just ':')) "00:01" (TimeOfDay 0 1 0))+              , PH.testCase+                  "Separator + opt seconds (present)"+                  (bsParseMatch (C.parserUtf8_HMS_opt_S (Just ':')) "00:01:05" (TimeOfDay 0 1 5000000000))+              , PH.testCase+                  "No Separator + opt seconds (absent)"+                  (bsParseMatch (C.parserUtf8_HMS_opt_S Nothing) "0001" (TimeOfDay 0 1 0))+              , PH.testCase+                  "No Separator + opt seconds (present)"+                  (bsParseMatch (C.parserUtf8_HMS_opt_S Nothing) "000105" (TimeOfDay 0 1 5000000000))+              ]+          , testGroup+              "Builder Spec Tests"+              [ PH.testCase+                  "No Separator + microseconds"+                  (bsTimeOfDayBuilder (SubsecondPrecisionFixed 6) Nothing "165956.246052" (TimeOfDay 16 59 56246052000))+              , PH.testCase+                  "Separator + microseconds"+                  (bsTimeOfDayBuilder (SubsecondPrecisionFixed 6) (Just ':') "16:59:56.246052" (TimeOfDay 16 59 56246052000))+              , PH.testCase+                  "Separator + no subseconds"+                  (bsTimeOfDayBuilder (SubsecondPrecisionFixed 0) (Just ':') "23:08:01" (TimeOfDay 23 8 1000000000))+              ]+          , testProperty "Builder Parser Isomorphism (H:M:S)" $+              propEncodeDecodeIso+                (LByteString.toStrict . BBuilder.toLazyByteString . C.builderUtf8_HMS (SubsecondPrecisionFixed 9) (Just ':'))+                (either (const Nothing) Just . AttoBS.parseOnly (C.parserUtf8_HMS (Just ':')))+          ]+      ]+  , testGroup+      "Date"+      [ 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)))+          , PH.testCase+              "Separator 1"+              (dateBuilder (Just '-') "2016-01-01" (Date (Year 2016) (Month 0) (DayOfMonth 1)))+          , PH.testCase+              "Separator 2"+              (dateBuilder (Just '-') "1876-09-27" (Date (Year 1876) (Month 8) (DayOfMonth 27)))+          , PH.testCase+              "zero-pad year"+              (dateBuilder (Just '-') "0001-01-01" (Date (Year 1) (Month 0) (DayOfMonth 1)))+          ]+      , testProperty "Builder Parser Isomorphism (Y-m-d)" $+          propEncodeDecodeIso+            (LText.toStrict . Builder.toLazyText . C.builder_Ymd (Just '-'))+            (either (const Nothing) Just . Atto.parseOnly (C.parser_Ymd (Just '-')))+      ]+  , testGroup+      "Datetime"+      [ 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)))+          ]+      , testProperty "ISO-8601 Roundtrip" $+          propEncodeDecodeIso+            C.encodeShortTextIso8601Zulu+            ( \input -> case C.decodeShortTextIso8601 input of+                Just (OffsetDatetime dt (Offset 0)) -> Just dt+                _ -> Nothing+            )+      , testProperty "ISO-8601 Zoneless Roundtrip" $+          propEncodeDecodeIso+            C.encodeShortTextIso8601Zoneless+            ( \input -> case C.decodeShortTextIso8601Zoneless input of+                Just dt -> Just dt+                _ -> Nothing+            )+      ]+  , testGroup+      "Offset Datetime"+      [ testGroup "Builder Spec Tests" $+          [ PH.testCase "W3C" $+              matchBuilder "1997-07-16T19:20:30.450+01:00" $+                C.builderW3Cz $+                  OffsetDatetime+                    ( Datetime+                        (Date (Year 1997) C.july (DayOfMonth 16))+                        (TimeOfDay 19 20 30450000000)+                    )+                    (Offset 60)+          ]+      , testProperty "Builder Parser Isomorphism (YmdHMSz)" $+          propEncodeDecodeIsoSettings+            ( \(offsetFormat, datetimeFormat) offsetDatetime ->+                LText.toStrict $+                  Builder.toLazyText $+                    C.builder_YmdHMSz offsetFormat (SubsecondPrecisionFixed 9) datetimeFormat offsetDatetime+            )+            ( \(offsetFormat, datetimeFormat) input ->+                either (const Nothing) Just $+                  flip Atto.parseOnly input $+                    C.parser_YmdHMSz offsetFormat datetimeFormat+            )+      ]+  , testGroup+      "Posix Time"+      [ PH.testCase "Get now" $ do+          now <- C.now+          assertBool "Current time is the beginning of the epoch." (now /= C.epoch)+      ]+  , testGroup+      "Conversion"+      [ testGroup+          "POSIX to Datetime"+          [ PH.testCase "Epoch" $+              C.timeToDatetime (Time 0)+                @?= Datetime+                  (Date (Year 1970) C.january (DayOfMonth 1))+                  (TimeOfDay 0 0 0)+          , PH.testCase "Billion Seconds" $+              C.timeToDatetime (Time $ 10 ^ (18 :: Integer))+                @?= Datetime+                  (Date (Year 2001) C.september (DayOfMonth 9))+                  (TimeOfDay 1 46 (40 * 10 ^ (9 :: Integer)))+          , testProperty "Isomorphism" $ propEncodeDecodeFullIso C.timeToDatetime C.datetimeToTime+          ]+      ]+  , testGroup+      "TimeInterval"+      [ testGroup+          "within"+          [ testProperty "Verify that Time bounds are inside TimeInterval" propWithinInsideInterval+          , testProperty+              "Verify that the sum of Time and the span of TimeInterval is outside the interval"+              propWithinOutsideInterval+          ]+      , testGroup+          "timeIntervalToTimespan"+          [ PH.testCase+              "Verify Timespan correctness with TimeInterval"+              (C.timeIntervalToTimespan (TimeInterval (Time 13) (Time 25)) @?= Timespan 12)+          , PH.testCase+              "Verify Timespan correctness with equal TimeInterval bounds"+              (C.timeIntervalToTimespan (TimeInterval (Time 13) (Time 13)) @?= Timespan 0)+          , testProperty "Almost isomorphism" propEncodeDecodeTimeInterval+          ]+      , testGroup+          "whole"+          [ PH.testCase+              "Verify TimeInterval's bound correctness"+              (C.whole @?= TimeInterval (Time (minBound :: Int64)) (Time (maxBound :: Int64)))+          ]+      , testGroup+          "singleton"+          [ testProperty "Verify that upper and lower bound are always equals" propSingletonBoundsEquals+          ]+      , testGroup+          "width"+          [ testProperty "Verify Time bounds correctness with TimeSpan" propWidthVerifyBounds+          ]+      , testGroup+          "timeIntervalBuilder"+          [ 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)+      ]+  , testGroup+      "timeToOffsetDatetime"+      [ PH.testCase+          "EpochNeg4h"+          ( C.timeToOffsetDatetime (Offset (-240)) (Time 0)+              @?= OffsetDatetime+                ( Datetime+                    (Date (Year 1969) C.december (DayOfMonth 31))+                    (TimeOfDay 20 0 0)+                )+                (Offset (-240))+          )+      ]+  , testGroup+      "json"+      [ PH.testCase "Datetime" $+          let dt = Datetime (Date (Year 3000) (Month 11) (DayOfMonth 31)) (TimeOfDay 0 0 0)+           in AE.eitherDecode (AE.encode dt) @?= Right dt+      ]+  ]++failure :: String -> Result+failure msg =+  failed+    { reason = msg+    , theException = Nothing+    }++propEncodeDecodeFullIso :: (Eq a, Show a, Show b) => (a -> b) -> (b -> a) -> a -> Result+propEncodeDecodeFullIso f g a =+  let fa = f a+      gfa = g fa+   in if gfa == a+        then succeeded+        else+          failure $+            concat+              [ "x:       "+              , show a+              , "\n"+              , "f(x):    "+              , show fa+              , "\n"+              , "g(f(x)): "+              , show gfa+              , "\n"+              ]++propEncodeDecodeIso :: (Eq a) => (a -> b) -> (b -> Maybe a) -> a -> Bool+propEncodeDecodeIso f g a = g (f a) == Just a++propEncodeDecodeIsoSettings ::+  (Eq a, Show a, Show b, Show e) =>+  (e -> a -> b) ->+  (e -> b -> Maybe a) ->+  e ->+  a ->+  Result+propEncodeDecodeIsoSettings f g e a =+  let fa = f e a+      gfa = g e fa+   in if gfa == Just a+        then succeeded+        else+          failure $+            concat+              [ "env:     "+              , show e+              , "\n"+              , "x:       "+              , show a+              , "\n"+              , "f(x):    "+              , show fa+              , "\n"+              , "g(f(x)): "+              , show gfa+              , "\n"+              ]++parseMatch :: (Eq a, Show a) => Atto.Parser a -> Text -> a -> Assertion+parseMatch p t expected =+  do+    Atto.parseOnly (p <* Atto.endOfInput) t+    @?= Right expected++bsParseMatch :: (Eq a, Show a) => AttoBS.Parser a -> ByteString -> a -> Assertion+bsParseMatch p t expected =+  do+    AttoBS.parseOnly (p <* AttoBS.endOfInput) t+    @?= Right expected++timeOfDayParse :: Maybe Char -> Text -> TimeOfDay -> Assertion+timeOfDayParse m t expected =+  Atto.parseOnly (C.parser_HMS m <* Atto.endOfInput) t+    @?= Right expected++bsTimeOfDayParse :: Maybe Char -> ByteString -> TimeOfDay -> Assertion+bsTimeOfDayParse m t expected =+  AttoBS.parseOnly (C.parserUtf8_HMS m <* AttoBS.endOfInput) t+    @?= Right expected++timeOfDayBuilder :: SubsecondPrecision -> Maybe Char -> Text -> TimeOfDay -> Assertion+timeOfDayBuilder sp m expected tod =+  LText.toStrict (Builder.toLazyText (C.builder_HMS sp m tod))+    @?= expected++bsTimeOfDayBuilder :: SubsecondPrecision -> Maybe Char -> Text -> TimeOfDay -> Assertion+bsTimeOfDayBuilder sp m expected tod =+  LText.toStrict (Builder.toLazyText (C.builder_HMS sp m tod))+    @?= expected++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 =+  LText.toStrict (Builder.toLazyText (C.builder_Ymd m tod))+    @?= expected++matchBuilder :: Text -> Builder -> Assertion+matchBuilder a b = LText.toStrict (Builder.toLazyText b) @?= a++propWithinInsideInterval :: TimeInterval -> Bool+propWithinInsideInterval ti@(TimeInterval t0 t1) = C.within t1 ti && C.within t0 ti++propWithinOutsideInterval :: RelatedTimes -> Bool+propWithinOutsideInterval (RelatedTimes t ti@(TimeInterval t0 t1))+  | t == t0 = discard+  | t == t1 = discard+  | t1 <= t0 = discard+  | t0 < (Time 0) = discard+  | t < (Time 0) = discard+  | otherwise =+      let+        span' = C.timeIntervalToTimespan ti+        tm = T.add span' t+       in+        not $ C.within tm ti++propEncodeDecodeTimeInterval :: TimeInterval -> Bool+propEncodeDecodeTimeInterval ti@(TimeInterval t0 t1)+  | t0 < (Time 0) = discard+  | t0 >= t1 = discard+  | otherwise =+      let+        span' = C.timeIntervalToTimespan ti+        tm = T.add span' t0+       in+        t1 == tm++propSingletonBoundsEquals :: Time -> Bool+propSingletonBoundsEquals tm =+  let+    (TimeInterval (Time ti) (Time te)) = C.singleton tm+   in+    ti == te++propWidthVerifyBounds :: TimeInterval -> Bool+propWidthVerifyBounds ti@(TimeInterval (Time lower) (Time upper)) =+  let+    tiWidth = (getTimespan . C.width) ti+   in+    T.add lower tiWidth == upper && T.difference upper tiWidth == lower++propTimeIntervalBuilder :: Time -> Time -> Bool+propTimeIntervalBuilder t0 t1 =+  let+    (TimeInterval ti te) = (C.timeIntervalBuilder t0 t1)+   in+    (getTime te) >= (getTime ti)++instance Arbitrary TimeOfDay where+  arbitrary =+    TimeOfDay+      <$> choose (0, 23)+      <*> choose (0, 59)+      -- never use leap seconds for property-based tests+      <*> ( do+              subsecPrecision <- chooseInt (0, 9)+              secs <- chooseInt (0, 59)+              case subsecPrecision of+                0 -> pure (fromIntegral @Int @Int64 secs * 1_000_000_000)+                _ -> do+                  subsecs <- chooseInt (0, ((10 :: Int) ^ subsecPrecision) - 1)+                  let subsecs' = subsecs * ((10 :: Int) ^ (9 - subsecPrecision))+                  if subsecs' < 0 || subsecs' >= 1_000_000_000+                    then error "Mistake in Arbitrary instance for TimeOfDay"+                    else+                      pure+                        ( (fromIntegral @Int @Int64 secs * 1_000_000_000)+                            + (fromIntegral @Int @Int64 subsecs)+                        )+          )++instance Arbitrary Date where+  arbitrary =+    Date+      <$> fmap Year (choose (1800, 2100))+      <*> fmap Month (choose (0, 11))+      <*> fmap DayOfMonth (choose (1, 28))++instance Arbitrary Datetime where+  arbitrary = Datetime <$> arbitrary <*> arbitrary++-- instance Arbitrary UtcTime where+--   arbitrary = UtcTime+--     <$> fmap Day (choose (-100000,100000))+--     <*> choose (0,24 * 60 * 60 * 1000000000 - 1)++instance Arbitrary OffsetDatetime where+  arbitrary =+    OffsetDatetime+      <$> arbitrary+      <*> arbitrary++instance Arbitrary DatetimeFormat where+  arbitrary =+    DatetimeFormat+      <$> elements [Nothing, Just '/', Just ':', Just '-', Just '.']+      <*> elements [Nothing, Just '/', Just ':', Just '-', Just 'T']+      <*> elements [Nothing, Just ':', Just '-']+  shrink (DatetimeFormat a b c)+    | a == Just '-', b == Just 'T', c == Just ':' = []+    | otherwise = [DatetimeFormat (Just '-') (Just 'T') (Just ':')]++instance Arbitrary OffsetFormat where+  arbitrary = arbitraryBoundedEnum+  shrink = genericShrink++deriving instance Arbitrary Time++instance Arbitrary TimeInterval where+  arbitrary = do+    t0 <- arbitrary+    t1 <- suchThat arbitrary (>= t0)+    pure (TimeInterval t0 t1)++data RelatedTimes = RelatedTimes Time TimeInterval+  deriving (Show)++instance Arbitrary RelatedTimes where+  arbitrary = do+    ti@(TimeInterval t0 t1) <- arbitrary+    tm <- fmap Time (choose (getTime t0, getTime t1))+    pure $ RelatedTimes tm ti++instance Arbitrary Offset where+  arbitrary = fmap Offset (choose ((-24) * 60, 24 * 60))