diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -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).
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -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.)
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/bench/Bench.hs b/bench/Bench.hs
new file mode 100644
--- /dev/null
+++ b/bench/Bench.hs
@@ -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"
diff --git a/chronos.cabal b/chronos.cabal
--- a/chronos.cabal
+++ b/chronos.cabal
@@ -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
diff --git a/src/Chronos.hs b/src/Chronos.hs
new file mode 100644
--- /dev/null
+++ b/src/Chronos.hs
@@ -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
diff --git a/src/Chronos/Calendar.hs b/src/Chronos/Calendar.hs
deleted file mode 100644
--- a/src/Chronos/Calendar.hs
+++ /dev/null
@@ -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
-
diff --git a/src/Chronos/Date/ByteString/Char7.hs b/src/Chronos/Date/ByteString/Char7.hs
deleted file mode 100644
--- a/src/Chronos/Date/ByteString/Char7.hs
+++ /dev/null
@@ -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))
-
-
-
diff --git a/src/Chronos/Date/Text.hs b/src/Chronos/Date/Text.hs
deleted file mode 100644
--- a/src/Chronos/Date/Text.hs
+++ /dev/null
@@ -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))
-
-
diff --git a/src/Chronos/Datetime.hs b/src/Chronos/Datetime.hs
deleted file mode 100644
--- a/src/Chronos/Datetime.hs
+++ /dev/null
@@ -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 =
diff --git a/src/Chronos/Datetime/ByteString/Char7.hs b/src/Chronos/Datetime/ByteString/Char7.hs
deleted file mode 100644
--- a/src/Chronos/Datetime/ByteString/Char7.hs
+++ /dev/null
@@ -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)
-
-
-
-
-
diff --git a/src/Chronos/Datetime/Text.hs b/src/Chronos/Datetime/Text.hs
deleted file mode 100644
--- a/src/Chronos/Datetime/Text.hs
+++ /dev/null
@@ -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)
-
-
-
-
diff --git a/src/Chronos/Day.hs b/src/Chronos/Day.hs
deleted file mode 100644
--- a/src/Chronos/Day.hs
+++ /dev/null
@@ -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
-
diff --git a/src/Chronos/Internal.hs b/src/Chronos/Internal.hs
deleted file mode 100644
--- a/src/Chronos/Internal.hs
+++ /dev/null
@@ -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 #-}
-
diff --git a/src/Chronos/Internal/CTimespec.hs b/src/Chronos/Internal/CTimespec.hs
--- a/src/Chronos/Internal/CTimespec.hs
+++ b/src/Chronos/Internal/CTimespec.hs
@@ -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
-
-
-
diff --git a/src/Chronos/Internal/Conversion.hs b/src/Chronos/Internal/Conversion.hs
deleted file mode 100644
--- a/src/Chronos/Internal/Conversion.hs
+++ /dev/null
@@ -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 #-}
-
diff --git a/src/Chronos/Internal/Format.hs b/src/Chronos/Internal/Format.hs
deleted file mode 100644
--- a/src/Chronos/Internal/Format.hs
+++ /dev/null
@@ -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
-
diff --git a/src/Chronos/Locale/English.hs b/src/Chronos/Locale/English.hs
new file mode 100644
--- /dev/null
+++ b/src/Chronos/Locale/English.hs
@@ -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"
diff --git a/src/Chronos/Locale/English/Text.hs b/src/Chronos/Locale/English/Text.hs
deleted file mode 100644
--- a/src/Chronos/Locale/English/Text.hs
+++ /dev/null
@@ -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"
-
diff --git a/src/Chronos/Match.hs b/src/Chronos/Match.hs
deleted file mode 100644
--- a/src/Chronos/Match.hs
+++ /dev/null
@@ -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])
-
-
-
diff --git a/src/Chronos/Month.hs b/src/Chronos/Month.hs
deleted file mode 100644
--- a/src/Chronos/Month.hs
+++ /dev/null
@@ -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
-
diff --git a/src/Chronos/Nanoseconds.hs b/src/Chronos/Nanoseconds.hs
deleted file mode 100644
--- a/src/Chronos/Nanoseconds.hs
+++ /dev/null
@@ -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 *)
-
diff --git a/src/Chronos/OffsetDatetime/ByteString/Char7.hs b/src/Chronos/OffsetDatetime/ByteString/Char7.hs
deleted file mode 100644
--- a/src/Chronos/OffsetDatetime/ByteString/Char7.hs
+++ /dev/null
@@ -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
-
diff --git a/src/Chronos/OffsetDatetime/Text.hs b/src/Chronos/OffsetDatetime/Text.hs
deleted file mode 100644
--- a/src/Chronos/OffsetDatetime/Text.hs
+++ /dev/null
@@ -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
diff --git a/src/Chronos/Posix.hs b/src/Chronos/Posix.hs
deleted file mode 100644
--- a/src/Chronos/Posix.hs
+++ /dev/null
@@ -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)
-
diff --git a/src/Chronos/Tai.hs b/src/Chronos/Tai.hs
deleted file mode 100644
--- a/src/Chronos/Tai.hs
+++ /dev/null
@@ -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)
-
diff --git a/src/Chronos/TimeOfDay/ByteString/Char7.hs b/src/Chronos/TimeOfDay/ByteString/Char7.hs
deleted file mode 100644
--- a/src/Chronos/TimeOfDay/ByteString/Char7.hs
+++ /dev/null
@@ -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
-
-
diff --git a/src/Chronos/TimeOfDay/Text.hs b/src/Chronos/TimeOfDay/Text.hs
deleted file mode 100644
--- a/src/Chronos/TimeOfDay/Text.hs
+++ /dev/null
@@ -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
-
diff --git a/src/Chronos/Types.hs b/src/Chronos/Types.hs
--- a/src/Chronos/Types.hs
+++ b/src/Chronos/Types.hs
@@ -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
diff --git a/src/cbits/hs-time.c b/src/cbits/hs-time.c
new file mode 100644
--- /dev/null
+++ b/src/cbits/hs-time.c
@@ -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
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -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))
