diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,43 @@
 # Change Log
 
+## [1.16.0.1] - 2026-07-20
+
+- fix gregorianPaschalMoon and gregorianEaster
+- fix negative rollover differences in diffGregorianDurationRollOver and diffJulianDurationRollOver
+- fix case-insensitive parsing for AM/PM markers, UTC, and known timezone names
+- fix Unix timezone fallback offset sign and DST abbreviation
+- fix JavaScript getTimeZone offsets and summer flag around DST transitions
+- fix numeric parsers accepting overflowing bounded values
+- fix systemToPOSIXTime for SystemTime leap-second values
+
+## [1.16] - 2026-05-03
+
+- add periodIn
+- use template-haskell-lift rather than template-haskell for Lift instances on GHC 9.14 and later
+- support GHC 9.14
+- improve documentation, test, build, CI
+
+## [1.15] - 2025-08-04
+
+- support compiler / GHC backends (with CI):
+  - JavaScript
+  - WebAssembly
+  - MicroHs
+- add diffTimeOfDay
+- add \[add/diff\]\[UTC/Local\]Duration\[Clip/Rollover\]
+- add patterns for DiffTime & NominalDiffTime
+- UNIXish formatting/parsing:
+  - add instance ParseTime DayOfWeek
+  - make use of %s specifiers in parsing various types
+  - instances for Quarter and QuarterOfYear
+  - %v specifier for quarter-of-year
+- ISO8601 formatting/parsing:
+  - loosen up parsing time-zone modifiers
+- add Lift instances to all types (really this time)
+- hide Data.Time.Format.Internal
+- add periodToDayClip
+- fix MonthDay and QuarterDay clipping
+
 ## [1.14] - 2024-03-10
 - add Lift instances to all types
 - add Generic instances to all types that have exposed constructors
diff --git a/configure b/configure
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.71 for Haskell time package 1.14.
+# Generated by GNU Autoconf 2.71 for Haskell time package 1.16.0.1.
 #
 # Report bugs to <ashley@semantic.org>.
 #
@@ -610,8 +610,8 @@
 # Identity of this package.
 PACKAGE_NAME='Haskell time package'
 PACKAGE_TARNAME='time'
-PACKAGE_VERSION='1.14'
-PACKAGE_STRING='Haskell time package 1.14'
+PACKAGE_VERSION='1.16.0.1'
+PACKAGE_STRING='Haskell time package 1.16.0.1'
 PACKAGE_BUGREPORT='ashley@semantic.org'
 PACKAGE_URL=''
 
@@ -1258,7 +1258,7 @@
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures Haskell time package 1.14 to adapt to many kinds of systems.
+\`configure' configures Haskell time package 1.16.0.1 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1320,7 +1320,7 @@
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of Haskell time package 1.14:";;
+     short | recursive ) echo "Configuration of Haskell time package 1.16.0.1:";;
    esac
   cat <<\_ACEOF
 
@@ -1406,7 +1406,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-Haskell time package configure 1.14
+Haskell time package configure 1.16.0.1
 generated by GNU Autoconf 2.71
 
 Copyright (C) 2021 Free Software Foundation, Inc.
@@ -1736,7 +1736,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by Haskell time package $as_me 1.14, which was
+It was created by Haskell time package $as_me 1.16.0.1, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   $ $0$ac_configure_args_raw
@@ -4334,7 +4334,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by Haskell time package $as_me 1.14, which was
+This file was extended by Haskell time package $as_me 1.16.0.1, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -4389,7 +4389,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config='$ac_cs_config_escaped'
 ac_cs_version="\\
-Haskell time package config.status 1.14
+Haskell time package config.status 1.16.0.1
 configured by $0, generated by GNU Autoconf 2.71,
   with options \\"\$ac_cs_config\\"
 
diff --git a/configure.ac b/configure.ac
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,4 @@
-AC_INIT([Haskell time package],[1.14],[ashley@semantic.org],[time])
+AC_INIT([Haskell time package],[1.16.0.1],[ashley@semantic.org],[time])
 
 # Safety check: Ensure that we are in the correct source directory.
 AC_CONFIG_SRCDIR([lib/include/HsTime.h])
diff --git a/lib/Data/Format.hs b/lib/Data/Format.hs
--- a/lib/Data/Format.hs
+++ b/lib/Data/Format.hs
@@ -15,6 +15,7 @@
     literalFormat,
     specialCaseShowFormat,
     specialCaseFormat,
+    specialCaseReadFormat,
     optionalFormat,
     casesFormat,
     optionalSignFormat,
@@ -28,6 +29,7 @@
 import Data.Char
 import Data.Void
 import Text.ParserCombinators.ReadP
+import Text.Read (readMaybe)
 import Prelude hiding (fail)
 
 class IsoVariant f where
@@ -177,6 +179,13 @@
     in
         MkFormat s' r'
 
+specialCaseReadFormat :: (a, String) -> Format a -> Format a
+specialCaseReadFormat (val, str) (MkFormat s r) =
+    let
+        r' = r <++ (string str >> return val)
+    in
+        MkFormat s r'
+
 optionalFormat :: Eq a => a -> Format a -> Format a
 optionalFormat val = specialCaseFormat (val, "")
 
@@ -205,13 +214,40 @@
 readSign NegSign = option id $ char '-' >> return negate
 readSign PosNegSign = (char '+' >> return id) +++ (char '-' >> return negate)
 
+readDigits :: Maybe Int -> ReadP String
+readDigits mdigitcount =
+    case mdigitcount of
+        Just digitcount -> count digitcount $ satisfy isDigit
+        Nothing -> munch1 isDigit
+
+readMaybeP :: Read t => String -> ReadP t
+readMaybeP str =
+    case readMaybe str of
+        Just t -> return t
+        Nothing -> pfail
+
+fromIntegerExact :: Integral t => Integer -> Maybe t
+fromIntegerExact i =
+    let
+        t = fromInteger i
+    in
+        if toInteger t == i
+            then Just t
+            else Nothing
+
+readIntegerNumber :: Integral t => SignOption -> Maybe Int -> ReadP t
+readIntegerNumber signOpt mdigitcount = do
+    sign <- readSign signOpt
+    digits <- readDigits mdigitcount
+    i <- readMaybeP digits
+    case fromIntegerExact $ sign i of
+        Just t -> return t
+        Nothing -> pfail
+
 readNumber :: (Num t, Read t) => SignOption -> Maybe Int -> Bool -> ReadP t
 readNumber signOpt mdigitcount allowDecimal = do
     sign <- readSign signOpt
-    digits <-
-        case mdigitcount of
-            Just digitcount -> count digitcount $ satisfy isDigit
-            Nothing -> munch1 isDigit
+    digits <- readDigits mdigitcount
     moredigits <-
         case allowDecimal of
             False -> return ""
@@ -220,41 +256,50 @@
                     _ <- char '.' +++ char ','
                     dd <- munch1 isDigit
                     return $ '.' : dd
-    return $ sign $ read $ digits ++ moredigits
+    a <- readMaybeP $ digits ++ moredigits
+    return $ sign a
 
-zeroPad :: Maybe Int -> String -> String
-zeroPad Nothing s = s
-zeroPad (Just i) s = replicate (i - length s) '0' ++ s
+zeroPad :: Maybe Int -> String -> Maybe String
+zeroPad Nothing s = Just s
+zeroPad (Just i) s =
+    let
+        padCount = i - length s
+    in
+        if padCount >= 0
+            then Just $ replicate padCount '0' ++ s
+            else Nothing
 
 trimTrailing :: String -> String
-trimTrailing "" = ""
-trimTrailing "." = ""
-trimTrailing s
-    | last s == '0' = trimTrailing $ init s
-trimTrailing s = s
+trimTrailing =
+    (\s -> if s == "." then "" else s)
+        . reverse
+        . dropWhile (== '0')
+        . reverse
 
 showNumber :: Show t => SignOption -> Maybe Int -> t -> Maybe String
 showNumber signOpt mdigitcount t =
     let
-        showIt str =
+        showIt str = do
             let
                 (intPart, decPart) = break ((==) '.') str
-            in
-                (zeroPad mdigitcount intPart) ++ trimTrailing decPart
+            paddedIntPart <- zeroPad mdigitcount intPart
+            return $ paddedIntPart ++ trimTrailing decPart
     in
         case show t of
             ('-' : str) ->
                 case signOpt of
                     NoSign -> Nothing
-                    _ -> Just $ '-' : showIt str
-            str ->
-                Just $
-                    case signOpt of
-                        PosNegSign -> '+' : showIt str
-                        _ -> showIt str
+                    _ -> do
+                        s <- showIt str
+                        return $ '-' : s
+            str -> do
+                s <- showIt str
+                return $ case signOpt of
+                    PosNegSign -> '+' : s
+                    _ -> s
 
-integerFormat :: (Show t, Read t, Num t) => SignOption -> Maybe Int -> Format t
-integerFormat signOpt mdigitcount = MkFormat (showNumber signOpt mdigitcount) (readNumber signOpt mdigitcount False)
+integerFormat :: (Show t, Integral t) => SignOption -> Maybe Int -> Format t
+integerFormat signOpt mdigitcount = MkFormat (showNumber signOpt mdigitcount) (readIntegerNumber signOpt mdigitcount)
 
 decimalFormat :: (Show t, Read t, Num t) => SignOption -> Maybe Int -> Format t
 decimalFormat signOpt mdigitcount = MkFormat (showNumber signOpt mdigitcount) (readNumber signOpt mdigitcount True)
diff --git a/lib/Data/Time/Calendar.hs b/lib/Data/Time/Calendar.hs
--- a/lib/Data/Time/Calendar.hs
+++ b/lib/Data/Time/Calendar.hs
@@ -1,14 +1,78 @@
 {-# LANGUAGE Safe #-}
 
 module Data.Time.Calendar (
-    module Data.Time.Calendar.Days,
+    -- * Days
+    Day (..),
+    addDays,
+    diffDays,
+
+    -- * DayPeriod
+    DayPeriod (..),
+    periodAllDays,
+    periodIn,
+    periodLength,
+    periodFromDay,
+    periodToDay,
+    periodToDayClip,
+    periodToDayValid,
+
+    -- * Calendar Duration
     module Data.Time.Calendar.CalendarDiffDays,
-    module Data.Time.Calendar.Gregorian,
-    module Data.Time.Calendar.Week,
+
+    -- * Year, month and day
+    Year,
+    pattern CommonEra,
+    pattern BeforeCommonEra,
+    MonthOfYear,
+    pattern January,
+    pattern February,
+    pattern March,
+    pattern April,
+    pattern May,
+    pattern June,
+    pattern July,
+    pattern August,
+    pattern September,
+    pattern October,
+    pattern November,
+    pattern December,
+    DayOfMonth,
+
+    -- * Gregorian calendar
+    toGregorian,
+    fromGregorian,
+    pattern YearMonthDay,
+    fromGregorianValid,
+    showGregorian,
+    gregorianMonthLength,
+    -- calendrical arithmetic
+    -- e.g. "one month after March 31st"
+    addGregorianMonthsClip,
+    addGregorianMonthsRollOver,
+    addGregorianYearsClip,
+    addGregorianYearsRollOver,
+    addGregorianDurationClip,
+    addGregorianDurationRollOver,
+    diffGregorianDurationClip,
+    diffGregorianDurationRollOver,
+    -- re-exported from OrdinalDate
+    isLeapYear,
+
+    -- * Week
+    DayOfWeek (..),
+    dayOfWeek,
+    dayOfWeekDiff,
+    firstDayOfWeekOnAfter,
+    weekAllDays,
+    weekFirstDay,
+    weekLastDay,
 ) where
 
 import Data.Time.Calendar.CalendarDiffDays
 import Data.Time.Calendar.Days
 import Data.Time.Calendar.Gregorian
+import Data.Time.Calendar.MonthDay
+import Data.Time.Calendar.OrdinalDate
+import Data.Time.Calendar.Types
 import Data.Time.Calendar.Week
 import Data.Time.Format ()
diff --git a/lib/Data/Time/Calendar/CalendarDiffDays.hs b/lib/Data/Time/Calendar/CalendarDiffDays.hs
--- a/lib/Data/Time/Calendar/CalendarDiffDays.hs
+++ b/lib/Data/Time/Calendar/CalendarDiffDays.hs
@@ -1,30 +1,22 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
-module Data.Time.Calendar.CalendarDiffDays (
-    -- * Calendar Duration
-    module Data.Time.Calendar.CalendarDiffDays,
-) where
+module Data.Time.Calendar.CalendarDiffDays where
 
 import Control.DeepSeq
 import Data.Data
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 data CalendarDiffDays = CalendarDiffDays
     { cdMonths :: Integer
     , cdDays :: Integer
     }
-    deriving
-        ( Eq
-        , -- | @since 1.9.2
-          Data
-        , -- | @since 1.9.2
-          Typeable
-        , -- | @since 1.14
-          TH.Lift
-        , -- | @since 1.14
-          Generic
-        )
+    deriving (Eq, Typeable, Data, Generic, TH.Lift)
 
 instance NFData CalendarDiffDays where
     rnf (CalendarDiffDays m d) = rnf m `seq` rnf d `seq` ()
diff --git a/lib/Data/Time/Calendar/Days.hs b/lib/Data/Time/Calendar/Days.hs
--- a/lib/Data/Time/Calendar/Days.hs
+++ b/lib/Data/Time/Calendar/Days.hs
@@ -1,31 +1,24 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
-module Data.Time.Calendar.Days (
-    -- * Days
-    Day (..),
-    addDays,
-    diffDays,
-
-    -- * DayPeriod
-    DayPeriod (..),
-    periodAllDays,
-    periodLength,
-    periodFromDay,
-    periodToDay,
-    periodToDayValid,
-) where
+module Data.Time.Calendar.Days where
 
 import Control.DeepSeq
 import Data.Data
 import Data.Ix
+import Data.Time.Calendar.Private
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 -- | The Modified Julian Day is a standard count of days, with zero being the day 1858-11-17.
 newtype Day = ModifiedJulianDay
     { toModifiedJulianDay :: Integer
     }
-    deriving (Eq, Ord, Data, Typeable, TH.Lift, Generic)
+    deriving (Eq, Ord, Typeable, Data, Generic, TH.Lift)
 
 instance NFData Day where
     rnf (ModifiedJulianDay a) = rnf a
@@ -72,6 +65,12 @@
 periodAllDays :: DayPeriod p => p -> [Day]
 periodAllDays p = [periodFirstDay p .. periodLastDay p]
 
+-- | Test whether a day is in a given period.
+--
+-- @since 1.16
+periodIn :: DayPeriod p => p -> Day -> Bool
+periodIn p d = (d >= periodFirstDay p) && (d <= periodLastDay p)
+
 -- | The number of days in this period.
 --
 -- @since 1.12.1
@@ -96,6 +95,12 @@
 -- @since 1.12.1
 periodToDay :: DayPeriod p => p -> Int -> Day
 periodToDay p i = addDays (toInteger $ pred i) $ periodFirstDay p
+
+-- | Inverse of 'periodFromDay', clipping the day number to the period.
+--
+-- @since 1.15
+periodToDayClip :: DayPeriod p => p -> Int -> Day
+periodToDayClip p i = periodToDay p $ clip 1 (periodLength p) i
 
 -- | Validating inverse of 'periodFromDay'.
 --
diff --git a/lib/Data/Time/Calendar/Easter.hs b/lib/Data/Time/Calendar/Easter.hs
--- a/lib/Data/Time/Calendar/Easter.hs
+++ b/lib/Data/Time/Calendar/Easter.hs
@@ -37,7 +37,7 @@
     century = (div year 100) + 1
     shiftedEpact = mod (14 + 11 * (mod year 19) - (div (3 * century) 4) + (div (5 + 8 * century) 25)) 30
     adjustedEpact =
-        if shiftedEpact == 0 || ((shiftedEpact == 1) && (mod year 19 < 10))
+        if shiftedEpact == 0 || ((shiftedEpact == 1) && (mod year 19 > 10))
             then shiftedEpact + 1
             else shiftedEpact
 
diff --git a/lib/Data/Time/Calendar/Gregorian.hs b/lib/Data/Time/Calendar/Gregorian.hs
--- a/lib/Data/Time/Calendar/Gregorian.hs
+++ b/lib/Data/Time/Calendar/Gregorian.hs
@@ -1,55 +1,14 @@
 {-# LANGUAGE Safe #-}
-{-# LANGUAGE TypeSynonymInstances #-}
 
 {-# OPTIONS -fno-warn-orphans #-}
 
-module Data.Time.Calendar.Gregorian (
-    -- * Year, month and day
-    Year,
-    pattern CommonEra,
-    pattern BeforeCommonEra,
-    MonthOfYear,
-    pattern January,
-    pattern February,
-    pattern March,
-    pattern April,
-    pattern May,
-    pattern June,
-    pattern July,
-    pattern August,
-    pattern September,
-    pattern October,
-    pattern November,
-    pattern December,
-    DayOfMonth,
-
-    -- * Gregorian calendar
-    toGregorian,
-    fromGregorian,
-    pattern YearMonthDay,
-    fromGregorianValid,
-    showGregorian,
-    gregorianMonthLength,
-    -- calendrical arithmetic
-    -- e.g. "one month after March 31st"
-    addGregorianMonthsClip,
-    addGregorianMonthsRollOver,
-    addGregorianYearsClip,
-    addGregorianYearsRollOver,
-    addGregorianDurationClip,
-    addGregorianDurationRollOver,
-    diffGregorianDurationClip,
-    diffGregorianDurationRollOver,
-    -- re-exported from OrdinalDate
-    isLeapYear,
-) where
+module Data.Time.Calendar.Gregorian where
 
 import Data.Time.Calendar.CalendarDiffDays
 import Data.Time.Calendar.Days
 import Data.Time.Calendar.MonthDay
 import Data.Time.Calendar.OrdinalDate
 import Data.Time.Calendar.Private
-import Data.Time.Calendar.Types
 
 -- | Convert to proleptic Gregorian calendar.
 toGregorian :: Day -> (Year, MonthOfYear, DayOfMonth)
@@ -131,7 +90,9 @@
 addGregorianDurationRollOver :: CalendarDiffDays -> Day -> Day
 addGregorianDurationRollOver (CalendarDiffDays m d) day = addDays d $ addGregorianMonthsRollOver m day
 
--- | Calendrical difference, with as many whole months as possible
+-- | Calendrical difference, with as many whole months as possible.
+-- Has the property @addGregorianDurationClip (diffGregorianDurationClip d2 d1) d1 = d2@.
+-- For example, 2027-03-01 - 2027-01-31 = 1 month and 1 day.
 diffGregorianDurationClip :: Day -> Day -> CalendarDiffDays
 diffGregorianDurationClip day2 day1 =
     let
@@ -155,6 +116,8 @@
         CalendarDiffDays ymAllowed $ diffDays day2 dayAllowed
 
 -- | Calendrical difference, with as many whole months as possible.
+-- Has the property @addGregorianDurationRollOver (diffGregorianDurationRollOver d2 d1) d1 = d2@.
+-- For example, 2027-03-01 - 2027-01-31 = 29 days.
 diffGregorianDurationRollOver :: Day -> Day -> CalendarDiffDays
 diffGregorianDurationRollOver day2 day1 =
     let
@@ -174,7 +137,7 @@
                 dayAllowed = addGregorianDurationRollOver (CalendarDiffDays mdiff 0) day1
                 dd = diffDays day2 dayAllowed
             in
-                if dd <= 0 then CalendarDiffDays mdiff dd else findpos (succ mdiff)
+                if dd <= 0 then CalendarDiffDays mdiff dd else findneg (succ mdiff)
     in
         if day2 >= day1
             then findpos ymdiff
diff --git a/lib/Data/Time/Calendar/Julian.hs b/lib/Data/Time/Calendar/Julian.hs
--- a/lib/Data/Time/Calendar/Julian.hs
+++ b/lib/Data/Time/Calendar/Julian.hs
@@ -17,6 +17,8 @@
     pattern December,
     DayOfMonth,
     DayOfYear,
+
+    -- * Year and day format
     module Data.Time.Calendar.JulianYearDay,
     toJulian,
     fromJulian,
@@ -166,7 +168,7 @@
                 dayAllowed = addJulianDurationRollOver (CalendarDiffDays mdiff 0) day1
                 dd = diffDays day2 dayAllowed
             in
-                if dd <= 0 then CalendarDiffDays mdiff dd else findpos (succ mdiff)
+                if dd <= 0 then CalendarDiffDays mdiff dd else findneg (succ mdiff)
     in
         if day2 >= day1
             then findpos ymdiff
diff --git a/lib/Data/Time/Calendar/JulianYearDay.hs b/lib/Data/Time/Calendar/JulianYearDay.hs
--- a/lib/Data/Time/Calendar/JulianYearDay.hs
+++ b/lib/Data/Time/Calendar/JulianYearDay.hs
@@ -1,9 +1,6 @@
 {-# LANGUAGE Safe #-}
 
-module Data.Time.Calendar.JulianYearDay (
-    -- * Year and day format
-    module Data.Time.Calendar.JulianYearDay,
-) where
+module Data.Time.Calendar.JulianYearDay where
 
 import Data.Time.Calendar.Days
 import Data.Time.Calendar.Private
diff --git a/lib/Data/Time/Calendar/Month.hs b/lib/Data/Time/Calendar/Month.hs
--- a/lib/Data/Time/Calendar/Month.hs
+++ b/lib/Data/Time/Calendar/Month.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
 -- | An absolute count of common calendar months.
@@ -18,14 +19,19 @@
 import Data.Time.Calendar.Days
 import Data.Time.Calendar.Gregorian
 import Data.Time.Calendar.Private
+import Data.Time.Calendar.Types
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 import Text.ParserCombinators.ReadP
 import Text.Read
 
 -- | An absolute count of common calendar months.
 -- Number is equal to @(year * 12) + (monthOfYear - 1)@.
-newtype Month = MkMonth Integer deriving (Eq, Ord, Data, Typeable, TH.Lift, Generic)
+newtype Month = MkMonth Integer deriving (Eq, Ord, Typeable, Data, Generic, TH.Lift)
 
 instance NFData Month where
     rnf (MkMonth m) = rnf m
@@ -74,7 +80,7 @@
 -- Invalid months of year will be clipped to the correct range.
 pattern YearMonth :: Year -> MonthOfYear -> Month
 pattern YearMonth y my <-
-    MkMonth ((\m -> divMod' m 12) -> (y, succ . fromInteger -> my))
+    MkMonth ((\m -> divMod' m 12) -> (y, (succ . fromInteger -> my)))
     where
         YearMonth y my = MkMonth $ (y * 12) + toInteger (pred $ clip 1 12 my)
 
@@ -91,7 +97,7 @@
 pattern MonthDay m dm <-
     (periodFromDay -> (m, dm))
     where
-        MonthDay = periodToDay
+        MonthDay = periodToDayClip
 
 fromMonthDayValid :: Month -> DayOfMonth -> Maybe Day
 fromMonthDayValid = periodToDayValid
diff --git a/lib/Data/Time/Calendar/Quarter.hs b/lib/Data/Time/Calendar/Quarter.hs
--- a/lib/Data/Time/Calendar/Quarter.hs
+++ b/lib/Data/Time/Calendar/Quarter.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
 -- | Year quarters.
@@ -23,12 +24,16 @@
 import Data.Time.Calendar.Private
 import Data.Time.Calendar.Types
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 import Text.ParserCombinators.ReadP
 import Text.Read
 
 -- | Quarters of each year. Each quarter corresponds to three months.
-data QuarterOfYear = Q1 | Q2 | Q3 | Q4 deriving (Eq, Ord, Data, Typeable, Read, Show, Ix, TH.Lift, Generic)
+data QuarterOfYear = Q1 | Q2 | Q3 | Q4 deriving (Eq, Ord, Read, Show, Ix, Typeable, Data, Generic, TH.Lift)
 
 -- | maps Q1..Q4 to 1..4
 instance Enum QuarterOfYear where
@@ -55,7 +60,7 @@
 
 -- | An absolute count of year quarters.
 -- Number is equal to @(year * 4) + (quarterOfYear - 1)@.
-newtype Quarter = MkQuarter Integer deriving (Eq, Ord, Data, Typeable, Generic)
+newtype Quarter = MkQuarter Integer deriving (Eq, Ord, Typeable, Data, Generic, TH.Lift)
 
 instance NFData Quarter where
     rnf (MkQuarter m) = rnf m
@@ -113,7 +118,7 @@
 -- | Bidirectional abstract constructor.
 pattern YearQuarter :: Year -> QuarterOfYear -> Quarter
 pattern YearQuarter y qy <-
-    MkQuarter ((\q -> divMod' q 4) -> (y, toEnum . succ . fromInteger -> qy))
+    MkQuarter ((\q -> divMod' q 4) -> (y, (toEnum . succ . fromInteger -> qy)))
     where
         YearQuarter y qy = MkQuarter $ (y * 4) + toInteger (pred $ fromEnum qy)
 
@@ -142,6 +147,6 @@
 pattern QuarterDay q dq <-
     (periodFromDay -> (q, dq))
     where
-        QuarterDay = periodToDay
+        QuarterDay = periodToDayClip
 
 {-# COMPLETE QuarterDay #-}
diff --git a/lib/Data/Time/Calendar/Week.hs b/lib/Data/Time/Calendar/Week.hs
--- a/lib/Data/Time/Calendar/Week.hs
+++ b/lib/Data/Time/Calendar/Week.hs
@@ -1,15 +1,7 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
-module Data.Time.Calendar.Week (
-    -- * Week
-    DayOfWeek (..),
-    dayOfWeek,
-    dayOfWeekDiff,
-    firstDayOfWeekOnAfter,
-    weekAllDays,
-    weekFirstDay,
-    weekLastDay,
-) where
+module Data.Time.Calendar.Week where
 
 import Control.DeepSeq
 import Data.Data
@@ -17,7 +9,11 @@
 import Data.Ix
 import Data.Time.Calendar.Days
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 data DayOfWeek
     = Monday
@@ -27,7 +23,7 @@
     | Friday
     | Saturday
     | Sunday
-    deriving (Eq, Show, Read, Data, Typeable, Ord, Ix, TH.Lift, Generic)
+    deriving (Eq, Ord, Ix, Show, Read, Typeable, Data, Generic, TH.Lift)
 
 instance NFData DayOfWeek where
     rnf Monday = ()
diff --git a/lib/Data/Time/Calendar/WeekDate.hs b/lib/Data/Time/Calendar/WeekDate.hs
--- a/lib/Data/Time/Calendar/WeekDate.hs
+++ b/lib/Data/Time/Calendar/WeekDate.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
 -- | Week-based calendars
@@ -23,7 +24,11 @@
 import Data.Time.Calendar.OrdinalDate
 import Data.Time.Calendar.Private
 import Data.Time.Calendar.Week
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 data FirstWeekType
     = -- | first week is the first whole week of the year
@@ -124,7 +129,7 @@
 -- Invalid week values will be clipped to the correct range.
 pattern YearWeekDay :: Year -> WeekOfYear -> DayOfWeek -> Day
 pattern YearWeekDay y wy dw <-
-    (toWeekDate -> (y, wy, toEnum -> dw))
+    (toWeekDate -> (y, wy, (toEnum -> dw)))
     where
         YearWeekDay y wy dw = fromWeekDate y wy (fromEnum dw)
 
diff --git a/lib/Data/Time/Clock.hs b/lib/Data/Time/Clock.hs
--- a/lib/Data/Time/Clock.hs
+++ b/lib/Data/Time/Clock.hs
@@ -2,10 +2,37 @@
 
 -- | Types and functions for UTC and UT1
 module Data.Time.Clock (
-    module Data.Time.Clock.Internal.UniversalTime,
-    module Data.Time.Clock.Internal.DiffTime,
-    module Data.Time.Clock.Internal.UTCTime,
-    module Data.Time.Clock.Internal.NominalDiffTime,
+    -- * Universal Time
+
+    -- | Time as measured by the Earth.
+    UniversalTime (..),
+
+    -- * Absolute intervals
+    DiffTime,
+    pattern Picoseconds,
+    pattern Seconds,
+    pattern Minutes,
+    pattern Hours,
+    secondsToDiffTime,
+    picosecondsToDiffTime,
+    diffTimeToPicoseconds,
+
+    -- * UTC
+
+    -- | UTC is time as measured by a clock, corrected to keep pace with the earth by adding or removing
+    -- occasional seconds, known as \"leap seconds\".
+    -- These corrections are not predictable and are announced with six month's notice.
+    -- No table of these corrections is provided, as any program compiled with it would become
+    -- out of date in six months.
+    --
+    -- If you don't care about leap seconds, use 'UTCTime' and 'NominalDiffTime' for your clock calculations,
+    -- and you'll be fine.
+    UTCTime (..),
+    NominalDiffTime,
+    pattern Nominal,
+    secondsToNominalDiffTime,
+    nominalDiffTimeToSeconds,
+    nominalDay,
     module Data.Time.Clock.Internal.UTCDiff,
     getCurrentTime,
     getTime_resolution,
diff --git a/lib/Data/Time/Clock/Internal/AbsoluteTime.hs b/lib/Data/Time/Clock/Internal/AbsoluteTime.hs
--- a/lib/Data/Time/Clock/Internal/AbsoluteTime.hs
+++ b/lib/Data/Time/Clock/Internal/AbsoluteTime.hs
@@ -1,25 +1,23 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
 -- | TAI and leap-second maps for converting to UTC: most people won't need this module.
-module Data.Time.Clock.Internal.AbsoluteTime (
-    -- TAI arithmetic
-    AbsoluteTime,
-    taiEpoch,
-    addAbsoluteTime,
-    diffAbsoluteTime,
-    taiNominalDayStart,
-) where
+module Data.Time.Clock.Internal.AbsoluteTime where
 
 import Control.DeepSeq
 import Data.Data
 import Data.Time.Calendar.Days
 import Data.Time.Clock.Internal.DiffTime
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 -- | AbsoluteTime is TAI, time as measured by a clock.
 newtype AbsoluteTime
     = MkAbsoluteTime DiffTime
-    deriving (Eq, Ord, Data, Typeable, TH.Lift)
+    deriving (Eq, Ord, Typeable, Data, TH.Lift)
 
 instance NFData AbsoluteTime where
     rnf (MkAbsoluteTime a) = rnf a
diff --git a/lib/Data/Time/Clock/Internal/CTimespec.hsc b/lib/Data/Time/Clock/Internal/CTimespec.hsc
--- a/lib/Data/Time/Clock/Internal/CTimespec.hsc
+++ b/lib/Data/Time/Clock/Internal/CTimespec.hsc
@@ -6,7 +6,7 @@
 
 #include "HsTimeConfig.h"
 
-#if !defined(mingw32_HOST_OS) && HAVE_CLOCK_GETTIME
+#if !defined(mingw32_HOST_OS) && HAVE_CLOCK_GETTIME && !defined(__MHS__)
 
 import Foreign
 import Foreign.C
@@ -34,18 +34,31 @@
 
 foreign import ccall unsafe "time.h clock_gettime"
     clock_gettime :: ClockID -> Ptr CTimespec -> IO CInt
-foreign import ccall unsafe "time.h clock_getres"
-    clock_getres :: ClockID -> Ptr CTimespec -> IO CInt
 
-#else
+#else /* defined(javascript_HOST_ARCH) */
 
 foreign import capi unsafe "time.h clock_gettime"
     clock_gettime :: ClockID -> Ptr CTimespec -> IO CInt
+
+#endif /* defined(javascript_HOST_ARCH) */
+
+-- | Get the current time from the given clock.
+clockGetTime :: ClockID -> IO CTimespec
+clockGetTime clockid = alloca (\ptspec -> do
+    throwErrnoIfMinus1_ "clock_gettime" $ clock_gettime clockid ptspec
+    peek ptspec
+    )
+
+#if defined(javascript_HOST_ARCH)
+
+clockGetRes :: ClockID -> IO (Either Errno CTimespec)
+clockGetRes _ = return $ Right $ MkCTimespec 0 0
+
+#else /* defined(javascript_HOST_ARCH) */
+
 foreign import capi unsafe "time.h clock_getres"
     clock_getres :: ClockID -> Ptr CTimespec -> IO CInt
 
-#endif
-
 -- | Get the resolution of the given clock.
 clockGetRes :: ClockID -> IO (Either Errno CTimespec)
 clockGetRes clockid = alloca $ \ptspec -> do
@@ -58,28 +71,23 @@
             errno <- getErrno
             return $ Left errno
 
--- | Get the current time from the given clock.
-clockGetTime :: ClockID -> IO CTimespec
-clockGetTime clockid = alloca (\ptspec -> do
-    throwErrnoIfMinus1_ "clock_gettime" $ clock_gettime clockid ptspec
-    peek ptspec
-    )
+#endif /* defined(javascript_HOST_ARCH) */
 
 #if defined(javascript_HOST_ARCH)
 -- JS backend doesn't support foreign imports with capi convention
 clock_REALTIME :: ClockID
 clock_REALTIME = #{const CLOCK_REALTIME}
-#else
+#else /* defined(javascript_HOST_ARCH) */
 foreign import capi unsafe "HsTime.h value HS_CLOCK_REALTIME" clock_REALTIME :: ClockID
-#endif
+#endif /* defined(javascript_HOST_ARCH) */
 
 clock_TAI :: Maybe ClockID
 clock_TAI =
 #if defined(CLOCK_TAI)
     Just #{const CLOCK_TAI}
-#else
+#else /* defined(CLOCK_TAI) */
     Nothing
-#endif
+#endif /* defined(CLOCK_TAI) */
 
 realtimeRes :: CTimespec
 realtimeRes = unsafePerformIO $ do
@@ -95,4 +103,5 @@
         Left _ -> return Nothing
         Right res -> return $ Just res
 
-#endif
+#endif /* !defined(mingw32_HOST_OS) && HAVE_CLOCK_GETTIME */
+
diff --git a/lib/Data/Time/Clock/Internal/CTimeval.hs b/lib/Data/Time/Clock/Internal/CTimeval.hs
--- a/lib/Data/Time/Clock/Internal/CTimeval.hs
+++ b/lib/Data/Time/Clock/Internal/CTimeval.hs
@@ -26,7 +26,7 @@
         pokeElemOff (castPtr p) 0 s
         pokeElemOff (castPtr p) 1 mus
 
-#if defined(javascript_HOST_ARCH)
+#if defined(javascript_HOST_ARCH) || defined(__MHS__)
 
 foreign import ccall unsafe "sys/time.h gettimeofday" gettimeofday :: Ptr CTimeval -> Ptr () -> IO CInt
 
diff --git a/lib/Data/Time/Clock/Internal/DiffTime.hs b/lib/Data/Time/Clock/Internal/DiffTime.hs
--- a/lib/Data/Time/Clock/Internal/DiffTime.hs
+++ b/lib/Data/Time/Clock/Internal/DiffTime.hs
@@ -1,23 +1,21 @@
-{-# LANGUAGE InstanceSigs #-}
-{-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Trustworthy #-}
 
-module Data.Time.Clock.Internal.DiffTime (
-    -- * Absolute intervals
-    DiffTime,
-    secondsToDiffTime,
-    picosecondsToDiffTime,
-    diffTimeToPicoseconds,
-) where
+module Data.Time.Clock.Internal.DiffTime where
 
 import Control.DeepSeq
 import Data.Data
 import Data.Fixed
+#ifdef __GLASGOW_HASKELL__
 import GHC.Read
+#endif
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 import Text.ParserCombinators.ReadP
-import Text.ParserCombinators.ReadPrec
+import Text.Read
 
 -- | This is a length of time, as measured by a clock.
 -- Conversion functions such as 'fromInteger' and 'realToFrac' will treat it as seconds.
@@ -26,7 +24,7 @@
 -- It has a precision of one picosecond (= 10^-12 s). Enumeration functions will treat it as picoseconds.
 newtype DiffTime
     = MkDiffTime Pico
-    deriving (Eq, Ord, Data, Typeable)
+    deriving (Eq, Ord, Typeable, Data, TH.Lift)
 
 instance NFData DiffTime where
     rnf (MkDiffTime t) = rnf t
@@ -78,10 +76,31 @@
     ceiling (MkDiffTime a) = ceiling a
     floor (MkDiffTime a) = floor a
 
--- Let GHC derive the instances when 'Fixed' has 'TH.Lift' instance.
-instance TH.Lift DiffTime where
-    liftTyped :: TH.Quote m => DiffTime -> TH.Code m DiffTime
-    liftTyped (MkDiffTime (MkFixed a)) = [||MkDiffTime (MkFixed $$(TH.liftTyped a))||]
+pattern Picoseconds :: Integer -> DiffTime
+pattern Picoseconds a <- (diffTimeToPicoseconds -> a)
+    where
+        Picoseconds a = picosecondsToDiffTime a
+
+{-# COMPLETE Picoseconds #-}
+
+pattern Seconds :: Pico -> DiffTime
+pattern Seconds a = MkDiffTime a
+
+{-# COMPLETE Seconds #-}
+
+pattern Minutes :: Pico -> DiffTime
+pattern Minutes a <- Seconds ((/ 60) -> a)
+    where
+        Minutes a = Seconds $ a * 60
+
+{-# COMPLETE Minutes #-}
+
+pattern Hours :: Pico -> DiffTime
+pattern Hours a <- Minutes ((/ 60) -> a)
+    where
+        Hours a = Minutes $ a * 60
+
+{-# COMPLETE Hours #-}
 
 -- | Create a 'DiffTime' which represents an integral number of seconds.
 secondsToDiffTime :: Integer -> DiffTime
diff --git a/lib/Data/Time/Clock/Internal/NominalDiffTime.hs b/lib/Data/Time/Clock/Internal/NominalDiffTime.hs
--- a/lib/Data/Time/Clock/Internal/NominalDiffTime.hs
+++ b/lib/Data/Time/Clock/Internal/NominalDiffTime.hs
@@ -1,38 +1,46 @@
-{-# LANGUAGE InstanceSigs #-}
-{-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Trustworthy #-}
 
-module Data.Time.Clock.Internal.NominalDiffTime (
-    NominalDiffTime,
-    secondsToNominalDiffTime,
-    nominalDiffTimeToSeconds,
-    nominalDay,
-) where
+module Data.Time.Clock.Internal.NominalDiffTime where
 
 import Control.DeepSeq
 import Data.Data
 import Data.Fixed
+#ifdef __GLASGOW_HASKELL__
 import GHC.Read
+#endif
+import Data.Time.Clock.Internal.DiffTime
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 import Text.ParserCombinators.ReadP
 import Text.ParserCombinators.ReadPrec
 
 -- | This is a length of time, as measured by UTC.
--- It has a precision of 10^-12 s.
+-- It has a precision of one picosecond (10^-12 s).
 --
 -- Conversion functions such as 'fromInteger' and 'realToFrac' will treat it as seconds.
 -- For example, @(0.010 :: NominalDiffTime)@ corresponds to 10 milliseconds.
 --
--- It has a precision of one picosecond (= 10^-12 s). Enumeration functions will treat it as picoseconds.
+-- Enumeration functions will treat it as picoseconds.
 --
 -- It ignores leap-seconds, so it's not necessarily a fixed amount of clock time.
 -- For instance, 23:00 UTC + 2 hours of NominalDiffTime = 01:00 UTC (+ 1 day),
 -- regardless of whether a leap-second intervened.
 newtype NominalDiffTime
     = MkNominalDiffTime Pico
-    deriving (Eq, Ord, Data, Typeable)
+    deriving (Eq, Ord, Typeable, Data, TH.Lift)
 
+-- | convert from DiffTime
+pattern Nominal :: DiffTime -> NominalDiffTime
+pattern Nominal dt <- MkNominalDiffTime (realToFrac -> dt)
+    where
+        Nominal dt = MkNominalDiffTime $ realToFrac dt
+
+{-# COMPLETE Nominal #-}
+
 -- | Create a 'NominalDiffTime' from a number of seconds.
 --
 -- @since 1.9.1
@@ -44,11 +52,6 @@
 -- @since 1.9.1
 nominalDiffTimeToSeconds :: NominalDiffTime -> Pico
 nominalDiffTimeToSeconds (MkNominalDiffTime t) = t
-
--- Let GHC derive the instances when 'Fixed' has 'TH.Lift' instance.
-instance TH.Lift NominalDiffTime where
-    liftTyped :: TH.Quote m => NominalDiffTime -> TH.Code m NominalDiffTime
-    liftTyped (MkNominalDiffTime (MkFixed a)) = [||MkNominalDiffTime (MkFixed $$(TH.liftTyped a))||]
 
 instance NFData NominalDiffTime where
     rnf (MkNominalDiffTime t) = rnf t
diff --git a/lib/Data/Time/Clock/Internal/SystemTime.hs b/lib/Data/Time/Clock/Internal/SystemTime.hs
--- a/lib/Data/Time/Clock/Internal/SystemTime.hs
+++ b/lib/Data/Time/Clock/Internal/SystemTime.hs
@@ -1,6 +1,8 @@
 {-# LANGUAGE CPP #-}
 
+#if !defined(__MHS__)
 #include "HsTimeConfig.h"
+#endif
 
 #if defined(mingw32_HOST_OS) || !defined(HAVE_CLOCK_GETTIME)
 {-# LANGUAGE Safe #-}
@@ -21,11 +23,15 @@
 import Data.Time.Clock.Internal.DiffTime
 import Data.Word
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 #ifdef mingw32_HOST_OS
 import qualified System.Win32.Time as Win32
-#elif defined(HAVE_CLOCK_GETTIME)
+#elif defined(HAVE_CLOCK_GETTIME) && !defined(__MHS__)
 import Data.Time.Clock.Internal.CTimespec
 import Foreign.C.Types (CLong(..), CTime(..))
 #else
@@ -41,7 +47,7 @@
     { systemSeconds :: {-# UNPACK #-} !Int64
     , systemNanoseconds :: {-# UNPACK #-} !Word32
     }
-    deriving (Eq, Ord, Show, Data, Typeable, TH.Lift, Generic)
+    deriving (Eq, Ord, Show, Typeable, Data, Generic, TH.Lift)
 
 instance NFData SystemTime where
     rnf a = a `seq` ()
@@ -74,7 +80,7 @@
 getTime_resolution = 100E-9 -- 100ns
 
 getTAISystemTime = Nothing
-#elif defined(HAVE_CLOCK_GETTIME)
+#elif defined(HAVE_CLOCK_GETTIME) && !defined(__MHS__)
 -- Use hi-res clock_gettime
 timespecToSystemTime :: CTimespec -> SystemTime
 timespecToSystemTime (MkCTimespec (CTime s) (CLong ns)) = (MkSystemTime (fromIntegral s) (fromIntegral ns))
diff --git a/lib/Data/Time/Clock/Internal/UTCTime.hs b/lib/Data/Time/Clock/Internal/UTCTime.hs
--- a/lib/Data/Time/Clock/Internal/UTCTime.hs
+++ b/lib/Data/Time/Clock/Internal/UTCTime.hs
@@ -1,25 +1,18 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
-module Data.Time.Clock.Internal.UTCTime (
-    -- * UTC
-
-    -- | UTC is time as measured by a clock, corrected to keep pace with the earth by adding or removing
-    -- occasional seconds, known as \"leap seconds\".
-    -- These corrections are not predictable and are announced with six month's notice.
-    -- No table of these corrections is provided, as any program compiled with it would become
-    -- out of date in six months.
-    --
-    -- If you don't care about leap seconds, use 'UTCTime' and 'NominalDiffTime' for your clock calculations,
-    -- and you'll be fine.
-    UTCTime (..),
-) where
+module Data.Time.Clock.Internal.UTCTime where
 
 import Control.DeepSeq
 import Data.Data
 import Data.Time.Calendar.Days
 import Data.Time.Clock.Internal.DiffTime
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 -- | This is the simplest representation of UTC.
 -- It consists of the day number, and a time offset from midnight.
@@ -30,16 +23,7 @@
     , utctDayTime :: DiffTime
     -- ^ the time from midnight, 0 <= t < 86401s (because of leap-seconds)
     }
-    deriving (Data, Typeable, TH.Lift, Generic)
+    deriving (Eq, Ord, Typeable, Data, Generic, TH.Lift)
 
 instance NFData UTCTime where
     rnf (UTCTime d t) = rnf d `seq` rnf t `seq` ()
-
-instance Eq UTCTime where
-    (UTCTime da ta) == (UTCTime db tb) = (da == db) && (ta == tb)
-
-instance Ord UTCTime where
-    compare (UTCTime da ta) (UTCTime db tb) =
-        case (compare da db) of
-            EQ -> compare ta tb
-            cmp -> cmp
diff --git a/lib/Data/Time/Clock/Internal/UniversalTime.hs b/lib/Data/Time/Clock/Internal/UniversalTime.hs
--- a/lib/Data/Time/Clock/Internal/UniversalTime.hs
+++ b/lib/Data/Time/Clock/Internal/UniversalTime.hs
@@ -1,23 +1,23 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
-module Data.Time.Clock.Internal.UniversalTime (
-    -- * Universal Time
-
-    -- | Time as measured by the Earth.
-    UniversalTime (..),
-) where
+module Data.Time.Clock.Internal.UniversalTime where
 
 import Control.DeepSeq
 import Data.Data
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
 import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 -- | The Modified Julian Date is the day with the fraction of the day, measured from UT midnight.
 -- It's used to represent UT1, which is time as measured by the earth's rotation, adjusted for various wobbles.
 newtype UniversalTime = ModJulianDate
     { getModJulianDate :: Rational
     }
-    deriving (Eq, Ord, Data, Typeable, TH.Lift, Generic)
+    deriving (Eq, Ord, Typeable, Data, Generic, TH.Lift)
 
 instance NFData UniversalTime where
     rnf (ModJulianDate a) = rnf a
diff --git a/lib/Data/Time/Clock/POSIX.hs b/lib/Data/Time/Clock/POSIX.hs
--- a/lib/Data/Time/Clock/POSIX.hs
+++ b/lib/Data/Time/Clock/POSIX.hs
@@ -45,7 +45,12 @@
     (fromInteger (diffDays d systemEpochDay) * posixDayLength) + min posixDayLength (realToFrac t)
 
 systemToPOSIXTime :: SystemTime -> POSIXTime
-systemToPOSIXTime (MkSystemTime s ns) = (fromIntegral s) + (fromIntegral ns) * 1E-9
+systemToPOSIXTime (MkSystemTime s ns) =
+    let
+        (days, timeSeconds) = s `divMod` 86400
+        time = fromIntegral timeSeconds + fromIntegral ns * 1E-9
+    in
+        fromIntegral days * posixDayLength + min posixDayLength time
 
 -- | Get the current POSIX time from the system clock.
 getPOSIXTime :: IO POSIXTime
diff --git a/lib/Data/Time/Clock/TAI.hs b/lib/Data/Time/Clock/TAI.hs
--- a/lib/Data/Time/Clock/TAI.hs
+++ b/lib/Data/Time/Clock/TAI.hs
@@ -1,14 +1,19 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
 {-# OPTIONS -fno-warn-orphans #-}
 
 -- | TAI and leap-second maps for converting to UTC: most people won't need this module.
 module Data.Time.Clock.TAI (
-    -- TAI arithmetic
-    module Data.Time.Clock.Internal.AbsoluteTime,
-    -- leap-second map type
+    -- * Absolute Time
+    AbsoluteTime,
+    taiEpoch,
+    addAbsoluteTime,
+    diffAbsoluteTime,
+    taiNominalDayStart,
+
+    -- * Leap-Second Map
     LeapSecondMap,
-    -- conversion between UTC and TAI with map
     utcDayLength,
     utcToTAITime,
     taiToUTCTime,
@@ -23,6 +28,9 @@
 import Data.Time.Clock.Internal.SystemTime
 import Data.Time.Clock.System
 import Data.Time.LocalTime
+#ifdef __MHS__
+import Data.Tuple.Instances
+#endif
 
 instance Show AbsoluteTime where
     show t = show (utcToLocalTime utc (fromJust (taiToUTCTime (const (Just 0)) t))) ++ " TAI" -- ugly, but standard apparently
diff --git a/lib/Data/Time/Format.hs b/lib/Data/Time/Format.hs
--- a/lib/Data/Time/Format.hs
+++ b/lib/Data/Time/Format.hs
@@ -4,10 +4,25 @@
     -- * UNIX-style formatting
     FormatTime (),
     formatTime,
-    module Data.Time.Format.Parse,
+
+    -- * UNIX-style parsing
+    parseTimeM,
+    parseTimeMultipleM,
+    parseTimeOrError,
+    readSTime,
+    readPTime,
+    ParseTime (),
+
+    -- * Locale
+    TimeLocale (..),
+    defaultTimeLocale,
+    iso8601DateFormat,
+    rfc822DateFormat,
 ) where
 
 import Data.Time.Format.Format.Class
 import Data.Time.Format.Format.Instances ()
 import Data.Time.Format.ISO8601 ()
+import Data.Time.Format.Locale
 import Data.Time.Format.Parse
+import Data.Time.Format.Parse.Class
diff --git a/lib/Data/Time/Format/Format/Class.hs b/lib/Data/Time/Format/Format/Class.hs
--- a/lib/Data/Time/Format/Format/Class.hs
+++ b/lib/Data/Time/Format/Format/Class.hs
@@ -211,15 +211,26 @@
 --
 -- [@%A@] day of week, long form ('fst' from 'wDays' @locale@), @Sunday@ - @Saturday@
 --
--- === 'Month'
--- For 'Month' (and 'Day' and 'LocalTime' and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):
+-- === 'QuarterOfYear'
+-- For 'QuarterOfYear' (and 'Quarter' and 'Month' and 'Day' and 'LocalTime' and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):
 --
+-- [@%v@] quarter number, @1@ - @4@
+--
+-- === 'Quarter'
+-- For 'Quarter' (and 'Month' and 'Day' and 'LocalTime' and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):
+--
 -- [@%Y@] year, no padding. Note @%0Y@ and @%_Y@ pad to four chars
 --
 -- [@%y@] year of century, 0-padded to two chars, @00@ - @99@
 --
 -- [@%C@] century, no padding. Note @%0C@ and @%_C@ pad to two chars
 --
+-- [@%v@] quarter number, @1@ - @4@
+--
+--
+-- === 'Month'
+-- For 'Month' (and 'Day' and 'LocalTime' and 'ZonedTime' and 'UTCTime' and 'UniversalTime'):
+--
 -- [@%B@] month name, long form ('fst' from 'months' @locale@), @January@ - @December@
 --
 -- [@%b@, @%h@] month name, short form ('snd' from 'months' @locale@), @Jan@ - @Dec@
@@ -241,9 +252,13 @@
 --
 -- [@%j@] day of year, 0-padded to three chars, @001@ - @366@
 --
--- [@%f@] century for Week Date format, no padding. Note @%0f@ and @%_f@ pad to two chars
+-- [@%G@] year for ISO 8601 Week Date format. Note @%0G@ and @%_G@ pad to four chars
 --
--- [@%V@] week of year for Week Date format, 0-padded to two chars, @01@ - @53@
+-- [@%g@] year of century for ISO 8601 Week Date format, 0-padded to two chars, @00@ - @99@
+--
+-- [@%f@] century for ISO 8601 Week Date format, no padding. Note @%0f@ and @%_f@ pad to two chars
+--
+-- [@%V@] week of year for ISO 8601 Week Date format, 0-padded to two chars, @01@ - @53@
 --
 -- [@%U@] week of year where weeks start on Sunday (as 'sundayStartWeek'), 0-padded to two chars, @00@ - @53@
 --
diff --git a/lib/Data/Time/Format/Format/Instances.hs b/lib/Data/Time/Format/Format/Instances.hs
--- a/lib/Data/Time/Format/Format/Instances.hs
+++ b/lib/Data/Time/Format/Format/Instances.hs
@@ -15,6 +15,7 @@
 import Data.Time.Calendar.Month
 import Data.Time.Calendar.OrdinalDate
 import Data.Time.Calendar.Private
+import Data.Time.Calendar.Quarter
 import Data.Time.Calendar.Week
 import Data.Time.Calendar.WeekDate
 import Data.Time.Clock.Internal.DiffTime
@@ -110,11 +111,20 @@
     formatCharacter _ 'A' = Just $ formatString $ \locale wd -> fst $ (wDays locale) !! (mod (fromEnum wd) 7)
     formatCharacter _ _ = Nothing
 
-instance FormatTime Month where
+instance FormatTime QuarterOfYear where
+    -- Quarter of Year
+    formatCharacter _ 'v' = Just $ formatNumber False 1 '0' fromEnum
+    formatCharacter _ _ = Nothing
+
+instance FormatTime Quarter where
     -- Year Count
-    formatCharacter _ 'Y' = Just $ formatNumber False 4 '0' $ \(YearMonth y _) -> y
-    formatCharacter _ 'y' = Just $ formatNumber True 2 '0' $ \(YearMonth y _) -> mod100 y
-    formatCharacter _ 'C' = Just $ formatNumber False 2 '0' $ \(YearMonth y _) -> div100 y
+    formatCharacter _ 'Y' = Just $ formatNumber False 4 '0' $ \(YearQuarter y _) -> y
+    formatCharacter _ 'y' = Just $ formatNumber True 2 '0' $ \(YearQuarter y _) -> mod100 y
+    formatCharacter _ 'C' = Just $ formatNumber False 2 '0' $ \(YearQuarter y _) -> div100 y
+    -- Default
+    formatCharacter alt c = mapFormatCharacter (\(YearQuarter _ q) -> q) $ formatCharacter alt c
+
+instance FormatTime Month where
     -- Month of Year
     formatCharacter _ 'B' =
         Just $ formatString $ \locale (YearMonth _ my) -> fst $ (months locale) !! (my - 1)
@@ -124,7 +134,7 @@
         Just $ formatString $ \locale (YearMonth _ my) -> snd $ (months locale) !! (my - 1)
     formatCharacter _ 'm' = Just $ formatNumber True 2 '0' $ \(YearMonth _ m) -> m
     -- Default
-    formatCharacter _ _ = Nothing
+    formatCharacter alt c = mapFormatCharacter monthQuarter $ formatCharacter alt c
 
 instance FormatTime Day where
     -- Aggregate
diff --git a/lib/Data/Time/Format/ISO8601.hs b/lib/Data/Time/Format/ISO8601.hs
--- a/lib/Data/Time/Format/ISO8601.hs
+++ b/lib/Data/Time/Format/ISO8601.hs
@@ -276,7 +276,7 @@
 
 -- | @xZ@ [ISO 8601:2004(E) sec. 4.2.4]
 withUTCDesignator :: Format t -> Format t
-withUTCDesignator f = f <** literalFormat "Z"
+withUTCDesignator f = f <** specialCaseReadFormat ((), "") (literalFormat "Z")
 
 -- | @±hh:mm@ (extended), @±hhmm@ (basic) [ISO 8601:2004(E) sec. 4.2.5.1]
 timeOffsetFormat :: FormatExtension -> Format TimeZone
@@ -295,8 +295,10 @@
                 (signum mm, Right (h, m))
         digits2 = integerFormat NoSign (Just 2)
     in
-        isoMap toTimeZone fromTimeZone $
-            mandatorySignFormat <**> (digits2 <++> extColonFormat fe digits2 digits2)
+        specialCaseReadFormat (utc, "") $
+            specialCaseReadFormat (utc, "Z") $
+                isoMap toTimeZone fromTimeZone $
+                    mandatorySignFormat <**> (digits2 <++> extColonFormat fe digits2 digits2)
 
 -- | @hh:mm:ss±hh:mm@ (extended), @hhmmss±hhmm@ (basic) [ISO 8601:2004(E) sec. 4.2.5.2]
 timeOfDayAndOffsetFormat :: FormatExtension -> Format (TimeOfDay, TimeZone)
@@ -326,7 +328,7 @@
 timeAndOffsetFormat :: Format t -> FormatExtension -> Format (t, TimeZone)
 timeAndOffsetFormat ft fe = ft <**> timeOffsetFormat fe
 
-intDesignator :: (Eq t, Show t, Read t, Num t) => Char -> Format t
+intDesignator :: (Show t, Integral t) => Char -> Format t
 intDesignator c = optionalFormat 0 $ integerFormat NegSign Nothing <** literalFormat [c]
 
 decDesignator :: (Eq t, Show t, Read t, Num t) => Char -> Format t
diff --git a/lib/Data/Time/Format/Locale.hs b/lib/Data/Time/Format/Locale.hs
--- a/lib/Data/Time/Format/Locale.hs
+++ b/lib/Data/Time/Format/Locale.hs
@@ -1,12 +1,7 @@
 {-# LANGUAGE Safe #-}
 
 -- Note: this file derives from old-locale:System.Locale.hs, which is copyright (c) The University of Glasgow 2001
-module Data.Time.Format.Locale (
-    TimeLocale (..),
-    defaultTimeLocale,
-    iso8601DateFormat,
-    rfc822DateFormat,
-) where
+module Data.Time.Format.Locale where
 
 import Data.Time.LocalTime.Internal.TimeZone
 
diff --git a/lib/Data/Time/Format/Parse.hs b/lib/Data/Time/Format/Parse.hs
--- a/lib/Data/Time/Format/Parse.hs
+++ b/lib/Data/Time/Format/Parse.hs
@@ -2,18 +2,7 @@
 
 {-# OPTIONS -fno-warn-orphans #-}
 
-module Data.Time.Format.Parse (
-    -- * UNIX-style parsing
-    parseTimeM,
-    parseTimeMultipleM,
-    parseTimeOrError,
-    readSTime,
-    readPTime,
-    ParseTime (),
-
-    -- * Locale
-    module Data.Time.Format.Locale,
-) where
+module Data.Time.Format.Parse where
 
 import Control.Monad.Fail
 import Data.Char
diff --git a/lib/Data/Time/Format/Parse/Class.hs b/lib/Data/Time/Format/Parse/Class.hs
--- a/lib/Data/Time/Format/Parse/Class.hs
+++ b/lib/Data/Time/Format/Parse/Class.hs
@@ -1,7 +1,6 @@
 {-# LANGUAGE Safe #-}
 
 module Data.Time.Format.Parse.Class (
-    -- * Parsing
     ParseNumericPadding (..),
     ParseTime (..),
     parseSpecifiers,
@@ -194,6 +193,8 @@
             -- year of century
             'y' -> parseDigitsUsual ZeroPadding 2
             'g' -> parseDigitsUsual ZeroPadding 2
+            -- quarter of year
+            'v' -> parseDigitsUsual ZeroPadding 1
             -- month of year
             'B' -> oneOf (map fst (months l))
             'b' -> oneOf (map snd (months l))
diff --git a/lib/Data/Time/Format/Parse/Instances.hs b/lib/Data/Time/Format/Parse/Instances.hs
--- a/lib/Data/Time/Format/Parse/Instances.hs
+++ b/lib/Data/Time/Format/Parse/Instances.hs
@@ -10,6 +10,7 @@
 import Data.Char
 import Data.Fixed
 import Data.List (elemIndex, find)
+import Data.Maybe
 import Data.Ratio
 import Data.Time.Calendar.CalendarDiffDays
 import Data.Time.Calendar.Days
@@ -17,6 +18,8 @@
 import Data.Time.Calendar.Month
 import Data.Time.Calendar.OrdinalDate
 import Data.Time.Calendar.Private (clipValid)
+import Data.Time.Calendar.Quarter
+import Data.Time.Calendar.Types
 import Data.Time.Calendar.WeekDate
 import Data.Time.Clock.Internal.DiffTime
 import Data.Time.Clock.Internal.NominalDiffTime
@@ -33,27 +36,113 @@
 import Data.Traversable
 import Text.Read (readMaybe)
 
-data DayComponent
-    = DCCentury Integer -- century of all years
-    | DCCenturyYear Integer -- 0-99, last two digits of both real years and week years
-    | DCYearMonth MonthOfYear -- 1-12
-    | DCMonthDay DayOfMonth -- 1-31
-    | DCYearDay DayOfYear -- 1-366
-    | DCWeekDay Int -- 1-7 (mon-sun)
-    | DCYearWeek
-        WeekType
-        WeekOfYear -- 1-53 or 0-53
-
 data WeekType
     = ISOWeek
     | SundayWeek
     | MondayWeek
+    deriving Eq
 
-makeDayComponent :: TimeLocale -> Char -> String -> Maybe [DayComponent]
-makeDayComponent l c x =
+mkDayFromWeekType :: WeekType -> Year -> WeekOfYear -> DayOfWeek -> Maybe Day
+mkDayFromWeekType wt y woy dow =
+    case wt of
+        ISOWeek -> fromWeekDateValid y woy $ fromEnum dow
+        SundayWeek -> fromSundayStartWeekValid y woy $ mod (fromEnum dow) 7
+        MondayWeek -> fromMondayStartWeekValid y woy $ fromEnum dow
+
+data DayFact
+    = CenturyDayFact Integer -- century of all years
+    | YearOfCenturyDayFact Integer -- 0-99, last two digits of both real years and week years
+    | QuarterOfYearDayFact QuarterOfYear
+    | MonthOfYearDayFact MonthOfYear -- 1-12
+    | DayOfMonthDayFact DayOfMonth -- 1-31
+    | DayOfYearDayFact DayOfYear -- 1-366
+    | DayOfWeekDayFact DayOfWeek
+    | WeekOfYearDayFact
+        WeekType
+        WeekOfYear -- 1-53 or 0-53
+    | UTCTimeDayFact UTCTime
+    | TimeZoneDayFact TimeZone
+
+lastMatch :: (a -> Maybe b) -> [a] -> Maybe b
+lastMatch f aa = listToMaybe $ reverse $ catMaybes $ fmap f aa
+
+dayFactGetCentury :: [DayFact] -> Maybe Integer
+dayFactGetCentury = lastMatch $ \case
+    CenturyDayFact x -> Just x
+    _ -> Nothing
+
+dayFactGetYearOfCentury :: [DayFact] -> Maybe Integer
+dayFactGetYearOfCentury = lastMatch $ \case
+    YearOfCenturyDayFact x -> Just x
+    _ -> Nothing
+
+dayFactGetQuarterOfYear :: [DayFact] -> Maybe QuarterOfYear
+dayFactGetQuarterOfYear = lastMatch $ \case
+    QuarterOfYearDayFact x -> Just x
+    _ -> Nothing
+
+dayFactGetMonthOfYear :: [DayFact] -> Maybe MonthOfYear
+dayFactGetMonthOfYear = lastMatch $ \case
+    MonthOfYearDayFact x -> Just x
+    _ -> Nothing
+
+dayFactGetDayOfMonth :: [DayFact] -> Maybe DayOfMonth
+dayFactGetDayOfMonth = lastMatch $ \case
+    DayOfMonthDayFact x -> Just x
+    _ -> Nothing
+
+dayFactGetDayOfYear :: [DayFact] -> Maybe DayOfYear
+dayFactGetDayOfYear = lastMatch $ \case
+    DayOfYearDayFact x -> Just x
+    _ -> Nothing
+
+dayFactGetDayOfWeek :: [DayFact] -> Maybe DayOfWeek
+dayFactGetDayOfWeek = lastMatch $ \case
+    DayOfWeekDayFact x -> Just x
+    _ -> Nothing
+
+dayFactGetWeekOfYear :: [DayFact] -> Maybe (WeekType, WeekOfYear)
+dayFactGetWeekOfYear = lastMatch $ \case
+    WeekOfYearDayFact wt x -> Just (wt, x)
+    _ -> Nothing
+
+dayFactGetUTCTime :: [DayFact] -> Maybe UTCTime
+dayFactGetUTCTime = lastMatch $ \case
+    UTCTimeDayFact x -> Just x
+    _ -> Nothing
+
+dayFactGetTimeZone :: [DayFact] -> Maybe TimeZone
+dayFactGetTimeZone = lastMatch $ \case
+    TimeZoneDayFact x -> Just x
+    _ -> Nothing
+
+readSpec_z :: String -> Maybe Int
+readSpec_z = readTzOffset
+
+readSpec_Z :: TimeLocale -> String -> Maybe TimeZone
+readSpec_Z _ str | Just offset <- readTzOffset str = Just $ TimeZone offset False ""
+readSpec_Z l str | Just zone <- getKnownTimeZone l str = Just zone
+readSpec_Z _ str | eqCI str "UTC" = Just utc
+readSpec_Z _ [c] | Just zone <- getMilZone c = Just zone
+readSpec_Z _ _ = Nothing
+
+eqCI :: String -> String -> Bool
+eqCI x y = map toUpper x == map toUpper y
+
+readIntegralMaybe :: Integral a => String -> Maybe a
+readIntegralMaybe str = do
+    i <- readMaybe str
     let
-        ra :: Read a => Maybe a
-        ra = readMaybe x
+        a = fromInteger i
+    if toInteger a == i
+        then Just a
+        else Nothing
+
+makeDayFact :: TimeLocale -> Char -> String -> Maybe [DayFact]
+makeDayFact l c x =
+    let
+        ra :: Integral a => Maybe a
+        ra = readIntegralMaybe x
         zeroBasedListIndex :: [String] -> Maybe Int
         zeroBasedListIndex ss = elemIndex (map toUpper x) $ fmap (map toUpper) ss
         oneBasedListIndex :: [String] -> Maybe Int
@@ -65,177 +154,214 @@
             -- %C: century (all but the last two digits of the year), 00 - 99
             'C' -> do
                 a <- ra
-                return [DCCentury a]
+                return [CenturyDayFact a]
             -- %f century (all but the last two digits of the year), 00 - 99
             'f' -> do
                 a <- ra
-                return [DCCentury a]
+                return [CenturyDayFact a]
             -- %Y: year
             'Y' -> do
                 a <- ra
-                return [DCCentury (a `div` 100), DCCenturyYear (a `mod` 100)]
+                return [CenturyDayFact (a `div` 100), YearOfCenturyDayFact (a `mod` 100)]
             -- %G: year for Week Date format
             'G' -> do
                 a <- ra
-                return [DCCentury (a `div` 100), DCCenturyYear (a `mod` 100)]
+                return [CenturyDayFact (a `div` 100), YearOfCenturyDayFact (a `mod` 100)]
             -- %y: last two digits of year, 00 - 99
             'y' -> do
                 a <- ra
-                return [DCCenturyYear a]
+                return [YearOfCenturyDayFact a]
             -- %g: last two digits of year for Week Date format, 00 - 99
             'g' -> do
                 a <- ra
-                return [DCCenturyYear a]
+                return [YearOfCenturyDayFact a]
+            -- %v: quarter of year, 1 - 4
+            'v' -> do
+                raw <- ra
+                a <- clipValid 1 4 raw
+                return [QuarterOfYearDayFact $ toEnum a]
             -- %B: month name, long form (fst from months locale), January - December
             'B' -> do
                 a <- oneBasedListIndex $ fmap fst $ months l
-                return [DCYearMonth a]
+                return [MonthOfYearDayFact a]
             -- %b: month name, short form (snd from months locale), Jan - Dec
             'b' -> do
                 a <- oneBasedListIndex $ fmap snd $ months l
-                return [DCYearMonth a]
+                return [MonthOfYearDayFact a]
             -- %m: month of year, leading 0 as needed, 01 - 12
             'm' -> do
                 raw <- ra
                 a <- clipValid 1 12 raw
-                return [DCYearMonth a]
+                return [MonthOfYearDayFact a]
             -- %d: day of month, leading 0 as needed, 01 - 31
             'd' -> do
                 raw <- ra
                 a <- clipValid 1 31 raw
-                return [DCMonthDay a]
+                return [DayOfMonthDayFact a]
             -- %e: day of month, leading space as needed, 1 - 31
             'e' -> do
                 raw <- ra
                 a <- clipValid 1 31 raw
-                return [DCMonthDay a]
+                return [DayOfMonthDayFact a]
             -- %V: week for Week Date format, 01 - 53
             'V' -> do
                 raw <- ra
                 a <- clipValid 1 53 raw
-                return [DCYearWeek ISOWeek a]
+                return [WeekOfYearDayFact ISOWeek a]
             -- %U: week number of year, where weeks start on Sunday (as sundayStartWeek), 00 - 53
             'U' -> do
                 raw <- ra
                 a <- clipValid 0 53 raw
-                return [DCYearWeek SundayWeek a]
+                return [WeekOfYearDayFact SundayWeek a]
             -- %W: week number of year, where weeks start on Monday (as mondayStartWeek), 00 - 53
             'W' -> do
                 raw <- ra
                 a <- clipValid 0 53 raw
-                return [DCYearWeek MondayWeek a]
+                return [WeekOfYearDayFact MondayWeek a]
             -- %u: day for Week Date format, 1 - 7
             'u' -> do
                 raw <- ra
                 a <- clipValid 1 7 raw
-                return [DCWeekDay a]
+                return [DayOfWeekDayFact $ toEnum a]
             -- %a: day of week, short form (snd from wDays locale), Sun - Sat
             'a' -> do
-                a' <- zeroBasedListIndex $ fmap snd $ wDays l
-                let
-                    a =
-                        if a' == 0
-                            then 7
-                            else a'
-                return [DCWeekDay a]
+                a <- zeroBasedListIndex $ fmap snd $ wDays l
+                return [DayOfWeekDayFact $ toEnum a]
             -- %A: day of week, long form (fst from wDays locale), Sunday - Saturday
             'A' -> do
-                a' <- zeroBasedListIndex $ fmap fst $ wDays l
-                let
-                    a =
-                        if a' == 0
-                            then 7
-                            else a'
-                return [DCWeekDay a]
+                a <- zeroBasedListIndex $ fmap fst $ wDays l
+                return [DayOfWeekDayFact $ toEnum a]
             -- %w: day of week number, 0 (= Sunday) - 6 (= Saturday)
             'w' -> do
                 raw <- ra
-                a' <- clipValid 0 6 raw
-                let
-                    a =
-                        if a' == 0
-                            then 7
-                            else a'
-                return [DCWeekDay a]
+                a <- clipValid 0 6 raw
+                return [DayOfWeekDayFact $ toEnum a]
             -- %j: day of year for Ordinal Date format, 001 - 366
             'j' -> do
                 raw <- ra
                 a <- clipValid 1 366 raw
-                return [DCYearDay a]
+                return [DayOfYearDayFact a]
+            -- %s: number of whole seconds since the Unix epoch.
+            's' -> do
+                raw <- ra
+                return [UTCTimeDayFact $ posixSecondsToUTCTime $ fromInteger raw]
+            'z' -> do
+                a <- readSpec_z x
+                return [TimeZoneDayFact $ TimeZone a False ""]
+            'Z' -> do
+                a <- readSpec_Z l x
+                return [TimeZoneDayFact a]
             -- unrecognised, pass on to other parsers
             _ -> return []
 
-makeDayComponents :: TimeLocale -> [(Char, String)] -> Maybe [DayComponent]
-makeDayComponents l pairs = do
-    components <- for pairs $ \(c, x) -> makeDayComponent l c x
-    return $ concat components
+makeDayFacts :: TimeLocale -> [(Char, String)] -> Maybe [DayFact]
+makeDayFacts l pairs = do
+    factss <- for pairs $ \(c, x) -> makeDayFact l c x
+    return $ mconcat factss
 
-safeLast :: a -> [a] -> a
-safeLast x xs = last (x : xs)
+dayFactYear :: [DayFact] -> Integer
+dayFactYear facts =
+    let
+        d = fromMaybe 70 $ dayFactGetYearOfCentury facts
+        c =
+            fromMaybe
+                ( if d >= 69
+                    then 19
+                    else 20
+                )
+                $ dayFactGetCentury facts
+    in
+        100 * c + d
 
-instance ParseTime Day where
-    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
-    parseTimeSpecifier _ = timeParseTimeSpecifier
-    buildTime l pairs = do
-        cs <- makeDayComponents l pairs
-        -- 'Nothing' indicates a parse failure,
-        -- while 'Just []' means no information
-        let
-            y =
+dayFactDay :: [DayFact] -> Maybe Day
+dayFactDay facts =
+    case dayFactYear facts of
+        y | Just doy <- dayFactGetDayOfYear facts -> fromOrdinalDateValid y doy
+        y
+            | Just moy <- dayFactGetMonthOfYear facts ->
                 let
-                    d = safeLast 70 [x | DCCenturyYear x <- cs]
-                    c =
-                        safeLast
-                            ( if d >= 69
-                                then 19
-                                else 20
-                            )
-                            [x | DCCentury x <- cs]
+                    dom = fromMaybe 1 $ dayFactGetDayOfMonth facts
                 in
-                    100 * c + d
-            rest (DCYearMonth m : _) =
+                    fromGregorianValid y moy dom
+        y
+            | Just (wt, woy) <- dayFactGetWeekOfYear facts ->
                 let
-                    d = safeLast 1 [x | DCMonthDay x <- cs]
+                    dow = fromMaybe Thursday $ dayFactGetDayOfWeek facts
                 in
-                    fromGregorianValid y m d
-            rest (DCYearDay d : _) = fromOrdinalDateValid y d
-            rest (DCYearWeek wt w : _) =
+                    mkDayFromWeekType wt y woy dow
+        y
+            | Just qoy <- dayFactGetQuarterOfYear facts ->
                 let
-                    d = safeLast 4 [x | DCWeekDay x <- cs]
+                    moy = case qoy of
+                        Q1 -> 1
+                        Q2 -> 4
+                        Q3 -> 7
+                        Q4 -> 10
+                    dom = fromMaybe 1 $ dayFactGetDayOfMonth facts
                 in
-                    case wt of
-                        ISOWeek -> fromWeekDateValid y w d
-                        SundayWeek -> fromSundayStartWeekValid y w (d `mod` 7)
-                        MondayWeek -> fromMondayStartWeekValid y w d
-            rest (_ : xs) = rest xs
-            rest [] = rest [DCYearMonth 1]
-        rest cs
+                    fromGregorianValid y moy dom
+        _
+            | Just ut <- dayFactGetUTCTime facts ->
+                let
+                    tz = fromMaybe utc $ dayFactGetTimeZone facts
+                in
+                    Just $ localDay $ utcToLocalTime tz ut
+        y | Just dom <- dayFactGetDayOfMonth facts -> fromGregorianValid y 1 dom
+        y | Just dow <- dayFactGetDayOfWeek facts -> fromWeekDateValid y 1 $ fromEnum dow
+        y -> fromOrdinalDateValid y 1
 
+instance ParseTime Day where
+    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
+    parseTimeSpecifier _ = timeParseTimeSpecifier
+    buildTime l pairs = do
+        facts <- makeDayFacts l pairs
+        dayFactDay facts
+
+instance ParseTime DayOfWeek where
+    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
+    parseTimeSpecifier _ = timeParseTimeSpecifier
+    buildTime l pairs = do
+        facts <- makeDayFacts l pairs
+        dayFactGetDayOfWeek facts
+            <|> (fmap dayOfWeek $ dayFactDay facts)
+
 instance ParseTime Month where
     substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
     parseTimeSpecifier _ = timeParseTimeSpecifier
     buildTime l pairs = do
-        cs <- makeDayComponents l pairs
-        -- 'Nothing' indicates a parse failure,
-        -- while 'Just []' means no information
-        let
-            y =
+        facts <- makeDayFacts l pairs
+        case dayFactGetMonthOfYear facts of
+            Just moy ->
                 let
-                    d = safeLast 70 [x | DCCenturyYear x <- cs]
-                    c =
-                        safeLast
-                            ( if d >= 69
-                                then 19
-                                else 20
-                            )
-                            [x | DCCentury x <- cs]
+                    y = dayFactYear facts
                 in
-                    100 * c + d
-            rest (DCYearMonth m : _) = fromYearMonthValid y m
-            rest (_ : xs) = rest xs
-            rest [] = fromYearMonthValid y 1
-        rest cs
+                    Just $ YearMonth y moy
+            Nothing -> fmap dayPeriod $ dayFactDay facts
 
+instance ParseTime QuarterOfYear where
+    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
+    parseTimeSpecifier _ = timeParseTimeSpecifier
+    buildTime l pairs = do
+        facts <- makeDayFacts l pairs
+        case dayFactGetQuarterOfYear facts of
+            Just qoy -> Just qoy
+            Nothing -> do
+                QuarterDay (YearQuarter _ qoy) _ <- dayFactDay facts
+                return qoy
+
+instance ParseTime Quarter where
+    substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
+    parseTimeSpecifier _ = timeParseTimeSpecifier
+    buildTime l pairs = do
+        facts <- makeDayFacts l pairs
+        case dayFactGetQuarterOfYear facts of
+            Just qoy ->
+                let
+                    y = dayFactYear facts
+                in
+                    Just $ YearQuarter y qoy
+            Nothing -> fmap dayPeriod $ dayFactDay facts
+
 mfoldl :: Monad m => (a -> b -> m a) -> m a -> [b] -> m a
 mfoldl f =
     let
@@ -245,74 +371,149 @@
     in
         foldl mf
 
+data AMPM = AM | PM
+
+data TimeFact
+    = AMAPMTimeFact AMPM
+    | HourTimeFact Int
+    | MinuteTimeFact Int
+    | WholeSecondTimeFact Int
+    | FractSecondTimeFact Pico
+    | UTCTimeFact UTCTime
+    | ZoneTimeFact TimeZone
+
+timeFactGetAMPM :: [TimeFact] -> Maybe AMPM
+timeFactGetAMPM = lastMatch $ \case
+    AMAPMTimeFact x -> Just x
+    _ -> Nothing
+
+timeFactGetHour :: [TimeFact] -> Maybe Int
+timeFactGetHour = lastMatch $ \case
+    HourTimeFact x -> Just x
+    _ -> Nothing
+
+timeFactGetMinute :: [TimeFact] -> Maybe Int
+timeFactGetMinute = lastMatch $ \case
+    MinuteTimeFact x -> Just x
+    _ -> Nothing
+
+timeFactGetWholeSecond :: [TimeFact] -> Maybe Int
+timeFactGetWholeSecond = lastMatch $ \case
+    WholeSecondTimeFact x -> Just x
+    _ -> Nothing
+
+timeFactGetFractSecond :: [TimeFact] -> Maybe Pico
+timeFactGetFractSecond = lastMatch $ \case
+    FractSecondTimeFact x -> Just x
+    _ -> Nothing
+
+timeFactGetUTC :: [TimeFact] -> Maybe UTCTime
+timeFactGetUTC = lastMatch $ \case
+    UTCTimeFact x -> Just x
+    _ -> Nothing
+
+timeFactGetZone :: [TimeFact] -> Maybe TimeZone
+timeFactGetZone = lastMatch $ \case
+    ZoneTimeFact x -> Just x
+    _ -> Nothing
+
+makeTimeFact :: TimeLocale -> Char -> String -> Maybe [TimeFact]
+makeTimeFact l c x =
+    let
+        ra :: Integral a => Maybe a
+        ra = readIntegralMaybe x
+        getAmPm =
+            let
+                (amStr, pmStr) = amPm l
+            in
+                if x `eqCI` amStr
+                    then Just [AMAPMTimeFact AM]
+                    else
+                        if x `eqCI` pmStr
+                            then Just [AMAPMTimeFact PM]
+                            else Nothing
+    in
+        case c of
+            'P' -> getAmPm
+            'p' -> getAmPm
+            'H' -> do
+                raw <- ra
+                a <- clipValid 0 23 raw
+                return [HourTimeFact a]
+            'I' -> do
+                raw <- ra
+                a <- clipValid 1 12 raw
+                return [HourTimeFact a]
+            'k' -> do
+                raw <- ra
+                a <- clipValid 0 23 raw
+                return [HourTimeFact a]
+            'l' -> do
+                raw <- ra
+                a <- clipValid 1 12 raw
+                return [HourTimeFact a]
+            'M' -> do
+                raw <- ra
+                a <- clipValid 0 59 raw
+                return [MinuteTimeFact a]
+            'S' -> do
+                raw <- ra
+                a <- clipValid 0 60 raw
+                return [WholeSecondTimeFact $ fromInteger a]
+            'q' -> do
+                ps <- (readMaybe $ take 12 $ rpad 12 '0' x) <|> return 0
+                return [FractSecondTimeFact $ mkPico 0 ps]
+            'Q' ->
+                if null x
+                    then return []
+                    else do
+                        ps <- (readMaybe $ take 12 $ rpad 12 '0' x) <|> return 0
+                        return [FractSecondTimeFact $ mkPico 0 ps]
+            's' -> do
+                raw <- ra
+                return [UTCTimeFact $ posixSecondsToUTCTime $ fromInteger raw]
+            'z' -> do
+                a <- readSpec_z x
+                return [ZoneTimeFact $ TimeZone a False ""]
+            'Z' -> do
+                a <- readSpec_Z l x
+                return [ZoneTimeFact a]
+            _ -> return []
+
+makeTimeFacts :: TimeLocale -> [(Char, String)] -> Maybe [TimeFact]
+makeTimeFacts l pairs = do
+    factss <- for pairs $ \(c, x) -> makeTimeFact l c x
+    return $ mconcat factss
+
 instance ParseTime TimeOfDay where
     substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
     parseTimeSpecifier _ = timeParseTimeSpecifier
-    buildTime l =
-        let
-            f t@(TimeOfDay h m s) (c, x) =
+    buildTime l pairs = do
+        facts <- makeTimeFacts l pairs
+        -- 'Nothing' indicates a parse failure,
+        -- while 'Just []' means no information
+        case timeFactGetUTC facts of
+            Just t ->
                 let
-                    ra :: Read a => Maybe a
-                    ra = readMaybe x
-                    getAmPm =
-                        let
-                            upx = map toUpper x
-                            (amStr, pmStr) = amPm l
-                        in
-                            if upx == amStr
-                                then Just $ TimeOfDay (h `mod` 12) m s
-                                else
-                                    if upx == pmStr
-                                        then
-                                            Just $
-                                                TimeOfDay
-                                                    ( if h < 12
-                                                        then h + 12
-                                                        else h
-                                                    )
-                                                    m
-                                                    s
-                                        else Nothing
+                    zone = fromMaybe utc $ timeFactGetZone facts
+                    sf = fromMaybe 0 $ timeFactGetFractSecond facts
+                    TimeOfDay h m s = localTimeOfDay $ utcToLocalTime zone t
                 in
-                    case c of
-                        'P' -> getAmPm
-                        'p' -> getAmPm
-                        'H' -> do
-                            raw <- ra
-                            a <- clipValid 0 23 raw
-                            return $ TimeOfDay a m s
-                        'I' -> do
-                            raw <- ra
-                            a <- clipValid 1 12 raw
-                            return $ TimeOfDay a m s
-                        'k' -> do
-                            raw <- ra
-                            a <- clipValid 0 23 raw
-                            return $ TimeOfDay a m s
-                        'l' -> do
-                            raw <- ra
-                            a <- clipValid 1 12 raw
-                            return $ TimeOfDay a m s
-                        'M' -> do
-                            raw <- ra
-                            a <- clipValid 0 59 raw
-                            return $ TimeOfDay h a s
-                        'S' -> do
-                            raw <- ra
-                            a <- clipValid 0 60 raw
-                            return $ TimeOfDay h m (fromInteger a)
-                        'q' -> do
-                            ps <- (readMaybe $ take 12 $ rpad 12 '0' x) <|> return 0
-                            return $ TimeOfDay h m (mkPico (floor s) ps)
-                        'Q' ->
-                            if null x
-                                then Just t
-                                else do
-                                    ps <- (readMaybe $ take 12 $ rpad 12 '0' x) <|> return 0
-                                    return $ TimeOfDay h m (mkPico (floor s) ps)
-                        _ -> Just t
-        in
-            mfoldl f (Just midnight)
+                    return $ TimeOfDay h m $ s + sf
+            Nothing ->
+                let
+                    h = fromMaybe 0 $ timeFactGetHour facts
+                    m = fromMaybe 0 $ timeFactGetMinute facts
+                    si = fromMaybe 0 $ timeFactGetWholeSecond facts
+                    sf = fromMaybe 0 $ timeFactGetFractSecond facts
+                    s :: Pico
+                    s = fromIntegral si + sf
+                    h' = case timeFactGetAMPM facts of
+                        Nothing -> h
+                        Just AM -> mod h 12
+                        Just PM -> if h < 12 then h + 12 else h
+                in
+                    return $ TimeOfDay h' m s
 
 rpad :: Int -> a -> [a] -> [a]
 rpad n c xs = xs ++ replicate (n - length xs) c
@@ -351,7 +552,7 @@
             return $ TimeZone (hours * 60) False [yc]
 
 getKnownTimeZone :: TimeLocale -> String -> Maybe TimeZone
-getKnownTimeZone locale x = find (\tz -> map toUpper x == timeZoneName tz) (knownTimeZones locale)
+getKnownTimeZone locale x = find (\tz -> x `eqCI` timeZoneName tz) (knownTimeZones locale)
 
 instance ParseTime TimeZone where
     substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
@@ -359,17 +560,10 @@
     buildTime l =
         let
             f :: Char -> String -> TimeZone -> Maybe TimeZone
-            f 'z' str (TimeZone _ dst name)
-                | Just offset <- readTzOffset str = Just $ TimeZone offset dst name
-            f 'z' _ _ = Nothing
-            f 'Z' str _
-                | Just offset <- readTzOffset str = Just $ TimeZone offset False ""
-            f 'Z' str _
-                | Just zone <- getKnownTimeZone l str = Just zone
-            f 'Z' "UTC" _ = Just utc
-            f 'Z' [c] _
-                | Just zone <- getMilZone c = Just zone
-            f 'Z' _ _ = Nothing
+            f 'z' str (TimeZone _ dst name) = do
+                offset <- readSpec_z str
+                return $ TimeZone offset dst name
+            f 'Z' str _ = readSpec_Z l str
             f _ _ tz = Just tz
         in
             foldl (\mt (c, s) -> mt >>= f c s) (Just $ minutesToTimeZone 0)
@@ -416,6 +610,8 @@
     substituteTimeSpecifier _ = timeSubstituteTimeSpecifier
     parseTimeSpecifier _ = timeParseTimeSpecifier
     buildTime l xs = localTimeToUT1 0 <$> buildTime l xs
+
+--- Duration
 
 buildTimeMonths :: [(Char, String)] -> Maybe Integer
 buildTimeMonths xs = do
diff --git a/lib/Data/Time/LocalTime.hs b/lib/Data/Time/LocalTime.hs
--- a/lib/Data/Time/LocalTime.hs
+++ b/lib/Data/Time/LocalTime.hs
@@ -11,14 +11,53 @@
     -- getting the locale time zone
     getTimeZone,
     getCurrentTimeZone,
-    module Data.Time.LocalTime.Internal.TimeOfDay,
+
+    -- * Time of day
+    TimeOfDay (..),
+    midnight,
+    midday,
+    makeTimeOfDayValid,
+    timeToDaysAndTimeOfDay,
+    daysAndTimeOfDayToTime,
+    utcToLocalTimeOfDay,
+    localToUTCTimeOfDay,
+    timeToTimeOfDay,
+    pastMidnight,
+    timeOfDayToTime,
+    sinceMidnight,
+    diffTimeOfDay,
+    dayFractionToTimeOfDay,
+    timeOfDayToDayFraction,
+
+    -- * Calendar Duration
     module Data.Time.LocalTime.Internal.CalendarDiffTime,
-    module Data.Time.LocalTime.Internal.LocalTime,
-    module Data.Time.LocalTime.Internal.ZonedTime,
+
+    -- * Local Time
+    LocalTime (..),
+    addLocalTime,
+    diffLocalTime,
+    -- converting UTC and UT1 times to LocalTime
+    utcToLocalTime,
+    localTimeToUTC,
+    ut1ToLocalTime,
+    localTimeToUT1,
+    -- using CalendarDiffTime
+    addLocalDurationClip,
+    addLocalDurationRollOver,
+    diffLocalDurationClip,
+    diffLocalDurationRollOver,
+
+    -- * Zoned Time
+    ZonedTime (..),
+    utcToZonedTime,
+    zonedTimeToUTC,
+    getZonedTime,
+    utcToLocalZonedTime,
 ) where
 
 import Data.Time.Format ()
 import Data.Time.LocalTime.Internal.CalendarDiffTime
+import Data.Time.LocalTime.Internal.Foreign
 import Data.Time.LocalTime.Internal.LocalTime
 import Data.Time.LocalTime.Internal.TimeOfDay
 import Data.Time.LocalTime.Internal.TimeZone hiding (timeZoneOffsetString'')
diff --git a/lib/Data/Time/LocalTime/Internal/CalendarDiffTime.hs b/lib/Data/Time/LocalTime/Internal/CalendarDiffTime.hs
--- a/lib/Data/Time/LocalTime/Internal/CalendarDiffTime.hs
+++ b/lib/Data/Time/LocalTime/Internal/CalendarDiffTime.hs
@@ -1,28 +1,27 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
-module Data.Time.LocalTime.Internal.CalendarDiffTime (
-    -- * Calendar Duration
-    module Data.Time.LocalTime.Internal.CalendarDiffTime,
-) where
+module Data.Time.LocalTime.Internal.CalendarDiffTime where
 
 import Control.DeepSeq
 import Data.Data
 import Data.Time.Calendar.CalendarDiffDays
+import Data.Time.Calendar.Gregorian
 import Data.Time.Clock.Internal.NominalDiffTime
+import Data.Time.Clock.Internal.UTCDiff
+import Data.Time.Clock.Internal.UTCTime
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
+import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 data CalendarDiffTime = CalendarDiffTime
     { ctMonths :: Integer
     , ctTime :: NominalDiffTime
     }
-    deriving
-        ( Eq
-        , -- | @since 1.9.2
-          Data
-        , -- | @since 1.9.2
-          Typeable
-        , Generic
-        )
+    deriving (Eq, Typeable, Data, Generic, TH.Lift)
 
 instance NFData CalendarDiffTime where
     rnf (CalendarDiffTime m t) = rnf m `seq` rnf t `seq` ()
@@ -45,3 +44,25 @@
 -- | Scale by a factor. Note that @scaleCalendarDiffTime (-1)@ will not perfectly invert a duration, due to variable month lengths.
 scaleCalendarDiffTime :: Integer -> CalendarDiffTime -> CalendarDiffTime
 scaleCalendarDiffTime k (CalendarDiffTime m d) = CalendarDiffTime (k * m) (fromInteger k * d)
+
+addUTCDurationClip :: CalendarDiffTime -> UTCTime -> UTCTime
+addUTCDurationClip (CalendarDiffTime m d) (UTCTime day t) =
+    addUTCTime d $ UTCTime (addGregorianMonthsClip m day) t
+
+addUTCDurationRollOver :: CalendarDiffTime -> UTCTime -> UTCTime
+addUTCDurationRollOver (CalendarDiffTime m d) (UTCTime day t) =
+    addUTCTime d $ UTCTime (addGregorianMonthsRollOver m day) t
+
+diffUTCDurationClip :: UTCTime -> UTCTime -> CalendarDiffTime
+diffUTCDurationClip (UTCTime day1 t1) (UTCTime day2 t2) =
+    let
+        CalendarDiffTime m t = calendarTimeDays $ diffGregorianDurationClip day1 day2
+    in
+        CalendarDiffTime m $ t + realToFrac (t1 - t2)
+
+diffUTCDurationRollOver :: UTCTime -> UTCTime -> CalendarDiffTime
+diffUTCDurationRollOver (UTCTime day1 t1) (UTCTime day2 t2) =
+    let
+        CalendarDiffTime m t = calendarTimeDays $ diffGregorianDurationRollOver day1 day2
+    in
+        CalendarDiffTime m $ t + realToFrac (t1 - t2)
diff --git a/lib/Data/Time/LocalTime/Internal/Foreign.hs b/lib/Data/Time/LocalTime/Internal/Foreign.hs
new file mode 100644
--- /dev/null
+++ b/lib/Data/Time/LocalTime/Internal/Foreign.hs
@@ -0,0 +1,130 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE Safe #-}
+
+module Data.Time.LocalTime.Internal.Foreign (
+    getTimeZone,
+    getCurrentTimeZone,
+) where
+
+import Data.Time.Clock.Internal.UTCTime
+import Data.Time.Clock.POSIX
+import Data.Time.Clock.System
+import Data.Time.LocalTime.Internal.TimeZone
+import Foreign
+import Foreign.C
+#if defined(javascript_HOST_ARCH)
+import Data.Time.Calendar.Gregorian
+import Data.Time.Clock.Internal.NominalDiffTime
+import Data.Time.LocalTime.Internal.LocalTime
+import Data.Time.LocalTime.Internal.TimeOfDay
+#endif
+
+#if defined(javascript_HOST_ARCH)
+
+foreign import javascript "((dy,dm,dd,th,tm,ts) => { const d = new Date(0); d.setUTCFullYear(dy,dm,dd); d.setUTCHours(th,tm,ts,0); return -d.getTimezoneOffset(); })"
+    js_get_timezone_minutes :: Int -> Int -> Int -> Int -> Int -> Int -> IO Int
+
+foreign import javascript "((dy,dm,dd,th,tm,ts) => { const d = new Date(0); d.setUTCFullYear(dy,dm,dd); d.setUTCHours(th,tm,ts,0); const jan = new Date(0); jan.setFullYear(d.getFullYear(),0,1); jan.setHours(0,0,0,0); const jul = new Date(0); jul.setFullYear(d.getFullYear(),6,1); jul.setHours(0,0,0,0); return d.getTimezoneOffset() < Math.max(jan.getTimezoneOffset(),jul.getTimezoneOffset()) ? 1 : 0; })"
+    js_get_timezone_summer :: Int -> Int -> Int -> Int -> Int -> Int -> IO Int
+
+get_timezone :: UTCTime -> IO (Int, Bool)
+get_timezone ut =
+    let
+        lt :: LocalTime
+        lt = utcToLocalTime utc ut
+    in
+        case lt of
+            LocalTime (YearMonthDay dy dm dd) (TimeOfDay th tm ts) -> do
+                let
+                    dy' = fromInteger dy
+                    dm' = pred dm
+                    ts' = floor ts
+                mins <- js_get_timezone_minutes dy' dm' dd th tm ts'
+                summer <- js_get_timezone_summer dy' dm' dd th tm ts'
+                return (mins, summer /= 0)
+
+getTimeZoneCTime :: CTime -> IO TimeZone
+getTimeZoneCTime ct = do
+    let
+        ut :: UTCTime
+        ut = posixSecondsToUTCTime $ secondsToNominalDiffTime $ fromIntegral $ fromCTime ct
+    (mins, summer) <- get_timezone ut
+    return $ TimeZone mins summer ""
+
+fromCTime :: CTime -> Int64
+fromCTime (CTime tt) = fromIntegral tt
+
+#else
+{-# CFILES cbits/HsTime.c #-}
+foreign import ccall unsafe "HsTime.h get_current_timezone_seconds"
+    get_current_timezone_seconds ::
+        CTime -> Ptr CInt -> Ptr CString -> IO CLong
+
+getTimeZoneCTime :: CTime -> IO TimeZone
+getTimeZoneCTime ctime =
+    with 0 $ \pdst ->
+        with nullPtr $ \pcname -> do
+            secs <- get_current_timezone_seconds ctime pdst pcname
+            case secs of
+                0x80000000 -> fail "localtime_r failed"
+                _ -> do
+                    dst <- peek pdst
+                    cname <- peek pcname
+                    name <- peekCString cname
+                    return (TimeZone (div (fromIntegral secs) 60) (dst == 1) name)
+#endif
+
+-- there's no instance Bounded CTime, so this is the easiest way to check for overflow
+toCTime :: Int64 -> IO CTime
+toCTime t =
+    let
+        tt = fromIntegral t
+        t' = fromIntegral tt
+    in
+        if t' == t
+            then return $ CTime tt
+            else fail "Data.Time.LocalTime.Internal.TimeZone.toCTime: Overflow"
+
+-- | Get the configured time-zone for a given time (varying as per summertime adjustments).
+getTimeZoneSystem :: SystemTime -> IO TimeZone
+getTimeZoneSystem t = do
+    ctime <- toCTime $ systemSeconds t
+    getTimeZoneCTime ctime
+
+-- | Get the configured time-zone for a given time (varying as per summertime adjustments).
+--
+-- On Unix systems the output of this function depends on:
+--
+-- 1. The value of @TZ@ environment variable (if set)
+--
+-- 2. The system time zone (usually configured by @\/etc\/localtime@ symlink)
+--
+-- For details see tzset(3) and localtime(3).
+--
+-- Example:
+--
+-- @
+-- > let t = `UTCTime` (`Data.Time.Calendar.fromGregorian` 2021 7 1) 0
+-- > `getTimeZone` t
+-- CEST
+-- > `System.Environment.setEnv` \"TZ\" \"America/New_York\" >> `getTimeZone` t
+-- EDT
+-- > `System.Environment.setEnv` \"TZ\" \"Europe/Berlin\" >> `getTimeZone` t
+-- CEST
+-- @
+--
+-- On Windows systems the output of this function depends on:
+--
+-- 1. The value of @TZ@ environment variable (if set).
+-- See [here](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset) for how Windows interprets this variable.
+--
+-- 2. The system time zone, configured in Settings
+getTimeZone :: UTCTime -> IO TimeZone
+getTimeZone t = do
+    ctime <- toCTime $ floor $ utcTimeToPOSIXSeconds t
+    getTimeZoneCTime ctime
+
+-- | Get the configured time-zone for the current time.
+getCurrentTimeZone :: IO TimeZone
+getCurrentTimeZone = getSystemTime >>= getTimeZoneSystem
diff --git a/lib/Data/Time/LocalTime/Internal/LocalTime.hs b/lib/Data/Time/LocalTime/Internal/LocalTime.hs
--- a/lib/Data/Time/LocalTime/Internal/LocalTime.hs
+++ b/lib/Data/Time/LocalTime/Internal/LocalTime.hs
@@ -1,18 +1,9 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
 {-# OPTIONS -fno-warn-orphans #-}
 
-module Data.Time.LocalTime.Internal.LocalTime (
-    -- * Local Time
-    LocalTime (..),
-    addLocalTime,
-    diffLocalTime,
-    -- converting UTC and UT1 times to LocalTime
-    utcToLocalTime,
-    localTimeToUTC,
-    ut1ToLocalTime,
-    localTimeToUT1,
-) where
+module Data.Time.LocalTime.Internal.LocalTime where
 
 import Control.DeepSeq
 import Data.Data
@@ -22,9 +13,15 @@
 import Data.Time.Clock.Internal.UTCDiff
 import Data.Time.Clock.Internal.UTCTime
 import Data.Time.Clock.Internal.UniversalTime
+import Data.Time.LocalTime.Internal.CalendarDiffTime
 import Data.Time.LocalTime.Internal.TimeOfDay
 import Data.Time.LocalTime.Internal.TimeZone
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
+import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 -- | A simple day and time aggregate, where the day is of the specified parameter,
 -- and the time is a TimeOfDay.
@@ -34,7 +31,7 @@
     { localDay :: Day
     , localTimeOfDay :: TimeOfDay
     }
-    deriving (Eq, Ord, Data, Typeable, Generic)
+    deriving (Eq, Ord, Typeable, Data, Generic, TH.Lift)
 
 instance NFData LocalTime where
     rnf (LocalTime d t) = rnf d `seq` rnf t `seq` ()
@@ -79,3 +76,25 @@
 -- orphan instance
 instance Show UniversalTime where
     show t = show (ut1ToLocalTime 0 t)
+
+addLocalDurationClip :: CalendarDiffTime -> LocalTime -> LocalTime
+addLocalDurationClip (CalendarDiffTime m d) (LocalTime day t) =
+    addLocalTime d $ LocalTime (addGregorianMonthsClip m day) t
+
+addLocalDurationRollOver :: CalendarDiffTime -> LocalTime -> LocalTime
+addLocalDurationRollOver (CalendarDiffTime m d) (LocalTime day t) =
+    addLocalTime d $ LocalTime (addGregorianMonthsRollOver m day) t
+
+diffLocalDurationClip :: LocalTime -> LocalTime -> CalendarDiffTime
+diffLocalDurationClip (LocalTime day1 t1) (LocalTime day2 t2) =
+    let
+        CalendarDiffTime m t = calendarTimeDays $ diffGregorianDurationClip day1 day2
+    in
+        CalendarDiffTime m $ t + diffTimeOfDay t1 t2
+
+diffLocalDurationRollOver :: LocalTime -> LocalTime -> CalendarDiffTime
+diffLocalDurationRollOver (LocalTime day1 t1) (LocalTime day2 t2) =
+    let
+        CalendarDiffTime m t = calendarTimeDays $ diffGregorianDurationRollOver day1 day2
+    in
+        CalendarDiffTime m $ t + diffTimeOfDay t1 t2
diff --git a/lib/Data/Time/LocalTime/Internal/TimeOfDay.hs b/lib/Data/Time/LocalTime/Internal/TimeOfDay.hs
--- a/lib/Data/Time/LocalTime/Internal/TimeOfDay.hs
+++ b/lib/Data/Time/LocalTime/Internal/TimeOfDay.hs
@@ -1,22 +1,7 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
-module Data.Time.LocalTime.Internal.TimeOfDay (
-    -- * Time of day
-    TimeOfDay (..),
-    midnight,
-    midday,
-    makeTimeOfDayValid,
-    timeToDaysAndTimeOfDay,
-    daysAndTimeOfDayToTime,
-    utcToLocalTimeOfDay,
-    localToUTCTimeOfDay,
-    timeToTimeOfDay,
-    pastMidnight,
-    timeOfDayToTime,
-    sinceMidnight,
-    dayFractionToTimeOfDay,
-    timeOfDayToDayFraction,
-) where
+module Data.Time.LocalTime.Internal.TimeOfDay where
 
 import Control.DeepSeq
 import Data.Data
@@ -26,6 +11,11 @@
 import Data.Time.Clock.Internal.NominalDiffTime
 import Data.Time.LocalTime.Internal.TimeZone
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
+import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 -- | Time of day as represented in hour, minute and second (with picoseconds), typically used to express local time of day.
 --
@@ -37,10 +27,10 @@
     , todMin :: Int
     -- ^ range 0 - 59
     , todSec :: Pico
-    -- ^ Note that 0 <= 'todSec' < 61, accomodating leap seconds.
+    -- ^ Note that 0 <= 'todSec' < 61, accommodating leap seconds.
     -- Any local minute may have a leap second, since leap seconds happen in all zones simultaneously
     }
-    deriving (Eq, Ord, Data, Typeable, Generic)
+    deriving (Eq, Ord, Typeable, Data, Generic, TH.Lift)
 
 instance NFData TimeOfDay where
     rnf (TimeOfDay h m s) = rnf h `seq` rnf m `seq` rnf s `seq` ()
@@ -126,3 +116,6 @@
 -- | Get the fraction of a day since midnight given a time of day.
 timeOfDayToDayFraction :: TimeOfDay -> Rational
 timeOfDayToDayFraction tod = realToFrac (timeOfDayToTime tod) / realToFrac posixDayLength
+
+diffTimeOfDay :: TimeOfDay -> TimeOfDay -> NominalDiffTime
+diffTimeOfDay t1 t2 = realToFrac $ daysAndTimeOfDayToTime 0 t1 - daysAndTimeOfDayToTime 0 t2
diff --git a/lib/Data/Time/LocalTime/Internal/TimeZone.hs b/lib/Data/Time/LocalTime/Internal/TimeZone.hs
--- a/lib/Data/Time/LocalTime/Internal/TimeZone.hs
+++ b/lib/Data/Time/LocalTime/Internal/TimeZone.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
 module Data.Time.LocalTime.Internal.TimeZone (
@@ -10,20 +10,17 @@
     minutesToTimeZone,
     hoursToTimeZone,
     utc,
-    -- getting the locale time zone
-    getTimeZone,
-    getCurrentTimeZone,
 ) where
 
 import Control.DeepSeq
 import Data.Data
 import Data.Time.Calendar.Private
-import Data.Time.Clock.Internal.UTCTime
-import Data.Time.Clock.POSIX
-import Data.Time.Clock.System
-import Foreign
-import Foreign.C
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
+import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 -- | A TimeZone is a whole number of minutes offset from UTC, together with a name and a \"just for summer\" flag.
 data TimeZone = TimeZone
@@ -34,7 +31,7 @@
     , timeZoneName :: String
     -- ^ The name of the zone, typically a three- or four-letter acronym.
     }
-    deriving (Eq, Ord, Data, Typeable, Generic)
+    deriving (Eq, Ord, Typeable, Data, Generic, TH.Lift)
 
 instance NFData TimeZone where
     rnf (TimeZone m so n) = rnf m `seq` rnf so `seq` rnf n `seq` ()
@@ -79,81 +76,3 @@
 -- | The UTC time zone.
 utc :: TimeZone
 utc = TimeZone 0 False "UTC"
-
-{-# CFILES cbits/HsTime.c #-}
-foreign import ccall unsafe "HsTime.h get_current_timezone_seconds"
-    get_current_timezone_seconds ::
-        CTime -> Ptr CInt -> Ptr CString -> IO CLong
-
-getTimeZoneCTime :: CTime -> IO TimeZone
-getTimeZoneCTime ctime =
-    with
-        0
-        ( \pdst ->
-            with
-                nullPtr
-                ( \pcname -> do
-                    secs <- get_current_timezone_seconds ctime pdst pcname
-                    case secs of
-                        0x80000000 -> fail "localtime_r failed"
-                        _ -> do
-                            dst <- peek pdst
-                            cname <- peek pcname
-                            name <- peekCString cname
-                            return (TimeZone (div (fromIntegral secs) 60) (dst == 1) name)
-                )
-        )
-
--- there's no instance Bounded CTime, so this is the easiest way to check for overflow
-toCTime :: Int64 -> IO CTime
-toCTime t =
-    let
-        tt = fromIntegral t
-        t' = fromIntegral tt
-    in
-        if t' == t
-            then return $ CTime tt
-            else fail "Data.Time.LocalTime.Internal.TimeZone.toCTime: Overflow"
-
--- | Get the configured time-zone for a given time (varying as per summertime adjustments).
-getTimeZoneSystem :: SystemTime -> IO TimeZone
-getTimeZoneSystem t = do
-    ctime <- toCTime $ systemSeconds t
-    getTimeZoneCTime ctime
-
--- | Get the configured time-zone for a given time (varying as per summertime adjustments).
---
--- On Unix systems the output of this function depends on:
---
--- 1. The value of @TZ@ environment variable (if set)
---
--- 2. The system time zone (usually configured by @\/etc\/localtime@ symlink)
---
--- For details see tzset(3) and localtime(3).
---
--- Example:
---
--- @
--- > let t = `UTCTime` (`Data.Time.Calendar.fromGregorian` 2021 7 1) 0
--- > `getTimeZone` t
--- CEST
--- > `System.Environment.setEnv` \"TZ\" \"America/New_York\" >> `getTimeZone` t
--- EDT
--- > `System.Environment.setEnv` \"TZ\" \"Europe/Berlin\" >> `getTimeZone` t
--- CEST
--- @
---
--- On Windows systems the output of this function depends on:
---
--- 1. The value of @TZ@ environment variable (if set).
--- See [here](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset) for how Windows interprets this variable.
---
--- 2. The system time zone, configured in Settings
-getTimeZone :: UTCTime -> IO TimeZone
-getTimeZone t = do
-    ctime <- toCTime $ floor $ utcTimeToPOSIXSeconds t
-    getTimeZoneCTime ctime
-
--- | Get the configured time-zone for the current time.
-getCurrentTimeZone :: IO TimeZone
-getCurrentTimeZone = getSystemTime >>= getTimeZoneSystem
diff --git a/lib/Data/Time/LocalTime/Internal/ZonedTime.hs b/lib/Data/Time/LocalTime/Internal/ZonedTime.hs
--- a/lib/Data/Time/LocalTime/Internal/ZonedTime.hs
+++ b/lib/Data/Time/LocalTime/Internal/ZonedTime.hs
@@ -1,22 +1,23 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE Safe #-}
 
 {-# OPTIONS -fno-warn-orphans #-}
 
-module Data.Time.LocalTime.Internal.ZonedTime (
-    ZonedTime (..),
-    utcToZonedTime,
-    zonedTimeToUTC,
-    getZonedTime,
-    utcToLocalZonedTime,
-) where
+module Data.Time.LocalTime.Internal.ZonedTime where
 
 import Control.DeepSeq
 import Data.Data
 import Data.Time.Clock.Internal.UTCTime
 import Data.Time.Clock.POSIX
+import Data.Time.LocalTime.Internal.Foreign
 import Data.Time.LocalTime.Internal.LocalTime
 import Data.Time.LocalTime.Internal.TimeZone
 import GHC.Generics
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
+import qualified Language.Haskell.TH.Syntax as TH
+#endif
 
 -- | A local time together with a time zone.
 --
@@ -27,7 +28,7 @@
     { zonedTimeToLocalTime :: LocalTime
     , zonedTimeZone :: TimeZone
     }
-    deriving (Data, Typeable, Generic)
+    deriving (Typeable, Data, Generic, TH.Lift)
 
 instance NFData ZonedTime where
     rnf (ZonedTime lt z) = rnf lt `seq` rnf z `seq` ()
diff --git a/lib/cbits/HsTime.c b/lib/cbits/HsTime.c
--- a/lib/cbits/HsTime.c
+++ b/lib/cbits/HsTime.c
@@ -35,15 +35,15 @@
         *pname = dst ? _tzname[1] : _tzname[0];
         return - (dst ? _timezone - 3600 : _timezone);
 #else
-# if HAVE_TZNAME
-        *pname = *tzname;
+# if HAVE_TZNAME || defined(__MHS__)
+        *pname = tzname[dst ? 1 : 0];
 # else
 #  error "Don't know how to get timezone name on your OS"
 # endif
 # if HAVE_DECL_ALTZONE
-        return dst ? altzone : timezone;
+        return - (dst ? altzone : timezone);
 # else
-        return dst ? timezone - 3600 : timezone;
+        return - (dst ? timezone - 3600 : timezone);
 # endif
 #endif // HAVE_TM_ZONE
     }
diff --git a/lib/include/HsTime.h b/lib/include/HsTime.h
--- a/lib/include/HsTime.h
+++ b/lib/include/HsTime.h
@@ -1,7 +1,7 @@
 #ifndef __HSTIME_H__
 #define __HSTIME_H__
 
-#if defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32)
+#if defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32) || defined(__MHS__)
 #define HAVE_TIME_H 1
 #else
 
diff --git a/test/ForeignCalls.hs b/test/ForeignCalls.hs
new file mode 100644
--- /dev/null
+++ b/test/ForeignCalls.hs
@@ -0,0 +1,96 @@
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+import Control.Exception
+import Control.Monad
+import Data.Foldable
+import Data.Monoid
+import Data.Time
+import Data.Time.Clock.POSIX
+import Data.Time.Clock.System
+import Data.Time.Clock.TAI
+import Data.Traversable
+import System.Exit
+#if defined(javascript_HOST_ARCH)
+import System.Environment
+#endif
+import System.IO
+
+data Test = MkTest String (IO ())
+
+tests :: [Test]
+tests =
+    foreignCallTests
+#if defined(javascript_HOST_ARCH)
+        ++ jsTimezoneTests
+#endif
+
+foreignCallTests :: [Test]
+foreignCallTests =
+    [ MkTest "getCurrentTime" $ void $ getCurrentTime
+    , MkTest "getZonedTime" $ void $ getZonedTime
+    , MkTest "getCurrentTimeZone" $ void $ getCurrentTimeZone
+    , MkTest "getTimeZone" $ void $ getCurrentTime >>= getTimeZone
+    , MkTest "getPOSIXTime" $ void $ getPOSIXTime
+    , MkTest "getSystemTime" $ void $ getSystemTime
+    , MkTest "getTime_resolution" $ void $ evaluate getTime_resolution
+    , MkTest "taiClock time" $ for_ taiClock $ \(_, getTime) -> void $ getTime
+    , MkTest "taiClock resolution" $ for_ taiClock $ \(res, _) -> void $ evaluate res
+    ]
+
+#if defined(javascript_HOST_ARCH)
+jsTimezoneTests :: [Test]
+jsTimezoneTests =
+    [ MkTest "getTimeZone JavaScript DST transition" $ do
+        setEnv "TZ" "America/New_York"
+        let
+            transitionTime h m =
+                UTCTime (fromGregorian 2024 3 10) $
+                    secondsToDiffTime $
+                        h * 3600 + m * 60
+        zoneBefore <- getTimeZone $ transitionTime 6 30
+        assertTimeZone "before DST transition" (-300) False zoneBefore
+        zoneAfter <- getTimeZone $ transitionTime 7 30
+        assertTimeZone "after DST transition" (-240) True zoneAfter
+    ]
+
+assertTimeZone :: String -> Int -> Bool -> TimeZone -> IO ()
+assertTimeZone label expectedMinutes expectedSummer zone = do
+    when (timeZoneMinutes zone /= expectedMinutes) $
+        fail $
+            label
+                <> ": expected minutes "
+                <> show expectedMinutes
+                <> ", got "
+                <> show (timeZoneMinutes zone)
+    when (timeZoneSummerOnly zone /= expectedSummer) $
+        fail $
+            label
+                <> ": expected summer flag "
+                <> show expectedSummer
+                <> ", got "
+                <> show (timeZoneSummerOnly zone)
+#endif
+
+runTest :: Test -> IO Bool
+runTest (MkTest name action) = do
+    hPutStr stderr $ name <> ": "
+    result <- try action
+    case result of
+        Left err -> do
+            hPutStrLn stderr $ "FAILED: " <> show (err :: SomeException)
+            return False
+        Right () -> do
+            hPutStrLn stderr "PASSED"
+            return True
+
+main :: IO ()
+main = do
+    results <- for tests $ \test -> do
+        passed <- runTest test
+        return (Sum $ if passed then 1 else 0 :: Int, Sum 1)
+    let
+        (Sum i, Sum n) = mconcat results
+    hPutStrLn stderr $ show i <> " out of " <> show n <> " tests passed"
+    exitWith $ if i == n then ExitSuccess else ExitFailure 1
diff --git a/test/main/Main.hs b/test/main/Main.hs
--- a/test/main/Main.hs
+++ b/test/main/Main.hs
@@ -15,7 +15,7 @@
 import Test.Calendar.Week
 import Test.Calendar.Year
 import Test.Clock.Conversion
-import Test.Clock.Lift (testLift)
+import Test.Clock.Pattern
 import Test.Clock.Resolution
 import Test.Clock.TAI
 import Test.Format.Compile ()
@@ -49,7 +49,7 @@
             , testYear
             , testDuration
             ]
-        , testGroup "Clock" [testClockConversion, testResolutions, testTAI, testLift]
+        , testGroup "Clock" [testClockPatterns, testClockConversion, testResolutions, testTAI]
         , testGroup "Format" [testFormat, testParseTime, testISO8601]
         , testGroup "LocalTime" [testTime, testTimeOfDay, testCalendarDiffTime]
         ]
diff --git a/test/main/Test/AddDiff.hs b/test/main/Test/AddDiff.hs
new file mode 100644
--- /dev/null
+++ b/test/main/Test/AddDiff.hs
@@ -0,0 +1,20 @@
+module Test.AddDiff (
+    AddDiff (..),
+    testAddDiff,
+) where
+
+import Test.Arbitrary
+import Test.Tasty
+import Test.Tasty.QuickCheck hiding (reason)
+
+data AddDiff duration time = MkAddDiff
+    { adName :: String
+    , adAdd :: duration -> time -> time
+    , adDifference :: time -> time -> duration
+    }
+
+testAddDiff :: (Arbitrary (NoLeapSeconds time), Eq time, Show time) => AddDiff duration time -> TestTree
+testAddDiff MkAddDiff{..} =
+    testProperty adName $
+        \(MkNoLeapSeconds time1) (MkNoLeapSeconds time2) ->
+            adAdd (adDifference time2 time1) time1 == time2
diff --git a/test/main/Test/Arbitrary.hs b/test/main/Test/Arbitrary.hs
--- a/test/main/Test/Arbitrary.hs
+++ b/test/main/Test/Arbitrary.hs
@@ -1,6 +1,9 @@
 {-# OPTIONS -fno-warn-orphans #-}
 
-module Test.Arbitrary where
+module Test.Arbitrary (
+    supportedDayRange,
+    NoLeapSeconds (..),
+) where
 
 import Control.Monad
 import Data.Fixed
@@ -23,14 +26,51 @@
 
 deriving instance Show FirstWeekType
 
+deriving instance Random Month
+
+supportedMonthRange :: (Month, Month)
+supportedMonthRange = (YearMonth (-9899) 1, YearMonth 9999 12)
+
+shrinkYear :: Integer -> [Integer]
+shrinkYear y =
+    let
+        yearShrink =
+            if y > 2000
+                then [pred y]
+                else
+                    if y < 2000
+                        then [succ y]
+                        else []
+        year10Shrink =
+            if y > 2010
+                then fmap (\i -> y - i) [1 .. 10]
+                else
+                    if y < 1990
+                        then fmap (\i -> y + i) [1 .. 10]
+                        else []
+        year100Shrink =
+            if y > 2100
+                then [y - 100]
+                else
+                    if y < 1900
+                        then [y + 100]
+                        else []
+    in
+        year100Shrink <> year10Shrink <> yearShrink
+
 instance Arbitrary Month where
-    arbitrary = liftM MkMonth $ choose (-30000, 200000)
+    arbitrary = choose supportedMonthRange
 
 instance Arbitrary Quarter where
-    arbitrary = liftM MkQuarter $ choose (-30000, 200000)
+    arbitrary = fmap monthQuarter arbitrary
+    shrink (YearQuarter y qoy) =
+        fmap (\y' -> YearQuarter y' qoy) (shrinkYear y)
+            <> fmap (YearQuarter y) (shrink qoy)
 
 instance Arbitrary QuarterOfYear where
     arbitrary = liftM toEnum $ choose (1, 4)
+    shrink Q1 = []
+    shrink _ = [Q1]
 
 deriving instance Random Day
 
@@ -50,15 +90,8 @@
                 if m > 1
                     then [fromGregorian y (m - 1) d]
                     else []
-            yearShrink =
-                if y > 2000
-                    then [fromGregorian (y - 1) m d]
-                    else
-                        if y < 2000
-                            then [fromGregorian (y + 1) m d]
-                            else []
         in
-            dayShrink ++ monthShrink ++ yearShrink
+            dayShrink <> monthShrink <> fmap (\y' -> fromGregorian y' m d) (shrinkYear y)
 
 instance CoArbitrary Day where
     coarbitrary (ModifiedJulianDay d) = coarbitrary d
@@ -66,6 +99,16 @@
 instance Arbitrary CalendarDiffDays where
     arbitrary = liftM2 CalendarDiffDays arbitrary arbitrary
 
+newtype NoLeapSeconds a = MkNoLeapSeconds {unNoLeapSeconds :: a}
+    deriving newtype (Eq, Ord, Show)
+
+arbitraryNoLeapSeconds :: Arbitrary (NoLeapSeconds a) => Gen a
+arbitraryNoLeapSeconds = fmap unNoLeapSeconds arbitrary
+
+instance {-# OVERLAPPABLE #-} Arbitrary t => Arbitrary (NoLeapSeconds t) where
+    arbitrary = fmap MkNoLeapSeconds arbitrary
+    shrink (MkNoLeapSeconds t) = fmap MkNoLeapSeconds $ shrink t
+
 instance Arbitrary DiffTime where
     arbitrary = oneof [intSecs, fracSecs] -- up to 1 leap second
       where
@@ -79,6 +122,17 @@
 instance CoArbitrary DiffTime where
     coarbitrary t = coarbitrary (fromEnum t)
 
+instance Arbitrary (NoLeapSeconds DiffTime) where
+    arbitrary = fmap MkNoLeapSeconds $ oneof [intSecs, fracSecs] -- no leap second
+      where
+        intSecs = liftM secondsToDiffTime' $ choose (0, 86399)
+        fracSecs = liftM picosecondsToDiffTime' $ choose (0, 86399 * 10 ^ (12 :: Int))
+        secondsToDiffTime' :: Integer -> DiffTime
+        secondsToDiffTime' = fromInteger
+        picosecondsToDiffTime' :: Integer -> DiffTime
+        picosecondsToDiffTime' x = fromRational (x % 10 ^ (12 :: Int))
+    shrink (MkNoLeapSeconds t) = fmap MkNoLeapSeconds $ shrink t
+
 instance Arbitrary NominalDiffTime where
     arbitrary = oneof [intSecs, fracSecs]
       where
@@ -126,6 +180,10 @@
                 ++ [TimeOfDay h m' s | m' <- shrinkInt m]
                 ++ [TimeOfDay h m s' | s' <- shrinkPico s]
 
+instance Arbitrary (NoLeapSeconds TimeOfDay) where
+    arbitrary = fmap (MkNoLeapSeconds . timeToTimeOfDay) arbitraryNoLeapSeconds
+    shrink (MkNoLeapSeconds t) = fmap MkNoLeapSeconds $ shrink t
+
 instance CoArbitrary TimeOfDay where
     coarbitrary t = coarbitrary (timeOfDayToTime t)
 
@@ -136,6 +194,10 @@
 instance CoArbitrary LocalTime where
     coarbitrary t = coarbitrary (floor (utcTimeToPOSIXSeconds (localTimeToUTC utc t)) :: Integer)
 
+instance Arbitrary (NoLeapSeconds LocalTime) where
+    arbitrary = fmap MkNoLeapSeconds $ liftM2 LocalTime arbitraryNoLeapSeconds arbitraryNoLeapSeconds
+    shrink (MkNoLeapSeconds t) = fmap MkNoLeapSeconds $ shrink t
+
 instance Arbitrary TimeZone where
     arbitrary = liftM minutesToTimeZone $ choose (-720, 720)
     shrink (TimeZone 0 _ _) = []
@@ -151,12 +213,20 @@
 instance CoArbitrary ZonedTime where
     coarbitrary t = coarbitrary (floor (utcTimeToPOSIXSeconds (zonedTimeToUTC t)) :: Integer)
 
+instance Arbitrary (NoLeapSeconds ZonedTime) where
+    arbitrary = fmap MkNoLeapSeconds $ liftM2 ZonedTime arbitraryNoLeapSeconds arbitraryNoLeapSeconds
+    shrink (MkNoLeapSeconds t) = fmap MkNoLeapSeconds $ shrink t
+
 instance Arbitrary UTCTime where
     arbitrary = liftM2 UTCTime arbitrary arbitrary
     shrink t = fmap (localTimeToUTC utc) $ shrink $ utcToLocalTime utc t
 
 instance CoArbitrary UTCTime where
     coarbitrary t = coarbitrary (floor (utcTimeToPOSIXSeconds t) :: Integer)
+
+instance Arbitrary (NoLeapSeconds UTCTime) where
+    arbitrary = fmap MkNoLeapSeconds $ liftM2 UTCTime arbitraryNoLeapSeconds arbitraryNoLeapSeconds
+    shrink (MkNoLeapSeconds t) = fmap MkNoLeapSeconds $ shrink t
 
 instance Arbitrary UniversalTime where
     arbitrary = liftM (\n -> ModJulianDate $ n % k) $ choose (-313698 * k, 2973483 * k) -- 1000-01-1 to 9999-12-31
diff --git a/test/main/Test/Calendar/CalendarProps.hs b/test/main/Test/Calendar/CalendarProps.hs
--- a/test/main/Test/Calendar/CalendarProps.hs
+++ b/test/main/Test/Calendar/CalendarProps.hs
@@ -21,4 +21,4 @@
     YearQuarter y qy -> q == YearQuarter y qy
 
 testCalendarProps :: TestTree
-testCalendarProps = nameTest "calender-props" [testYearMonth, testMonthDay, testYearQuarter]
+testCalendarProps = nameTest "calendar-props" [testYearMonth, testMonthDay, testYearQuarter]
diff --git a/test/main/Test/Calendar/Duration.hs b/test/main/Test/Calendar/Duration.hs
--- a/test/main/Test/Calendar/Duration.hs
+++ b/test/main/Test/Calendar/Duration.hs
@@ -4,31 +4,27 @@
 
 import Data.Time.Calendar
 import Data.Time.Calendar.Julian
+import Test.AddDiff
 import Test.Arbitrary ()
 import Test.Tasty
 import Test.Tasty.HUnit
 import Test.Tasty.QuickCheck hiding (reason)
 
-data AddDiff = MkAddDiff
-    { adName :: String
-    , adAdd :: CalendarDiffDays -> Day -> Day
-    , adDifference :: Day -> Day -> CalendarDiffDays
-    , adFromYMD :: Integer -> Int -> Int -> Day
-    }
+type CalendarAddDiff = (AddDiff CalendarDiffDays Day, Integer -> Int -> Int -> Day)
 
-gregorianClip :: AddDiff
-gregorianClip = MkAddDiff "gregorianClip" addGregorianDurationClip diffGregorianDurationClip fromGregorian
+gregorianClip :: CalendarAddDiff
+gregorianClip = (MkAddDiff "gregorianClip" addGregorianDurationClip diffGregorianDurationClip, fromGregorian)
 
-gregorianRollOver :: AddDiff
-gregorianRollOver = MkAddDiff "gregorianRollOver" addGregorianDurationRollOver diffGregorianDurationRollOver fromGregorian
+gregorianRollOver :: CalendarAddDiff
+gregorianRollOver = (MkAddDiff "gregorianRollOver" addGregorianDurationRollOver diffGregorianDurationRollOver, fromGregorian)
 
-julianClip :: AddDiff
-julianClip = MkAddDiff "julianClip" addJulianDurationClip diffJulianDurationClip fromJulian
+julianClip :: CalendarAddDiff
+julianClip = (MkAddDiff "julianClip" addJulianDurationClip diffJulianDurationClip, fromJulian)
 
-julianRollOver :: AddDiff
-julianRollOver = MkAddDiff "julianRollOver" addJulianDurationRollOver diffJulianDurationRollOver fromJulian
+julianRollOver :: CalendarAddDiff
+julianRollOver = (MkAddDiff "julianRollOver" addJulianDurationRollOver diffJulianDurationRollOver, fromJulian)
 
-addDiffs :: [AddDiff]
+addDiffs :: [CalendarAddDiff]
 addDiffs =
     [ gregorianClip
     , gregorianRollOver
@@ -36,15 +32,11 @@
     , julianRollOver
     ]
 
-testAddDiff :: AddDiff -> TestTree
-testAddDiff MkAddDiff{..} = testProperty adName $ \day1 day2 ->
-    adAdd (adDifference day2 day1) day1 == day2
-
 testAddDiffs :: TestTree
 testAddDiffs =
     testGroup
         "add-diff"
-        $ fmap testAddDiff addDiffs
+        $ fmap (testAddDiff . fst) addDiffs
 
 newtype Smallish = MkSmallish Integer deriving (Eq, Ord)
 
@@ -56,8 +48,8 @@
         n <- if b then choose (0, 60) else return 30
         return $ MkSmallish n
 
-testPositiveDiff :: AddDiff -> TestTree
-testPositiveDiff MkAddDiff{..} = testProperty adName $ \day1 (MkSmallish i) ->
+testPositiveDiff :: CalendarAddDiff -> TestTree
+testPositiveDiff (MkAddDiff{..}, _) = testProperty adName $ \day1 (MkSmallish i) ->
     let
         day2 = addDays i day1
         r = adDifference day2 day1
@@ -70,11 +62,25 @@
         "positive-diff"
         $ fmap testPositiveDiff addDiffs
 
-testSpecific :: AddDiff -> (Integer, Int, Int) -> (Integer, Int, Int) -> (Integer, Integer) -> TestTree
-testSpecific MkAddDiff{..} (y2, m2, d2) (y1, m1, d1) (em, ed) =
+testNegativeDiff :: CalendarAddDiff -> TestTree
+testNegativeDiff (MkAddDiff{..}, _) = testProperty adName $ \day1 (MkSmallish i) ->
     let
-        day1 = adFromYMD y1 m1 d1
-        day2 = adFromYMD y2 m2 d2
+        day2 = addDays (negate i) day1
+        r = adDifference day2 day1
+    in
+        property $ cdMonths r <= 0 && cdDays r <= 0
+
+testNegativeDiffs :: TestTree
+testNegativeDiffs =
+    testGroup
+        "negative-diff"
+        $ fmap testNegativeDiff addDiffs
+
+testSpecific :: CalendarAddDiff -> (Integer, Int, Int) -> (Integer, Int, Int) -> (Integer, Integer) -> TestTree
+testSpecific (MkAddDiff{..}, fromYMD) (y2, m2, d2) (y1, m1, d1) (em, ed) =
+    let
+        day1 = fromYMD y1 m1 d1
+        day2 = fromYMD y2 m2 d2
         expected = CalendarDiffDays em ed
         found = adDifference day2 day1
     in
@@ -97,6 +103,7 @@
     testGroup
         "specific"
         [ testSpecificPair (2017, 04, 07) (2017, 04, 07) (0, 0) (0, 0)
+        , testSpecificPair (1999, 12, 31) (2000, 01, 01) (0, -1) (0, -1)
         , testSpecific gregorianClip (2017, 04, 07) (2017, 04, 01) (0, 6)
         , testSpecific gregorianClip (2017, 04, 01) (2017, 04, 07) (0, -6)
         , testSpecific gregorianClip (2017, 04, 07) (2017, 02, 01) (2, 6)
@@ -111,4 +118,4 @@
         ]
 
 testDuration :: TestTree
-testDuration = testGroup "CalendarDiffDays" [testAddDiffs, testPositiveDiffs, testSpecifics]
+testDuration = testGroup "CalendarDiffDays" [testAddDiffs, testPositiveDiffs, testNegativeDiffs, testSpecifics]
diff --git a/test/main/Test/Calendar/Easter.hs b/test/main/Test/Calendar/Easter.hs
--- a/test/main/Test/Calendar/Easter.hs
+++ b/test/main/Test/Calendar/Easter.hs
@@ -2,38 +2,126 @@
     testEaster,
 ) where
 
+import Data.Foldable
 import Data.Time.Calendar
 import Data.Time.Calendar.Easter
-import Data.Time.Format
-import Test.Calendar.EasterRef
 import Test.Tasty
 import Test.Tasty.HUnit
+import Text.Read
 
---
-days :: [Day]
-days = [ModifiedJulianDay 53000 .. ModifiedJulianDay 53014]
+easterDatesFile :: FilePath
+easterDatesFile = "test/main/Test/Calendar/EasterData/easter500.txt"
 
-showWithWDay :: Day -> String
-showWithWDay = formatTime defaultTimeLocale "%F %A"
+allEasterDatesFile :: FilePath
+allEasterDatesFile = "test/main/Test/Calendar/EasterData/easter-reference-1900-2099.csv"
 
+parseField :: Read a => String -> IO a
+parseField field =
+    case readMaybe field of
+        Just value -> return value
+        Nothing -> fail $ "invalid integer field: " ++ show field
+
+parseEasterDates :: [String] -> IO [Day]
+parseEasterDates [] = return []
+parseEasterDates (monthText : dayText : yearText : rest) = do
+    month <- parseField monthText
+    day <- parseField dayText
+    year <- parseField yearText
+    easterDay <-
+        maybe (fail $ "invalid date: " ++ unwords [yearText, monthText, dayText]) return $
+            fromGregorianValid year month day
+    (easterDay :) <$> parseEasterDates rest
+parseEasterDates fields = fail $ "easter500.txt: expected month/day/year triples, leftover fields: " ++ show fields
+
+getEasterDates :: IO [Day]
+getEasterDates = do
+    contents <- readFile easterDatesFile
+    parseEasterDates $ words contents
+
+splitCommas :: String -> [String]
+splitCommas [] = [""]
+splitCommas (',' : rest) = "" : splitCommas rest
+splitCommas (char : rest) =
+    case splitCommas rest of
+        [] -> [[char]]
+        field : fields -> (char : field) : fields
+
+testGregorianEaster :: TestTree
+testGregorianEaster =
+    testCase "Gregorian Easter" $ do
+        easterDates <- getEasterDates
+        for_ easterDates $ \expectedDate@(YearMonthDay y _ _) -> do
+            let
+                foundDate = gregorianEaster y
+            assertEqual "Gregorian Easter" expectedDate foundDate
+
+getAllEasterDates :: IO [(Day, Day, Day, Day)]
+getAllEasterDates = do
+    contents <- readFile allEasterDatesFile
+    parseAllEasterDates $ lines contents
+
+parseAllEasterDates :: [String] -> IO [(Day, Day, Day, Day)]
+parseAllEasterDates [] = fail $ allEasterDatesFile ++ ": empty file"
+parseAllEasterDates (header : rows) = do
+    let
+        expectedHeader =
+            [ "year"
+            , "golden_number"
+            , "gregorian_pfm"
+            , "gregorian_easter"
+            , "orthodox_pfm_gregorian"
+            , "orthodox_easter_gregorian"
+            ]
+    if splitCommas header == expectedHeader
+        then traverse parseAllEasterDateLine $ zip [2 :: Int ..] rows
+        else fail $ allEasterDatesFile ++ ": unexpected header: " ++ show header
+
+parseAllEasterDateLine :: (Int, String) -> IO (Day, Day, Day, Day)
+parseAllEasterDateLine (lineNumber, line) =
+    case splitCommas line of
+        [yearText, _, gregorianMoonText, gregorianEasterText, orthodoxMoonText, orthodoxEasterText] -> do
+            year <- parseField yearText
+            gregorianMoon <- parseDayField lineNumber "gregorian_pfm" gregorianMoonText
+            gregorianEasterDay <- parseDayField lineNumber "gregorian_easter" gregorianEasterText
+            orthodoxMoon <- parseDayField lineNumber "orthodox_pfm_gregorian" orthodoxMoonText
+            orthodoxEasterDay <- parseDayField lineNumber "orthodox_easter_gregorian" orthodoxEasterText
+            let
+                YearMonthDay gregorianMoonYear _ _ = gregorianMoon
+            if gregorianMoonYear == year
+                then return (gregorianMoon, gregorianEasterDay, orthodoxMoon, orthodoxEasterDay)
+                else fail $ allEasterDatesFile ++ ":" ++ show lineNumber ++ ": row year does not match Gregorian Paschal moon"
+        fields -> fail $ allEasterDatesFile ++ ":" ++ show lineNumber ++ ": expected 6 CSV fields, found " ++ show (length fields)
+
+parseDayField :: Int -> String -> String -> IO Day
+parseDayField lineNumber fieldName field =
+    case splitDate field of
+        [yearText, monthText, dayText] -> do
+            year <- parseField yearText
+            month <- parseField monthText
+            day <- parseField dayText
+            maybe
+                (fail $ allEasterDatesFile ++ ":" ++ show lineNumber ++ ": invalid " ++ fieldName ++ ": " ++ show field)
+                return
+                $ fromGregorianValid year month day
+        _ -> fail $ allEasterDatesFile ++ ":" ++ show lineNumber ++ ": invalid " ++ fieldName ++ ": " ++ show field
+
+splitDate :: String -> [String]
+splitDate [] = [""]
+splitDate ('-' : rest) = "" : splitDate rest
+splitDate (char : rest) =
+    case splitDate rest of
+        [] -> [[char]]
+        field : fields -> (char : field) : fields
+
+testAllEasters :: TestTree
+testAllEasters =
+    testCase "all Easters" $ do
+        allEasterDates <- getAllEasterDates
+        for_ allEasterDates $ \(expectedGM@(YearMonthDay y _ _), expectedGE, expectedOM, expectedOE) -> do
+            assertEqual "Gregorian Paschal moon" expectedGM $ gregorianPaschalMoon y
+            assertEqual "Gregorian Easter" expectedGE $ gregorianEaster y
+            assertEqual "Orthodox Paschal moon" expectedOM $ orthodoxPaschalMoon y
+            assertEqual "Orthodox Easter" expectedOE $ orthodoxEaster y
+
 testEaster :: TestTree
-testEaster =
-    testCase "testEaster" $
-        let
-            ds = unlines $ map (\day -> unwords [showWithWDay day, "->", showWithWDay (sundayAfter day)]) days
-            f y =
-                unwords
-                    [ show y ++ ", Gregorian: moon,"
-                    , show (gregorianPaschalMoon y) ++ ": Easter,"
-                    , showWithWDay (gregorianEaster y)
-                    ]
-                    ++ "\n"
-            g y =
-                unwords
-                    [ show y ++ ", Orthodox : moon,"
-                    , show (orthodoxPaschalMoon y) ++ ": Easter,"
-                    , showWithWDay (orthodoxEaster y)
-                    ]
-                    ++ "\n"
-        in
-            assertEqual "" testEasterRef $ ds ++ concatMap (\y -> f y ++ g y) [2000 .. 2020]
+testEaster = testGroup "testEaster" [testGregorianEaster, testAllEasters]
diff --git a/test/main/Test/Calendar/EasterData/easter-reference-1900-2099.csv b/test/main/Test/Calendar/EasterData/easter-reference-1900-2099.csv
new file mode 100644
--- /dev/null
+++ b/test/main/Test/Calendar/EasterData/easter-reference-1900-2099.csv
@@ -0,0 +1,201 @@
+year,golden_number,gregorian_pfm,gregorian_easter,orthodox_pfm_gregorian,orthodox_easter_gregorian
+1900,1,1900-04-14,1900-04-15,1900-04-18,1900-04-22
+1901,2,1901-04-03,1901-04-07,1901-04-07,1901-04-14
+1902,3,1902-03-23,1902-03-30,1902-04-26,1902-04-27
+1903,4,1903-04-11,1903-04-12,1903-04-15,1903-04-19
+1904,5,1904-03-31,1904-04-03,1904-04-04,1904-04-10
+1905,6,1905-04-18,1905-04-23,1905-04-23,1905-04-30
+1906,7,1906-04-08,1906-04-15,1906-04-12,1906-04-15
+1907,8,1907-03-28,1907-03-31,1907-05-01,1907-05-05
+1908,9,1908-04-16,1908-04-19,1908-04-20,1908-04-26
+1909,10,1909-04-05,1909-04-11,1909-04-09,1909-04-11
+1910,11,1910-03-25,1910-03-27,1910-04-28,1910-05-01
+1911,12,1911-04-13,1911-04-16,1911-04-17,1911-04-23
+1912,13,1912-04-02,1912-04-07,1912-04-06,1912-04-07
+1913,14,1913-03-22,1913-03-23,1913-04-25,1913-04-27
+1914,15,1914-04-10,1914-04-12,1914-04-14,1914-04-19
+1915,16,1915-03-30,1915-04-04,1915-04-03,1915-04-04
+1916,17,1916-04-17,1916-04-23,1916-04-22,1916-04-23
+1917,18,1917-04-07,1917-04-08,1917-04-11,1917-04-15
+1918,19,1918-03-27,1918-03-31,1918-04-30,1918-05-05
+1919,1,1919-04-14,1919-04-20,1919-04-18,1919-04-20
+1920,2,1920-04-03,1920-04-04,1920-04-07,1920-04-11
+1921,3,1921-03-23,1921-03-27,1921-04-26,1921-05-01
+1922,4,1922-04-11,1922-04-16,1922-04-15,1922-04-16
+1923,5,1923-03-31,1923-04-01,1923-04-04,1923-04-08
+1924,6,1924-04-18,1924-04-20,1924-04-23,1924-04-27
+1925,7,1925-04-08,1925-04-12,1925-04-12,1925-04-19
+1926,8,1926-03-28,1926-04-04,1926-05-01,1926-05-02
+1927,9,1927-04-16,1927-04-17,1927-04-20,1927-04-24
+1928,10,1928-04-05,1928-04-08,1928-04-09,1928-04-15
+1929,11,1929-03-25,1929-03-31,1929-04-28,1929-05-05
+1930,12,1930-04-13,1930-04-20,1930-04-17,1930-04-20
+1931,13,1931-04-02,1931-04-05,1931-04-06,1931-04-12
+1932,14,1932-03-22,1932-03-27,1932-04-25,1932-05-01
+1933,15,1933-04-10,1933-04-16,1933-04-14,1933-04-16
+1934,16,1934-03-30,1934-04-01,1934-04-03,1934-04-08
+1935,17,1935-04-17,1935-04-21,1935-04-22,1935-04-28
+1936,18,1936-04-07,1936-04-12,1936-04-11,1936-04-12
+1937,19,1937-03-27,1937-03-28,1937-04-30,1937-05-02
+1938,1,1938-04-14,1938-04-17,1938-04-18,1938-04-24
+1939,2,1939-04-03,1939-04-09,1939-04-07,1939-04-09
+1940,3,1940-03-23,1940-03-24,1940-04-26,1940-04-28
+1941,4,1941-04-11,1941-04-13,1941-04-15,1941-04-20
+1942,5,1942-03-31,1942-04-05,1942-04-04,1942-04-05
+1943,6,1943-04-18,1943-04-25,1943-04-23,1943-04-25
+1944,7,1944-04-08,1944-04-09,1944-04-12,1944-04-16
+1945,8,1945-03-28,1945-04-01,1945-05-01,1945-05-06
+1946,9,1946-04-16,1946-04-21,1946-04-20,1946-04-21
+1947,10,1947-04-05,1947-04-06,1947-04-09,1947-04-13
+1948,11,1948-03-25,1948-03-28,1948-04-28,1948-05-02
+1949,12,1949-04-13,1949-04-17,1949-04-17,1949-04-24
+1950,13,1950-04-02,1950-04-09,1950-04-06,1950-04-09
+1951,14,1951-03-22,1951-03-25,1951-04-25,1951-04-29
+1952,15,1952-04-10,1952-04-13,1952-04-14,1952-04-20
+1953,16,1953-03-30,1953-04-05,1953-04-03,1953-04-05
+1954,17,1954-04-17,1954-04-18,1954-04-22,1954-04-25
+1955,18,1955-04-07,1955-04-10,1955-04-11,1955-04-17
+1956,19,1956-03-27,1956-04-01,1956-04-30,1956-05-06
+1957,1,1957-04-14,1957-04-21,1957-04-18,1957-04-21
+1958,2,1958-04-03,1958-04-06,1958-04-07,1958-04-13
+1959,3,1959-03-23,1959-03-29,1959-04-26,1959-05-03
+1960,4,1960-04-11,1960-04-17,1960-04-15,1960-04-17
+1961,5,1961-03-31,1961-04-02,1961-04-04,1961-04-09
+1962,6,1962-04-18,1962-04-22,1962-04-23,1962-04-29
+1963,7,1963-04-08,1963-04-14,1963-04-12,1963-04-14
+1964,8,1964-03-28,1964-03-29,1964-05-01,1964-05-03
+1965,9,1965-04-16,1965-04-18,1965-04-20,1965-04-25
+1966,10,1966-04-05,1966-04-10,1966-04-09,1966-04-10
+1967,11,1967-03-25,1967-03-26,1967-04-28,1967-04-30
+1968,12,1968-04-13,1968-04-14,1968-04-17,1968-04-21
+1969,13,1969-04-02,1969-04-06,1969-04-06,1969-04-13
+1970,14,1970-03-22,1970-03-29,1970-04-25,1970-04-26
+1971,15,1971-04-10,1971-04-11,1971-04-14,1971-04-18
+1972,16,1972-03-30,1972-04-02,1972-04-03,1972-04-09
+1973,17,1973-04-17,1973-04-22,1973-04-22,1973-04-29
+1974,18,1974-04-07,1974-04-14,1974-04-11,1974-04-14
+1975,19,1975-03-27,1975-03-30,1975-04-30,1975-05-04
+1976,1,1976-04-14,1976-04-18,1976-04-18,1976-04-25
+1977,2,1977-04-03,1977-04-10,1977-04-07,1977-04-10
+1978,3,1978-03-23,1978-03-26,1978-04-26,1978-04-30
+1979,4,1979-04-11,1979-04-15,1979-04-15,1979-04-22
+1980,5,1980-03-31,1980-04-06,1980-04-04,1980-04-06
+1981,6,1981-04-18,1981-04-19,1981-04-23,1981-04-26
+1982,7,1982-04-08,1982-04-11,1982-04-12,1982-04-18
+1983,8,1983-03-28,1983-04-03,1983-05-01,1983-05-08
+1984,9,1984-04-16,1984-04-22,1984-04-20,1984-04-22
+1985,10,1985-04-05,1985-04-07,1985-04-09,1985-04-14
+1986,11,1986-03-25,1986-03-30,1986-04-28,1986-05-04
+1987,12,1987-04-13,1987-04-19,1987-04-17,1987-04-19
+1988,13,1988-04-02,1988-04-03,1988-04-06,1988-04-10
+1989,14,1989-03-22,1989-03-26,1989-04-25,1989-04-30
+1990,15,1990-04-10,1990-04-15,1990-04-14,1990-04-15
+1991,16,1991-03-30,1991-03-31,1991-04-03,1991-04-07
+1992,17,1992-04-17,1992-04-19,1992-04-22,1992-04-26
+1993,18,1993-04-07,1993-04-11,1993-04-11,1993-04-18
+1994,19,1994-03-27,1994-04-03,1994-04-30,1994-05-01
+1995,1,1995-04-14,1995-04-16,1995-04-18,1995-04-23
+1996,2,1996-04-03,1996-04-07,1996-04-07,1996-04-14
+1997,3,1997-03-23,1997-03-30,1997-04-26,1997-04-27
+1998,4,1998-04-11,1998-04-12,1998-04-15,1998-04-19
+1999,5,1999-03-31,1999-04-04,1999-04-04,1999-04-11
+2000,6,2000-04-18,2000-04-23,2000-04-23,2000-04-30
+2001,7,2001-04-08,2001-04-15,2001-04-12,2001-04-15
+2002,8,2002-03-28,2002-03-31,2002-05-01,2002-05-05
+2003,9,2003-04-16,2003-04-20,2003-04-20,2003-04-27
+2004,10,2004-04-05,2004-04-11,2004-04-09,2004-04-11
+2005,11,2005-03-25,2005-03-27,2005-04-28,2005-05-01
+2006,12,2006-04-13,2006-04-16,2006-04-17,2006-04-23
+2007,13,2007-04-02,2007-04-08,2007-04-06,2007-04-08
+2008,14,2008-03-22,2008-03-23,2008-04-25,2008-04-27
+2009,15,2009-04-10,2009-04-12,2009-04-14,2009-04-19
+2010,16,2010-03-30,2010-04-04,2010-04-03,2010-04-04
+2011,17,2011-04-17,2011-04-24,2011-04-22,2011-04-24
+2012,18,2012-04-07,2012-04-08,2012-04-11,2012-04-15
+2013,19,2013-03-27,2013-03-31,2013-04-30,2013-05-05
+2014,1,2014-04-14,2014-04-20,2014-04-18,2014-04-20
+2015,2,2015-04-03,2015-04-05,2015-04-07,2015-04-12
+2016,3,2016-03-23,2016-03-27,2016-04-26,2016-05-01
+2017,4,2017-04-11,2017-04-16,2017-04-15,2017-04-16
+2018,5,2018-03-31,2018-04-01,2018-04-04,2018-04-08
+2019,6,2019-04-18,2019-04-21,2019-04-23,2019-04-28
+2020,7,2020-04-08,2020-04-12,2020-04-12,2020-04-19
+2021,8,2021-03-28,2021-04-04,2021-05-01,2021-05-02
+2022,9,2022-04-16,2022-04-17,2022-04-20,2022-04-24
+2023,10,2023-04-05,2023-04-09,2023-04-09,2023-04-16
+2024,11,2024-03-25,2024-03-31,2024-04-28,2024-05-05
+2025,12,2025-04-13,2025-04-20,2025-04-17,2025-04-20
+2026,13,2026-04-02,2026-04-05,2026-04-06,2026-04-12
+2027,14,2027-03-22,2027-03-28,2027-04-25,2027-05-02
+2028,15,2028-04-10,2028-04-16,2028-04-14,2028-04-16
+2029,16,2029-03-30,2029-04-01,2029-04-03,2029-04-08
+2030,17,2030-04-17,2030-04-21,2030-04-22,2030-04-28
+2031,18,2031-04-07,2031-04-13,2031-04-11,2031-04-13
+2032,19,2032-03-27,2032-03-28,2032-04-30,2032-05-02
+2033,1,2033-04-14,2033-04-17,2033-04-18,2033-04-24
+2034,2,2034-04-03,2034-04-09,2034-04-07,2034-04-09
+2035,3,2035-03-23,2035-03-25,2035-04-26,2035-04-29
+2036,4,2036-04-11,2036-04-13,2036-04-15,2036-04-20
+2037,5,2037-03-31,2037-04-05,2037-04-04,2037-04-05
+2038,6,2038-04-18,2038-04-25,2038-04-23,2038-04-25
+2039,7,2039-04-08,2039-04-10,2039-04-12,2039-04-17
+2040,8,2040-03-28,2040-04-01,2040-05-01,2040-05-06
+2041,9,2041-04-16,2041-04-21,2041-04-20,2041-04-21
+2042,10,2042-04-05,2042-04-06,2042-04-09,2042-04-13
+2043,11,2043-03-25,2043-03-29,2043-04-28,2043-05-03
+2044,12,2044-04-13,2044-04-17,2044-04-17,2044-04-24
+2045,13,2045-04-02,2045-04-09,2045-04-06,2045-04-09
+2046,14,2046-03-22,2046-03-25,2046-04-25,2046-04-29
+2047,15,2047-04-10,2047-04-14,2047-04-14,2047-04-21
+2048,16,2048-03-30,2048-04-05,2048-04-03,2048-04-05
+2049,17,2049-04-17,2049-04-18,2049-04-22,2049-04-25
+2050,18,2050-04-07,2050-04-10,2050-04-11,2050-04-17
+2051,19,2051-03-27,2051-04-02,2051-04-30,2051-05-07
+2052,1,2052-04-14,2052-04-21,2052-04-18,2052-04-21
+2053,2,2053-04-03,2053-04-06,2053-04-07,2053-04-13
+2054,3,2054-03-23,2054-03-29,2054-04-26,2054-05-03
+2055,4,2055-04-11,2055-04-18,2055-04-15,2055-04-18
+2056,5,2056-03-31,2056-04-02,2056-04-04,2056-04-09
+2057,6,2057-04-18,2057-04-22,2057-04-23,2057-04-29
+2058,7,2058-04-08,2058-04-14,2058-04-12,2058-04-14
+2059,8,2059-03-28,2059-03-30,2059-05-01,2059-05-04
+2060,9,2060-04-16,2060-04-18,2060-04-20,2060-04-25
+2061,10,2061-04-05,2061-04-10,2061-04-09,2061-04-10
+2062,11,2062-03-25,2062-03-26,2062-04-28,2062-04-30
+2063,12,2063-04-13,2063-04-15,2063-04-17,2063-04-22
+2064,13,2064-04-02,2064-04-06,2064-04-06,2064-04-13
+2065,14,2065-03-22,2065-03-29,2065-04-25,2065-04-26
+2066,15,2066-04-10,2066-04-11,2066-04-14,2066-04-18
+2067,16,2067-03-30,2067-04-03,2067-04-03,2067-04-10
+2068,17,2068-04-17,2068-04-22,2068-04-22,2068-04-29
+2069,18,2069-04-07,2069-04-14,2069-04-11,2069-04-14
+2070,19,2070-03-27,2070-03-30,2070-04-30,2070-05-04
+2071,1,2071-04-14,2071-04-19,2071-04-18,2071-04-19
+2072,2,2072-04-03,2072-04-10,2072-04-07,2072-04-10
+2073,3,2073-03-23,2073-03-26,2073-04-26,2073-04-30
+2074,4,2074-04-11,2074-04-15,2074-04-15,2074-04-22
+2075,5,2075-03-31,2075-04-07,2075-04-04,2075-04-07
+2076,6,2076-04-18,2076-04-19,2076-04-23,2076-04-26
+2077,7,2077-04-08,2077-04-11,2077-04-12,2077-04-18
+2078,8,2078-03-28,2078-04-03,2078-05-01,2078-05-08
+2079,9,2079-04-16,2079-04-23,2079-04-20,2079-04-23
+2080,10,2080-04-05,2080-04-07,2080-04-09,2080-04-14
+2081,11,2081-03-25,2081-03-30,2081-04-28,2081-05-04
+2082,12,2082-04-13,2082-04-19,2082-04-17,2082-04-19
+2083,13,2083-04-02,2083-04-04,2083-04-06,2083-04-11
+2084,14,2084-03-22,2084-03-26,2084-04-25,2084-04-30
+2085,15,2085-04-10,2085-04-15,2085-04-14,2085-04-15
+2086,16,2086-03-30,2086-03-31,2086-04-03,2086-04-07
+2087,17,2087-04-17,2087-04-20,2087-04-22,2087-04-27
+2088,18,2088-04-07,2088-04-11,2088-04-11,2088-04-18
+2089,19,2089-03-27,2089-04-03,2089-04-30,2089-05-01
+2090,1,2090-04-14,2090-04-16,2090-04-18,2090-04-23
+2091,2,2091-04-03,2091-04-08,2091-04-07,2091-04-08
+2092,3,2092-03-23,2092-03-30,2092-04-26,2092-04-27
+2093,4,2093-04-11,2093-04-12,2093-04-15,2093-04-19
+2094,5,2094-03-31,2094-04-04,2094-04-04,2094-04-11
+2095,6,2095-04-18,2095-04-24,2095-04-23,2095-04-24
+2096,7,2096-04-08,2096-04-15,2096-04-12,2096-04-15
+2097,8,2097-03-28,2097-03-31,2097-05-01,2097-05-05
+2098,9,2098-04-16,2098-04-20,2098-04-20,2098-04-27
+2099,10,2099-04-05,2099-04-12,2099-04-09,2099-04-12
diff --git a/test/main/Test/Calendar/EasterData/easter500.txt b/test/main/Test/Calendar/EasterData/easter500.txt
new file mode 100644
--- /dev/null
+++ b/test/main/Test/Calendar/EasterData/easter500.txt
@@ -0,0 +1,500 @@
+  4        2    1600
+  4       22    1601
+  4        7    1602
+  3       30    1603
+  4       18    1604
+  4       10    1605
+  3       26    1606
+  4       15    1607
+  4        6    1608
+  4       19    1609
+  4       11    1610
+  4        3    1611
+  4       22    1612
+  4        7    1613
+  3       30    1614
+  4       19    1615
+  4        3    1616
+  3       26    1617
+  4       15    1618
+  3       31    1619
+  4       19    1620
+  4       11    1621
+  3       27    1622
+  4       16    1623
+  4        7    1624
+  3       30    1625
+  4       12    1626
+  4        4    1627
+  4       23    1628
+  4       15    1629
+  3       31    1630
+  4       20    1631
+  4       11    1632
+  3       27    1633
+  4       16    1634
+  4        8    1635
+  3       23    1636
+  4       12    1637
+  4        4    1638
+  4       24    1639
+  4        8    1640
+  3       31    1641
+  4       20    1642
+  4        5    1643
+  3       27    1644
+  4       16    1645
+  4        1    1646
+  4       21    1647
+  4       12    1648
+  4        4    1649
+  4       17    1650
+  4        9    1651
+  3       31    1652
+  4       13    1653
+  4        5    1654
+  3       28    1655
+  4       16    1656
+  4        1    1657
+  4       21    1658
+  4       13    1659
+  3       28    1660
+  4       17    1661
+  4        9    1662
+  3       25    1663
+  4       13    1664
+  4        5    1665
+  4       25    1666
+  4       10    1667
+  4        1    1668
+  4       21    1669
+  4        6    1670
+  3       29    1671
+  4       17    1672
+  4        2    1673
+  3       25    1674
+  4       14    1675
+  4        5    1676
+  4       18    1677
+  4       10    1678
+  4        2    1679
+  4       21    1680
+  4        6    1681
+  3       29    1682
+  4       18    1683
+  4        2    1684
+  4       22    1685
+  4       14    1686
+  3       30    1687
+  4       18    1688
+  4       10    1689
+  3       26    1690
+  4       15    1691
+  4        6    1692
+  3       22    1693
+  4       11    1694
+  4        3    1695
+  4       22    1696
+  4        7    1697
+  3       30    1698
+  4       19    1699
+  4       11    1700
+  3       27    1701
+  4       16    1702
+  4        8    1703
+  3       23    1704
+  4       12    1705
+  4        4    1706
+  4       24    1707
+  4        8    1708
+  3       31    1709
+  4       20    1710
+  4        5    1711
+  3       27    1712
+  4       16    1713
+  4        1    1714
+  4       21    1715
+  4       12    1716
+  3       28    1717
+  4       17    1718
+  4        9    1719
+  3       31    1720
+  4       13    1721
+  4        5    1722
+  3       28    1723
+  4       16    1724
+  4        1    1725
+  4       21    1726
+  4       13    1727
+  3       28    1728
+  4       17    1729
+  4        9    1730
+  3       25    1731
+  4       13    1732
+  4        5    1733
+  4       25    1734
+  4       10    1735
+  4        1    1736
+  4       21    1737
+  4        6    1738
+  3       29    1739
+  4       17    1740
+  4        2    1741
+  3       25    1742
+  4       14    1743
+  4        5    1744
+  4       18    1745
+  4       10    1746
+  4        2    1747
+  4       14    1748
+  4        6    1749
+  3       29    1750
+  4       11    1751
+  4        2    1752
+  4       22    1753
+  4       14    1754
+  3       30    1755
+  4       18    1756
+  4       10    1757
+  3       26    1758
+  4       15    1759
+  4        6    1760
+  3       22    1761
+  4       11    1762
+  4        3    1763
+  4       22    1764
+  4        7    1765
+  3       30    1766
+  4       19    1767
+  4        3    1768
+  3       26    1769
+  4       15    1770
+  3       31    1771
+  4       19    1772
+  4       11    1773
+  4        3    1774
+  4       16    1775
+  4        7    1776
+  3       30    1777
+  4       19    1778
+  4        4    1779
+  3       26    1780
+  4       15    1781
+  3       31    1782
+  4       20    1783
+  4       11    1784
+  3       27    1785
+  4       16    1786
+  4        8    1787
+  3       23    1788
+  4       12    1789
+  4        4    1790
+  4       24    1791
+  4        8    1792
+  3       31    1793
+  4       20    1794
+  4        5    1795
+  3       27    1796
+  4       16    1797
+  4        8    1798
+  3       24    1799
+  4       13    1800
+  4        5    1801
+  4       18    1802
+  4       10    1803
+  4        1    1804
+  4       14    1805
+  4        6    1806
+  3       29    1807
+  4       17    1808
+  4        2    1809
+  4       22    1810
+  4       14    1811
+  3       29    1812
+  4       18    1813
+  4       10    1814
+  3       26    1815
+  4       14    1816
+  4        6    1817
+  3       22    1818
+  4       11    1819
+  4        2    1820
+  4       22    1821
+  4        7    1822
+  3       30    1823
+  4       18    1824
+  4        3    1825
+  3       26    1826
+  4       15    1827
+  4        6    1828
+  4       19    1829
+  4       11    1830
+  4        3    1831
+  4       22    1832
+  4        7    1833
+  3       30    1834
+  4       19    1835
+  4        3    1836
+  3       26    1837
+  4       15    1838
+  3       31    1839
+  4       19    1840
+  4       11    1841
+  3       27    1842
+  4       16    1843
+  4        7    1844
+  3       23    1845
+  4       12    1846
+  4        4    1847
+  4       23    1848
+  4        8    1849
+  3       31    1850
+  4       20    1851
+  4       11    1852
+  3       27    1853
+  4       16    1854
+  4        8    1855
+  3       23    1856
+  4       12    1857
+  4        4    1858
+  4       24    1859
+  4        8    1860
+  3       31    1861
+  4       20    1862
+  4        5    1863
+  3       27    1864
+  4       16    1865
+  4        1    1866
+  4       21    1867
+  4       12    1868
+  3       28    1869
+  4       17    1870
+  4        9    1871
+  3       31    1872
+  4       13    1873
+  4        5    1874
+  3       28    1875
+  4       16    1876
+  4        1    1877
+  4       21    1878
+  4       13    1879
+  3       28    1880
+  4       17    1881
+  4        9    1882
+  3       25    1883
+  4       13    1884
+  4        5    1885
+  4       25    1886
+  4       10    1887
+  4        1    1888
+  4       21    1889
+  4        6    1890
+  3       29    1891
+  4       17    1892
+  4        2    1893
+  3       25    1894
+  4       14    1895
+  4        5    1896
+  4       18    1897
+  4       10    1898
+  4        2    1899
+  4       15    1900
+  4        7    1901
+  3       30    1902
+  4       12    1903
+  4        3    1904
+  4       23    1905
+  4       15    1906
+  3       31    1907
+  4       19    1908
+  4       11    1909
+  3       27    1910
+  4       16    1911
+  4        7    1912
+  3       23    1913
+  4       12    1914
+  4        4    1915
+  4       23    1916
+  4        8    1917
+  3       31    1918
+  4       20    1919
+  4        4    1920
+  3       27    1921
+  4       16    1922
+  4        1    1923
+  4       20    1924
+  4       12    1925
+  4        4    1926
+  4       17    1927
+  4        8    1928
+  3       31    1929
+  4       20    1930
+  4        5    1931
+  3       27    1932
+  4       16    1933
+  4        1    1934
+  4       21    1935
+  4       12    1936
+  3       28    1937
+  4       17    1938
+  4        9    1939
+  3       24    1940
+  4       13    1941
+  4        5    1942
+  4       25    1943
+  4        9    1944
+  4        1    1945
+  4       21    1946
+  4        6    1947
+  3       28    1948
+  4       17    1949
+  4        9    1950
+  3       25    1951
+  4       13    1952
+  4        5    1953
+  4       18    1954
+  4       10    1955
+  4        1    1956
+  4       21    1957
+  4        6    1958
+  3       29    1959
+  4       17    1960
+  4        2    1961
+  4       22    1962
+  4       14    1963
+  3       29    1964
+  4       18    1965
+  4       10    1966
+  3       26    1967
+  4       14    1968
+  4        6    1969
+  3       29    1970
+  4       11    1971
+  4        2    1972
+  4       22    1973
+  4       14    1974
+  3       30    1975
+  4       18    1976
+  4       10    1977
+  3       26    1978
+  4       15    1979
+  4        6    1980
+  4       19    1981
+  4       11    1982
+  4        3    1983
+  4       22    1984
+  4        7    1985
+  3       30    1986
+  4       19    1987
+  4        3    1988
+  3       26    1989
+  4       15    1990
+  3       31    1991
+  4       19    1992
+  4       11    1993
+  4        3    1994
+  4       16    1995
+  4        7    1996
+  3       30    1997
+  4       12    1998
+  4        4    1999
+  4       23    2000
+  4       15    2001
+  3       31    2002
+  4       20    2003
+  4       11    2004
+  3       27    2005
+  4       16    2006
+  4        8    2007
+  3       23    2008
+  4       12    2009
+  4        4    2010
+  4       24    2011
+  4        8    2012
+  3       31    2013
+  4       20    2014
+  4        5    2015
+  3       27    2016
+  4       16    2017
+  4        1    2018
+  4       21    2019
+  4       12    2020
+  4        4    2021
+  4       17    2022
+  4        9    2023
+  3       31    2024
+  4       20    2025
+  4        5    2026
+  3       28    2027
+  4       16    2028
+  4        1    2029
+  4       21    2030
+  4       13    2031
+  3       28    2032
+  4       17    2033
+  4        9    2034
+  3       25    2035
+  4       13    2036
+  4        5    2037
+  4       25    2038
+  4       10    2039
+  4        1    2040
+  4       21    2041
+  4        6    2042
+  3       29    2043
+  4       17    2044
+  4        9    2045
+  3       25    2046
+  4       14    2047
+  4        5    2048
+  4       18    2049
+  4       10    2050
+  4        2    2051
+  4       21    2052
+  4        6    2053
+  3       29    2054
+  4       18    2055
+  4        2    2056
+  4       22    2057
+  4       14    2058
+  3       30    2059
+  4       18    2060
+  4       10    2061
+  3       26    2062
+  4       15    2063
+  4        6    2064
+  3       29    2065
+  4       11    2066
+  4        3    2067
+  4       22    2068
+  4       14    2069
+  3       30    2070
+  4       19    2071
+  4       10    2072
+  3       26    2073
+  4       15    2074
+  4        7    2075
+  4       19    2076
+  4       11    2077
+  4        3    2078
+  4       23    2079
+  4        7    2080
+  3       30    2081
+  4       19    2082
+  4        4    2083
+  3       26    2084
+  4       15    2085
+  3       31    2086
+  4       20    2087
+  4       11    2088
+  4        3    2089
+  4       16    2090
+  4        8    2091
+  3       30    2092
+  4       12    2093
+  4        4    2094
+  4       24    2095
+  4       15    2096
+  3       31    2097
+  4       20    2098
+  4       12    2099
diff --git a/test/main/Test/Calendar/EasterRef.hs b/test/main/Test/Calendar/EasterRef.hs
deleted file mode 100644
--- a/test/main/Test/Calendar/EasterRef.hs
+++ /dev/null
@@ -1,63 +0,0 @@
-module Test.Calendar.EasterRef where
-
-testEasterRef :: String
-testEasterRef =
-    unlines
-        [ "2003-12-27 Saturday -> 2003-12-28 Sunday"
-        , "2003-12-28 Sunday -> 2004-01-04 Sunday"
-        , "2003-12-29 Monday -> 2004-01-04 Sunday"
-        , "2003-12-30 Tuesday -> 2004-01-04 Sunday"
-        , "2003-12-31 Wednesday -> 2004-01-04 Sunday"
-        , "2004-01-01 Thursday -> 2004-01-04 Sunday"
-        , "2004-01-02 Friday -> 2004-01-04 Sunday"
-        , "2004-01-03 Saturday -> 2004-01-04 Sunday"
-        , "2004-01-04 Sunday -> 2004-01-11 Sunday"
-        , "2004-01-05 Monday -> 2004-01-11 Sunday"
-        , "2004-01-06 Tuesday -> 2004-01-11 Sunday"
-        , "2004-01-07 Wednesday -> 2004-01-11 Sunday"
-        , "2004-01-08 Thursday -> 2004-01-11 Sunday"
-        , "2004-01-09 Friday -> 2004-01-11 Sunday"
-        , "2004-01-10 Saturday -> 2004-01-11 Sunday"
-        , "2000, Gregorian: moon, 2000-04-18: Easter, 2000-04-23 Sunday"
-        , "2000, Orthodox : moon, 2000-04-23: Easter, 2000-04-30 Sunday"
-        , "2001, Gregorian: moon, 2001-04-08: Easter, 2001-04-15 Sunday"
-        , "2001, Orthodox : moon, 2001-04-12: Easter, 2001-04-15 Sunday"
-        , "2002, Gregorian: moon, 2002-03-28: Easter, 2002-03-31 Sunday"
-        , "2002, Orthodox : moon, 2002-05-01: Easter, 2002-05-05 Sunday"
-        , "2003, Gregorian: moon, 2003-04-16: Easter, 2003-04-20 Sunday"
-        , "2003, Orthodox : moon, 2003-04-20: Easter, 2003-04-27 Sunday"
-        , "2004, Gregorian: moon, 2004-04-05: Easter, 2004-04-11 Sunday"
-        , "2004, Orthodox : moon, 2004-04-09: Easter, 2004-04-11 Sunday"
-        , "2005, Gregorian: moon, 2005-03-25: Easter, 2005-03-27 Sunday"
-        , "2005, Orthodox : moon, 2005-04-28: Easter, 2005-05-01 Sunday"
-        , "2006, Gregorian: moon, 2006-04-13: Easter, 2006-04-16 Sunday"
-        , "2006, Orthodox : moon, 2006-04-17: Easter, 2006-04-23 Sunday"
-        , "2007, Gregorian: moon, 2007-04-02: Easter, 2007-04-08 Sunday"
-        , "2007, Orthodox : moon, 2007-04-06: Easter, 2007-04-08 Sunday"
-        , "2008, Gregorian: moon, 2008-03-22: Easter, 2008-03-23 Sunday"
-        , "2008, Orthodox : moon, 2008-04-25: Easter, 2008-04-27 Sunday"
-        , "2009, Gregorian: moon, 2009-04-10: Easter, 2009-04-12 Sunday"
-        , "2009, Orthodox : moon, 2009-04-14: Easter, 2009-04-19 Sunday"
-        , "2010, Gregorian: moon, 2010-03-30: Easter, 2010-04-04 Sunday"
-        , "2010, Orthodox : moon, 2010-04-03: Easter, 2010-04-04 Sunday"
-        , "2011, Gregorian: moon, 2011-04-18: Easter, 2011-04-24 Sunday"
-        , "2011, Orthodox : moon, 2011-04-22: Easter, 2011-04-24 Sunday"
-        , "2012, Gregorian: moon, 2012-04-07: Easter, 2012-04-08 Sunday"
-        , "2012, Orthodox : moon, 2012-04-11: Easter, 2012-04-15 Sunday"
-        , "2013, Gregorian: moon, 2013-03-27: Easter, 2013-03-31 Sunday"
-        , "2013, Orthodox : moon, 2013-04-30: Easter, 2013-05-05 Sunday"
-        , "2014, Gregorian: moon, 2014-04-14: Easter, 2014-04-20 Sunday"
-        , "2014, Orthodox : moon, 2014-04-18: Easter, 2014-04-20 Sunday"
-        , "2015, Gregorian: moon, 2015-04-03: Easter, 2015-04-05 Sunday"
-        , "2015, Orthodox : moon, 2015-04-07: Easter, 2015-04-12 Sunday"
-        , "2016, Gregorian: moon, 2016-03-23: Easter, 2016-03-27 Sunday"
-        , "2016, Orthodox : moon, 2016-04-26: Easter, 2016-05-01 Sunday"
-        , "2017, Gregorian: moon, 2017-04-11: Easter, 2017-04-16 Sunday"
-        , "2017, Orthodox : moon, 2017-04-15: Easter, 2017-04-16 Sunday"
-        , "2018, Gregorian: moon, 2018-03-31: Easter, 2018-04-01 Sunday"
-        , "2018, Orthodox : moon, 2018-04-04: Easter, 2018-04-08 Sunday"
-        , "2019, Gregorian: moon, 2019-04-18: Easter, 2019-04-21 Sunday"
-        , "2019, Orthodox : moon, 2019-04-23: Easter, 2019-04-28 Sunday"
-        , "2020, Gregorian: moon, 2020-04-08: Easter, 2020-04-12 Sunday"
-        , "2020, Orthodox : moon, 2020-04-12: Easter, 2020-04-19 Sunday"
-        ]
diff --git a/test/main/Test/Calendar/MonthDay.hs b/test/main/Test/Calendar/MonthDay.hs
--- a/test/main/Test/Calendar/MonthDay.hs
+++ b/test/main/Test/Calendar/MonthDay.hs
@@ -2,6 +2,8 @@
     testMonthDay,
 ) where
 
+import Data.Time.Calendar
+import Data.Time.Calendar.Month
 import Data.Time.Calendar.MonthDay
 import Test.Calendar.MonthDayRef
 import Test.Tasty
@@ -14,10 +16,23 @@
 
 testMonthDay :: TestTree
 testMonthDay =
-    testCase "testMonthDay" $
-        assertEqual "" testMonthDayRef $
-            concat $
-                map (\isL -> unlines (leap isL : yearDays isL)) [False, True]
+    testGroup
+        "MonthDay"
+        [ testCase "good" $
+            assertEqual "" testMonthDayRef $
+                concat $
+                    map (\isL -> unlines (leap isL : yearDays isL)) [False, True]
+        , testGroup
+            "clip"
+            [ testCase "12" $ assertEqual "" (YearMonthDay 2005 05 12) (MonthDay (MkMonth 24064) 12)
+            , testCase "1" $ assertEqual "" (YearMonthDay 2005 05 1) (MonthDay (MkMonth 24064) 1)
+            , testCase "0" $ assertEqual "" (YearMonthDay 2005 05 1) (MonthDay (MkMonth 24064) 0)
+            , testCase "-1" $ assertEqual "" (YearMonthDay 2005 05 1) (MonthDay (MkMonth 24064) (-1))
+            , testCase "31" $ assertEqual "" (YearMonthDay 2005 05 31) (MonthDay (MkMonth 24064) 31)
+            , testCase "32" $ assertEqual "" (YearMonthDay 2005 05 31) (MonthDay (MkMonth 24064) 32)
+            , testCase "33" $ assertEqual "" (YearMonthDay 2005 05 31) (MonthDay (MkMonth 24064) 33)
+            ]
+        ]
   where
     leap isLeap =
         if isLeap
diff --git a/test/main/Test/Clock/Conversion.hs b/test/main/Test/Clock/Conversion.hs
--- a/test/main/Test/Clock/Conversion.hs
+++ b/test/main/Test/Clock/Conversion.hs
@@ -3,6 +3,7 @@
 ) where
 
 import Data.Time.Clock
+import Data.Time.Clock.POSIX
 import Data.Time.Clock.System
 import Test.Tasty
 import Test.Tasty.HUnit
@@ -17,6 +18,19 @@
                     [ testCase "systemToUTCTime" $ assertEqual (show ut) ut $ systemToUTCTime st
                     , testCase "utcToSystemTime" $ assertEqual (show ut) st $ utcToSystemTime ut
                     ]
+            testPOSIXTime :: (SystemTime, POSIXTime) -> TestTree
+            testPOSIXTime (st, pt) =
+                testGroup
+                    (show st)
+                    [ testCase "expected" $
+                        assertEqual (show st) pt $
+                            systemToPOSIXTime st
+                    , testCase "matches systemToUTCTime" $
+                        assertEqual
+                            (show st)
+                            (utcTimeToPOSIXSeconds $ systemToUTCTime st)
+                            (systemToPOSIXTime st)
+                    ]
         in
             [ testPair (MkSystemTime 0 0, UTCTime systemEpochDay 0)
             , testPair (MkSystemTime 86399 0, UTCTime systemEpochDay 86399)
@@ -24,4 +38,13 @@
             , testPair (MkSystemTime 86399 1000000000, UTCTime systemEpochDay 86400)
             , testPair (MkSystemTime 86399 1999999999, UTCTime systemEpochDay 86400.999999999)
             , testPair (MkSystemTime 86400 0, UTCTime (succ systemEpochDay) 0)
+            , testGroup
+                "systemToPOSIXTime"
+                [ testPOSIXTime (MkSystemTime 0 0, 0)
+                , testPOSIXTime (MkSystemTime 86399 999999999, 86399.999999999)
+                , testPOSIXTime (MkSystemTime 86399 1000000000, 86400)
+                , testPOSIXTime (MkSystemTime 86399 1500000000, 86400)
+                , testPOSIXTime (MkSystemTime 86399 1999999999, 86400)
+                , testPOSIXTime (MkSystemTime 86400 0, 86400)
+                ]
             ]
diff --git a/test/main/Test/Clock/Lift.hs b/test/main/Test/Clock/Lift.hs
deleted file mode 100644
--- a/test/main/Test/Clock/Lift.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Test.Clock.Lift (
-    testLift,
-) where
-
-import Data.Time.Clock
-import qualified Language.Haskell.TH.Syntax as TH
-import Test.Tasty
-import Test.Tasty.HUnit
-
-testLift :: TestTree
-testLift =
-    testGroup
-        "Lift instances"
-        [ testCase "DiffTime" $ $$(TH.liftTyped (secondsToDiffTime 100)) @?= secondsToDiffTime 100
-        , testCase "NominalDiffTime" $ $$(TH.liftTyped (secondsToNominalDiffTime 100)) @?= secondsToNominalDiffTime 100
-        ]
diff --git a/test/main/Test/Clock/Pattern.hs b/test/main/Test/Clock/Pattern.hs
new file mode 100644
--- /dev/null
+++ b/test/main/Test/Clock/Pattern.hs
@@ -0,0 +1,32 @@
+module Test.Clock.Pattern (
+    testClockPatterns,
+) where
+
+import Data.Time.Clock
+import Test.Tasty
+import Test.Tasty.HUnit
+
+testClockPatterns :: TestTree
+testClockPatterns =
+    let
+        testSame :: forall a. (Eq a, Show a) => TestName -> a -> a -> TestTree
+        testSame name expected found = testCase name $ assertEqual "" expected found
+    in
+        testGroup "pattern" $
+            [ testGroup
+                "construct"
+                [ testSame "Seconds" 52.4 (Seconds 52.4)
+                , testSame "Seconds-Picoseconds" (Picoseconds 7_276_000_000_000) (Seconds 7.276)
+                , testSame "Minutes-Seconds" (Seconds 210) (Minutes 3.5)
+                , testSame "Hours-Minutes" (Minutes 120) (Hours 2)
+                , testSame "Nominal" 37.4 (Nominal 37.4)
+                ]
+            , testGroup
+                "deconstruct"
+                [ testSame "Seconds" 52.4 ((\(Seconds x) -> x) 52.4)
+                , testSame "Seconds-Picoseconds" 7.276 ((\(Seconds x) -> x) $ Picoseconds 7_276_000_000_000)
+                , testSame "Minutes-Seconds" 3.5 ((\(Minutes x) -> x) $ Seconds 210)
+                , testSame "Hours-Minutes" 2 ((\(Hours x) -> x) $ Minutes 120)
+                , testSame "Nominal" 37.4 ((\(Nominal x) -> x) 37.4)
+                ]
+            ]
diff --git a/test/main/Test/Format/Compile.hs b/test/main/Test/Format/Compile.hs
--- a/test/main/Test/Format/Compile.hs
+++ b/test/main/Test/Format/Compile.hs
@@ -1,6 +1,4 @@
 -- Tests succeed if module compiles
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
 module Test.Format.Compile (
 
 ) where
diff --git a/test/main/Test/Format/Format.hs b/test/main/Test/Format/Format.hs
--- a/test/main/Test/Format/Format.hs
+++ b/test/main/Test/Format/Format.hs
@@ -9,12 +9,12 @@
 import Test.TestUtil
 
 -- as found in http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
--- plus FgGklz
+-- plus FgGklvz
 -- f not supported
 -- P not always supported
 -- s time-zone dependent
 chars :: [Char]
-chars = "aAbBcCdDeFgGhHIjklmMnprRStTuUVwWxXyYzZ%"
+chars = "aAbBcCdDeFgGhHIjklmMnprRStTuUvVwWxXyYzZ%"
 
 -- as found in "man strftime" on a glibc system. '#' is different, though
 modifiers :: [Char]
@@ -163,20 +163,20 @@
         , testAFormat "%3ES" "01.245" (61.24582 :: DiffTime)
         ]
 
-testCalenderDiffDays :: TestTree
-testCalenderDiffDays =
+testCalendarDiffDays :: TestTree
+testCalendarDiffDays =
     testGroup
-        "CalenderDiffDays"
+        "CalendarDiffDays"
         [ testAFormat "%yy%Bm%ww%Dd" "5y4m3w2d" $ CalendarDiffDays 64 23
         , testAFormat "%bm %dd" "64m 23d" $ CalendarDiffDays 64 23
         , testAFormat "%yy%Bm%ww%Dd" "-5y-4m-3w-2d" $ CalendarDiffDays (-64) (-23)
         , testAFormat "%bm %dd" "-64m -23d" $ CalendarDiffDays (-64) (-23)
         ]
 
-testCalenderDiffTime :: TestTree
-testCalenderDiffTime =
+testCalendarDiffTime :: TestTree
+testCalendarDiffTime =
     testGroup
-        "CalenderDiffTime"
+        "CalendarDiffTime"
         [ testAFormat "%yy%Bm%ww%Dd%Hh%Mm%Ss" "5y4m3w2d2h22m8s" $ CalendarDiffTime 64 $ 23 * 86400 + 8528.21
         , testAFormat "%yy%Bm%ww%Dd%Hh%Mm%ESs" "5y4m3w2d2h22m8.21s" $ CalendarDiffTime 64 $ 23 * 86400 + 8528.21
         , testAFormat "%yy%Bm%ww%Dd%Hh%Mm%0ESs" "5y4m3w2d2h22m08.210000000000s" $
@@ -207,6 +207,6 @@
         , testTimeZone
         , testNominalDiffTime
         , testDiffTime
-        , testCalenderDiffDays
-        , testCalenderDiffTime
+        , testCalendarDiffDays
+        , testCalendarDiffTime
         ]
diff --git a/test/main/Test/Format/ISO8601.hs b/test/main/Test/Format/ISO8601.hs
--- a/test/main/Test/Format/ISO8601.hs
+++ b/test/main/Test/Format/ISO8601.hs
@@ -4,10 +4,10 @@
     testISO8601,
 ) where
 
+import Data.Coerce
 import Data.Ratio
 import Data.Time
 import Data.Time.Format.ISO8601
-import Data.Time.Format.Internal
 import Test.Arbitrary ()
 import Test.QuickCheck.Property
 import Test.Tasty
@@ -33,32 +33,45 @@
                         else failed{reason = show str ++ ": expected " ++ (show expected) ++ ", found " ++ (show found)}
 
 class SpecialTestValues a where
+    testGen :: Gen a
+    default testGen :: Arbitrary a => Gen a
+    testGen = arbitrary
+
     -- | values that should always be tested
     specialTestValues :: [a]
-
-instance {-# OVERLAPPABLE #-} SpecialTestValues a where
     specialTestValues = []
 
+instance {-# OVERLAPPABLE #-} Arbitrary a => SpecialTestValues a
+
 instance SpecialTestValues TimeOfDay where
     specialTestValues = [TimeOfDay 0 0 0, TimeOfDay 0 0 60, TimeOfDay 1 0 60, TimeOfDay 24 0 0]
 
-readShowTestCheck :: (Eq a, Show a, Arbitrary a, SpecialTestValues a) => (a -> Bool) -> Format a -> [TestTree]
-readShowTestCheck skip fmt = [nameTest "random" $ readShowProperty skip fmt, nameTest "special" $ fmap (\a -> nameTest (show a) $ readShowProperty skip fmt a) $ filter (not . skip) specialTestValues]
+instance SpecialTestValues (Integer, Int) where
+    testGen = do
+        Small y <- arbitrary
+        woy <- choose (-10, 120)
+        pure (y, woy)
 
-readShowTest :: (Eq a, Show a, Arbitrary a, SpecialTestValues a) => Format a -> [TestTree]
+readShowTestCheck :: (Eq a, Show a, SpecialTestValues a) => (a -> Bool) -> Format a -> [TestTree]
+readShowTestCheck skip fmt =
+    [ nameTest "random" $ fmap (readShowProperty skip fmt) testGen
+    , nameTest "special" $ fmap (\a -> nameTest (show a) $ readShowProperty skip fmt a) $ filter (not . skip) specialTestValues
+    ]
+
+readShowTest :: (Eq a, Show a, SpecialTestValues a) => Format a -> [TestTree]
 readShowTest = readShowTestCheck $ \_ -> False
 
 readBoth :: NameTest t => (FormatExtension -> t) -> [TestTree]
 readBoth fmts = [nameTest "extended" $ fmts ExtendedFormat, nameTest "basic" $ fmts BasicFormat]
 
-readShowTestsCheck :: (Eq a, Show a, Arbitrary a, SpecialTestValues a) => (a -> Bool) -> (FormatExtension -> Format a) -> [TestTree]
+readShowTestsCheck :: (Eq a, Show a, SpecialTestValues a) => (a -> Bool) -> (FormatExtension -> Format a) -> [TestTree]
 readShowTestsCheck skip fmts = readBoth $ \fe -> readShowTestCheck skip $ fmts fe
 
-readShowTests :: (Eq a, Show a, Arbitrary a, SpecialTestValues a) => (FormatExtension -> Format a) -> [TestTree]
+readShowTests :: (Eq a, Show a, SpecialTestValues a) => (FormatExtension -> Format a) -> [TestTree]
 readShowTests = readShowTestsCheck $ \_ -> False
 
-newtype Durational t = MkDurational {unDurational :: t}
-    deriving (Eq)
+newtype Durational t = MkDurational t
+    deriving Eq
 
 instance Show t => Show (Durational t) where
     show (MkDurational t) = show t
@@ -81,59 +94,60 @@
                 return $ MkDurational $ CalendarDiffTime mm $ fromRational $ ss % picofactor
 
 durationalFormat :: Format a -> Format (Durational a)
-durationalFormat (MkFormat sa ra) = MkFormat (\b -> sa $ unDurational b) (fmap MkDurational ra)
+durationalFormat = coerce
 
 testReadShowFormat :: TestTree
 testReadShowFormat =
-    nameTest
-        "read-show format"
-        [ nameTest "calendarFormat" $ readShowTests $ calendarFormat
-        , nameTest "yearMonthFormat" $ readShowTest $ yearMonthFormat
-        , nameTest "yearFormat" $ readShowTest $ yearFormat
-        , nameTest "centuryFormat" $ readShowTest $ centuryFormat
-        , nameTest "expandedCalendarFormat" $ readShowTests $ expandedCalendarFormat 6
-        , nameTest "expandedYearMonthFormat" $ readShowTest $ expandedYearMonthFormat 6
-        , nameTest "expandedYearFormat" $ readShowTest $ expandedYearFormat 6
-        , nameTest "expandedCenturyFormat" $ readShowTest $ expandedCenturyFormat 4
-        , nameTest "ordinalDateFormat" $ readShowTests $ ordinalDateFormat
-        , nameTest "expandedOrdinalDateFormat" $ readShowTests $ expandedOrdinalDateFormat 6
-        , nameTest "weekDateFormat" $ readShowTests $ weekDateFormat
-        , nameTest "yearWeekFormat" $ readShowTests $ yearWeekFormat
-        , nameTest "expandedWeekDateFormat" $ readShowTests $ expandedWeekDateFormat 6
-        , nameTest "expandedYearWeekFormat" $ readShowTests $ expandedYearWeekFormat 6
-        , nameTest "timeOfDayFormat" $ readShowTests $ timeOfDayFormat
-        , nameTest "hourMinuteFormat" $ readShowTestsCheck (\(TimeOfDay _ _ s) -> s >= 60) $ hourMinuteFormat
-        , nameTest "hourFormat" $ readShowTestCheck (\(TimeOfDay _ _ s) -> s >= 60) $ hourFormat
-        , nameTest "withTimeDesignator" $ readShowTests $ \fe -> withTimeDesignator $ timeOfDayFormat fe
-        , nameTest "withUTCDesignator" $ readShowTests $ \fe -> withUTCDesignator $ timeOfDayFormat fe
-        , nameTest "timeOffsetFormat" $ readShowTests $ timeOffsetFormat
-        , nameTest "timeOfDayAndOffsetFormat" $ readShowTests $ timeOfDayAndOffsetFormat
-        , nameTest "localTimeFormat" $
-            readShowTests $
-                \fe -> localTimeFormat (calendarFormat fe) (timeOfDayFormat fe)
-        , nameTest "zonedTimeFormat" $
-            readShowTests $
-                \fe -> zonedTimeFormat (calendarFormat fe) (timeOfDayFormat fe) fe
-        , nameTest "utcTimeFormat" $ readShowTests $ \fe -> utcTimeFormat (calendarFormat fe) (timeOfDayFormat fe)
-        , nameTest "dayAndTimeFormat" $
-            readShowTests $
-                \fe -> dayAndTimeFormat (calendarFormat fe) (timeOfDayFormat fe)
-        , nameTest "timeAndOffsetFormat" $ readShowTests $ \fe -> timeAndOffsetFormat (timeOfDayFormat fe) fe
-        , nameTest "durationDaysFormat" $ readShowTest $ durationDaysFormat
-        , nameTest "durationTimeFormat" $ readShowTest $ durationTimeFormat
-        , nameTest "alternativeDurationDaysFormat" $
-            readBoth $
-                \fe -> readShowTest (durationalFormat $ alternativeDurationDaysFormat fe)
-        , nameTest "alternativeDurationTimeFormat" $
-            readBoth $
-                \fe -> readShowTest (durationalFormat $ alternativeDurationTimeFormat fe)
-        , nameTest "intervalFormat" $
-            readShowTests $ \fe ->
-                intervalFormat (localTimeFormat (calendarFormat fe) (timeOfDayFormat fe)) durationTimeFormat
-        , nameTest "recurringIntervalFormat" $
-            readShowTests $ \fe ->
-                recurringIntervalFormat (localTimeFormat (calendarFormat fe) (timeOfDayFormat fe)) durationTimeFormat
-        ]
+    localOption (QuickCheckTests 1000) $
+        nameTest
+            "read-show format"
+            [ nameTest "calendarFormat" $ readShowTests $ calendarFormat
+            , nameTest "yearMonthFormat" $ readShowTest $ yearMonthFormat
+            , nameTest "yearFormat" $ readShowTest $ yearFormat
+            , nameTest "centuryFormat" $ readShowTest $ centuryFormat
+            , nameTest "expandedCalendarFormat" $ readShowTests $ expandedCalendarFormat 6
+            , nameTest "expandedYearMonthFormat" $ readShowTest $ expandedYearMonthFormat 6
+            , nameTest "expandedYearFormat" $ readShowTest $ expandedYearFormat 6
+            , nameTest "expandedCenturyFormat" $ readShowTest $ expandedCenturyFormat 4
+            , nameTest "ordinalDateFormat" $ readShowTests $ ordinalDateFormat
+            , nameTest "expandedOrdinalDateFormat" $ readShowTests $ expandedOrdinalDateFormat 6
+            , nameTest "weekDateFormat" $ readShowTests $ weekDateFormat
+            , nameTest "yearWeekFormat" $ readShowTests $ yearWeekFormat
+            , nameTest "expandedWeekDateFormat" $ readShowTests $ expandedWeekDateFormat 6
+            , nameTest "expandedYearWeekFormat" $ readShowTests $ expandedYearWeekFormat 6
+            , nameTest "timeOfDayFormat" $ readShowTests $ timeOfDayFormat
+            , nameTest "hourMinuteFormat" $ readShowTestsCheck (\(TimeOfDay _ _ s) -> s >= 60) $ hourMinuteFormat
+            , nameTest "hourFormat" $ readShowTestCheck (\(TimeOfDay _ _ s) -> s >= 60) $ hourFormat
+            , nameTest "withTimeDesignator" $ readShowTests $ \fe -> withTimeDesignator $ timeOfDayFormat fe
+            , nameTest "withUTCDesignator" $ readShowTests $ \fe -> withUTCDesignator $ timeOfDayFormat fe
+            , nameTest "timeOffsetFormat" $ readShowTests $ timeOffsetFormat
+            , nameTest "timeOfDayAndOffsetFormat" $ readShowTests $ timeOfDayAndOffsetFormat
+            , nameTest "localTimeFormat" $
+                readShowTests $
+                    \fe -> localTimeFormat (calendarFormat fe) (timeOfDayFormat fe)
+            , nameTest "zonedTimeFormat" $
+                readShowTests $
+                    \fe -> zonedTimeFormat (calendarFormat fe) (timeOfDayFormat fe) fe
+            , nameTest "utcTimeFormat" $ readShowTests $ \fe -> utcTimeFormat (calendarFormat fe) (timeOfDayFormat fe)
+            , nameTest "dayAndTimeFormat" $
+                readShowTests $
+                    \fe -> dayAndTimeFormat (calendarFormat fe) (timeOfDayFormat fe)
+            , nameTest "timeAndOffsetFormat" $ readShowTests $ \fe -> timeAndOffsetFormat (timeOfDayFormat fe) fe
+            , nameTest "durationDaysFormat" $ readShowTest $ durationDaysFormat
+            , nameTest "durationTimeFormat" $ readShowTest $ durationTimeFormat
+            , nameTest "alternativeDurationDaysFormat" $
+                readBoth $
+                    \fe -> readShowTest (durationalFormat $ alternativeDurationDaysFormat fe)
+            , nameTest "alternativeDurationTimeFormat" $
+                readBoth $
+                    \fe -> readShowTest (durationalFormat $ alternativeDurationTimeFormat fe)
+            , nameTest "intervalFormat" $
+                readShowTests $ \fe ->
+                    intervalFormat (localTimeFormat (calendarFormat fe) (timeOfDayFormat fe)) durationTimeFormat
+            , nameTest "recurringIntervalFormat" $
+                readShowTests $ \fe ->
+                    recurringIntervalFormat (localTimeFormat (calendarFormat fe) (timeOfDayFormat fe)) durationTimeFormat
+            ]
 
 testShowReadFormat :: (Show t, Eq t) => String -> Format t -> String -> t -> TestTree
 testShowReadFormat name fmt str val =
@@ -146,6 +160,9 @@
 testReadFormat :: (Show t, Eq t) => String -> Format t -> String -> t -> TestTree
 testReadFormat name fmt str val = nameTest (name ++ ": " ++ str) $ assertEqual "" (Just val) $ formatParseM fmt str
 
+testReadFormatFails :: (Show t, Eq t) => String -> Format t -> String -> TestTree
+testReadFormatFails name fmt str = nameTest (name ++ ": " ++ str) $ assertEqual "" Nothing $ formatParseM fmt str
+
 testShowFormats :: TestTree
 testShowFormats =
     nameTest
@@ -226,6 +243,10 @@
             (recurringIntervalFormat (calendarFormat ExtendedFormat) durationDaysFormat)
             "R74/2015-06-13/P1Y2M7D"
             (74, fromGregorian 2015 6 13, CalendarDiffDays 14 7)
+        , testReadFormatFails
+            "recurringIntervalFormat etc."
+            (recurringIntervalFormat (calendarFormat ExtendedFormat) (calendarFormat ExtendedFormat))
+            "R18446744073709551616/2024-01-01/2024-01-02"
         , testShowReadFormat "timeOffsetFormat" iso8601Format "-06:30" (minutesToTimeZone (-390))
         , testShowReadFormat "timeOffsetFormat" iso8601Format "-06:00" (minutesToTimeZone (-360))
         , testReadFormat "timeOffsetFormat" iso8601Format "-06" (minutesToTimeZone (-360))
@@ -281,6 +302,16 @@
             iso8601Format
             "2024-07-06T08:45:56.553+06"
             (ZonedTime (LocalTime (fromGregorian 2024 07 06) (TimeOfDay 8 45 56.553)) (minutesToTimeZone 360))
+        , testReadFormat
+            "zonedTimeFormat"
+            iso8601Format
+            "2024-05-30T16:15:18Z"
+            (ZonedTime (LocalTime (fromGregorian 2024 05 30) (TimeOfDay 16 15 18)) utc)
+        , testReadFormat
+            "zonedTimeFormat"
+            iso8601Format
+            "2024-05-30T16:15:18"
+            (ZonedTime (LocalTime (fromGregorian 2024 05 30) (TimeOfDay 16 15 18)) utc)
         , testShowReadFormat
             "utcTimeFormat"
             iso8601Format
@@ -290,6 +321,11 @@
             "utcTimeFormat"
             iso8601Format
             "2028-12-31T23:59:60.9Z"
+            (UTCTime (fromGregorian 2028 12 31) (timeOfDayToTime $ TimeOfDay 23 59 60.9))
+        , testReadFormat
+            "utcTimeFormat"
+            iso8601Format
+            "2028-12-31T23:59:60.9"
             (UTCTime (fromGregorian 2028 12 31) (timeOfDayToTime $ TimeOfDay 23 59 60.9))
         , testShowReadFormat "weekDateFormat" (weekDateFormat ExtendedFormat) "1994-W52-7" (fromGregorian 1995 1 1)
         , testShowReadFormat "weekDateFormat" (weekDateFormat ExtendedFormat) "1995-W01-1" (fromGregorian 1995 1 2)
diff --git a/test/main/Test/Format/ParseTime.hs b/test/main/Test/Format/ParseTime.hs
--- a/test/main/Test/Format/ParseTime.hs
+++ b/test/main/Test/Format/ParseTime.hs
@@ -271,10 +271,34 @@
         "particular"
         [ parseTest @Day True Nothing "%-d%-m%0Y" "2122012" -- ISSUE #232
         , parseTest @Day True Nothing "%-d%-m%0Y" "2132012" -- ISSUE #232
+        , Test.Tasty.HUnit.testCase "#303 lower-case AM/PM locale parses its own %p output" $
+            assertEqual
+                ""
+                (Just $ TimeOfDay 13 0 0)
+                (parseTimeM False lowerAmPmLocale "%I %p" "01 pm")
+        , Test.Tasty.HUnit.testCase "#303 UTC zone parsing is case-insensitive" $
+            assertEqual
+                ""
+                (Just utc)
+                (parseTimeM False defaultTimeLocale "%Z" "utc")
+        , Test.Tasty.HUnit.testCase "#303 known time zone names are case-insensitive" $
+            assertEqual
+                ""
+                (Just lowerKnownZone)
+                (parseTimeM False lowerKnownZoneLocale "%Z" "foo")
         ]
+  where
+    lowerAmPmLocale = defaultTimeLocale{amPm = ("am", "pm")}
+    lowerKnownZone = TimeZone 90 False "foo"
+    lowerKnownZoneLocale = defaultTimeLocale{knownTimeZones = [lowerKnownZone]}
 
 badParseTests :: TestTree
-badParseTests = testGroup "bad" [parseTest False (Nothing :: Maybe Day) "%Y" ""]
+badParseTests =
+    testGroup
+        "bad"
+        [ parseTest False (Nothing :: Maybe Day) "%Y" ""
+        , parseTest False (Nothing :: Maybe TimeOfDay) "%-H" "18446744073709551616"
+        ]
 
 {-
 parseYMD :: Day -> TestTree
@@ -480,6 +504,18 @@
 instance HasFormatCodes TimeOfDay where
     allFormatCodes _ = [(False, s) | s <- "RTXrPpHkIlMSqQ"]
 
+instance HasFormatCodes DayOfWeek where
+    allFormatCodes _ = [(False, s) | s <- "uwaA"]
+
+instance HasFormatCodes Month where
+    allFormatCodes _ = [(False, s) | s <- "YyCBbhm"]
+
+instance HasFormatCodes QuarterOfYear where
+    allFormatCodes _ = [(False, s) | s <- "v"]
+
+instance HasFormatCodes Quarter where
+    allFormatCodes _ = allFormatCodes (Proxy :: Proxy QuarterOfYear) ++ [(False, s) | s <- "YyC"]
+
 instance HasFormatCodes LocalTime where
     allFormatCodes _ = allFormatCodes (Proxy :: Proxy Day) ++ allFormatCodes (Proxy :: Proxy TimeOfDay)
 
@@ -542,6 +578,7 @@
 typedTests prop =
     [ nameTest "Day" $ tgroup dayFormats prop
     , nameTest "Month" $ tgroup monthFormats prop
+    , nameTest "Quarter" $ tgroup quarterFormats prop
     , nameTest "TimeOfDay" $ tgroup timeOfDayFormats prop
     , nameTest "LocalTime" $ tgroup localTimeFormats prop
     , nameTest "TimeZone" $ tgroup timeZoneFormats prop
@@ -552,7 +589,7 @@
     , nameTest "UTCTime" $ tgroup utcTimeAlmostFormats $ \fmt t -> utctDayTime t < 86400 ==> prop fmt t
     , nameTest "UniversalTime" $ tgroup universalTimeFormats prop
     , nameTest "CalendarDiffDays" $ tgroup calendarDiffDaysFormats prop
-    , nameTest "CalenderDiffTime" $ tgroup calendarDiffTimeFormats prop
+    , nameTest "CalendarDiffTime" $ tgroup calendarDiffTimeFormats prop
     , nameTest "DiffTime" $ tgroup diffTimeFormats prop
     , nameTest "NominalDiffTime" $ tgroup nominalDiffTimeFormats prop
     ]
@@ -563,6 +600,10 @@
 allTypes f =
     [ f "Day" (Proxy :: Proxy Day)
     , f "TimeOfDay" (Proxy :: Proxy TimeOfDay)
+    , f "DayOfWeek" (Proxy :: Proxy DayOfWeek)
+    , f "Month" (Proxy :: Proxy Month)
+    , f "QuarterOfYear" (Proxy :: Proxy QuarterOfYear)
+    , f "Quarter" (Proxy :: Proxy Quarter)
     , f "LocalTime" (Proxy :: Proxy LocalTime)
     , f "TimeZone" (Proxy :: Proxy TimeZone)
     , f "ZonedTime" (Proxy :: Proxy ZonedTime)
@@ -605,6 +646,85 @@
 parseEmptyTests :: TestTree
 parseEmptyTests = nameTest "parse empty" $ allTypes $ \name p -> nameTest name $ parseEmptyTest p
 
+class (Eq a, Show a, ParseTime a) => TestParse a where
+    nonPosixValue :: a -> Bool
+    nonPosixValue _ = False
+
+instance TestParse UTCTime where
+    nonPosixValue (UTCTime _ dt) = dt >= 86400 || dt < 0
+
+instance TestParse ZonedTime where
+    nonPosixValue (ZonedTime t _) = nonPosixValue t
+
+instance TestParse TimeZone
+
+instance TestParse LocalTime where
+    nonPosixValue (LocalTime _ t) = nonPosixValue t
+
+instance TestParse Day
+
+instance TestParse Month
+
+instance TestParse DayOfWeek
+
+instance TestParse TimeOfDay where
+    nonPosixValue (TimeOfDay _ _ s) = s >= 60 || s < 0
+
+prop_parse_s :: forall a. TestParse a => (UTCTime -> a) -> UTCTime -> Result
+prop_parse_s f t =
+    if nonPosixValue t
+        then succeeded
+        else
+            let
+                str = format "%s%Q" t
+                found = parse False "%s%Q" str
+                expected = f t
+            in
+                compareResult (Just expected) found
+
+prop_parse_sz :: forall a. TestParse a => (ZonedTime -> a) -> ZonedTime -> Result
+prop_parse_sz f t =
+    if nonPosixValue t
+        then succeeded
+        else
+            let
+                str = format "%s%Q %z" t
+                found = parse False "%s%Q %z" str
+                expected = f t
+            in
+                compareResult (Just expected) found
+
+zeroTimeZone :: TimeZone
+zeroTimeZone = TimeZone 0 False ""
+
+parse_s_tests :: TestTree
+parse_s_tests =
+    nameTest
+        "parse_s"
+        [ nameTest "UTCTime" $ prop_parse_s @UTCTime id
+        , nameTest "ZonedTime" $ prop_parse_s @ZonedTime $ utcToZonedTime zeroTimeZone
+        , nameTest "TimeZone" $ prop_parse_s @TimeZone $ \_ -> zeroTimeZone
+        , nameTest "LocalTime" $ prop_parse_s @LocalTime $ utcToLocalTime zeroTimeZone
+        , nameTest "Day" $ prop_parse_s @Day utctDay
+        , nameTest "Month" $ prop_parse_s @Month $ (\(MonthDay m _) -> m) . utctDay
+        , nameTest "DayOfWeek" $ prop_parse_s @DayOfWeek $ dayOfWeek . utctDay
+        , nameTest "TimeOfDay" $ prop_parse_s @TimeOfDay $ localTimeOfDay . utcToLocalTime zeroTimeZone
+        ]
+
+parse_sz_tests :: TestTree
+parse_sz_tests =
+    nameTest
+        "parse_sz"
+        [ nameTest "UTCTime" $ prop_parse_sz @UTCTime zonedTimeToUTC
+        , nameTest "ZonedTime" $ prop_parse_sz @ZonedTime id
+        , nameTest "TimeZone" $ prop_parse_sz @TimeZone zonedTimeZone
+        , nameTest "LocalTime" $ prop_parse_sz @LocalTime zonedTimeToLocalTime
+        , nameTest "Day" $ prop_parse_sz @Day $ localDay . zonedTimeToLocalTime
+        , nameTest "Month" $ prop_parse_sz @Month $ (\(MonthDay m _) -> m) . localDay . zonedTimeToLocalTime
+        , nameTest "DayOfWeek" $ prop_parse_sz @DayOfWeek $ dayOfWeek . localDay . zonedTimeToLocalTime
+        , nameTest "TimeOfDay" $ prop_parse_sz @TimeOfDay $ localTimeOfDay . zonedTimeToLocalTime
+        ]
+
 formatParseFormatTests :: TestTree
 formatParseFormatTests =
     nameTest
@@ -680,6 +800,8 @@
             , nameTest "parse_format_lower" $ typedTests prop_parse_format_lower
             , nameTest "parse_format_upper" $ typedTests prop_parse_format_upper
             , parseEmptyTests
+            , parse_s_tests
+            , parse_sz_tests
             , formatParseFormatTests
             , badInputTests
             ]
@@ -752,6 +874,22 @@
         , "%C-%y-%B"
         , "%C-%y-%b"
         , "%C-%y-%h"
+        ]
+
+quarterFormats :: [FormatString Quarter]
+quarterFormats =
+    map
+        FormatString
+        -- numeric year, quarter
+        [ "%Y-%v"
+        , "%Y-Q%v"
+        , "%YQ%v"
+        , "%C%y%v"
+        , "%Y %v"
+        , "%v/%Y"
+        , "%v/%Y"
+        , "%Y/%vm"
+        , "%C %y %v"
         ]
 
 timeOfDayFormats :: [FormatString TimeOfDay]
diff --git a/test/main/Test/LocalTime/CalendarDiffTime.hs b/test/main/Test/LocalTime/CalendarDiffTime.hs
--- a/test/main/Test/LocalTime/CalendarDiffTime.hs
+++ b/test/main/Test/LocalTime/CalendarDiffTime.hs
@@ -2,12 +2,97 @@
     testCalendarDiffTime,
 ) where
 
+import Data.Fixed
 import Data.Time
+import Test.AddDiff
 import Test.Arbitrary ()
 import Test.Tasty
 import Test.Tasty.HUnit
 import Test.TestUtil
 
+utcClip :: AddDiff CalendarDiffTime UTCTime
+utcClip = MkAddDiff "utcClip" addUTCDurationClip diffUTCDurationClip
+
+utcRollOver :: AddDiff CalendarDiffTime UTCTime
+utcRollOver = MkAddDiff "utcRollOver" addUTCDurationRollOver diffUTCDurationRollOver
+
+localClip :: AddDiff CalendarDiffTime LocalTime
+localClip = MkAddDiff "localClip" addLocalDurationClip diffLocalDurationClip
+
+localRollOver :: AddDiff CalendarDiffTime LocalTime
+localRollOver = MkAddDiff "localRollOver" addLocalDurationRollOver diffLocalDurationRollOver
+
+testAddDiffs :: TestTree
+testAddDiffs =
+    testGroup
+        "add-diff"
+        [ testGroup "UTC" $ fmap testAddDiff [utcClip, utcRollOver]
+        , testGroup "LocalTime" $ fmap testAddDiff [localClip, localRollOver]
+        ]
+
+utcTime :: Integer -> Int -> Int -> DiffTime -> UTCTime
+utcTime y m d = UTCTime (YearMonthDay y m d)
+
+localTime :: Integer -> Int -> Int -> Int -> Int -> Pico -> LocalTime
+localTime y m d h minute s = LocalTime (YearMonthDay y m d) $ TimeOfDay h minute s
+
+testSpecifics :: TestTree
+testSpecifics =
+    testGroup
+        "specific"
+        [ testGroup
+            "add"
+            [ testCase "UTC clip" $
+                assertEqual
+                    "Jan 31 + 1 month clips to Feb 28"
+                    (utcTime 2001 2 28 0)
+                    (addUTCDurationClip (CalendarDiffTime 1 0) $ utcTime 2001 1 31 0)
+            , testCase "UTC rollover" $
+                assertEqual
+                    "Jan 31 + 1 month rolls over to Mar 3"
+                    (utcTime 2001 3 3 0)
+                    (addUTCDurationRollOver (CalendarDiffTime 1 0) $ utcTime 2001 1 31 0)
+            , testCase "LocalTime clip" $
+                assertEqual
+                    "Jan 31 + 1 month clips to Feb 28"
+                    (localTime 2001 2 28 0 0 0)
+                    (addLocalDurationClip (CalendarDiffTime 1 0) $ localTime 2001 1 31 0 0 0)
+            , testCase "LocalTime rollover" $
+                assertEqual
+                    "Jan 31 + 1 month rolls over to Mar 3"
+                    (localTime 2001 3 3 0 0 0)
+                    (addLocalDurationRollOver (CalendarDiffTime 1 0) $ localTime 2001 1 31 0 0 0)
+            , testCase "time part" $
+                assertEqual
+                    "the time part can carry into the next day"
+                    (localTime 2001 2 1 0 0 1)
+                    (addLocalDurationClip (CalendarDiffTime 0 2) $ localTime 2001 1 31 23 59 59)
+            ]
+        , testGroup
+            "diff"
+            [ testCase "UTC clip" $
+                assertEqual
+                    "Mar 1 - Jan 30 clips as 1 month and 1 day"
+                    (CalendarDiffTime 1 nominalDay)
+                    (diffUTCDurationClip (utcTime 2001 3 1 0) $ utcTime 2001 1 30 0)
+            , testCase "UTC rollover" $
+                assertEqual
+                    "Mar 1 - Jan 30 rolls over as 30 days"
+                    (CalendarDiffTime 0 $ 30 * nominalDay)
+                    (diffUTCDurationRollOver (utcTime 2001 3 1 0) $ utcTime 2001 1 30 0)
+            , testCase "LocalTime clip" $
+                assertEqual
+                    "Mar 1 - Jan 30 clips as 1 month and 1 day"
+                    (CalendarDiffTime 1 nominalDay)
+                    (diffLocalDurationClip (localTime 2001 3 1 0 0 0) $ localTime 2001 1 30 0 0 0)
+            , testCase "LocalTime rollover" $
+                assertEqual
+                    "Mar 1 - Jan 30 rolls over as 30 days"
+                    (CalendarDiffTime 0 $ 30 * nominalDay)
+                    (diffLocalDurationRollOver (localTime 2001 3 1 0 0 0) $ localTime 2001 1 30 0 0 0)
+            ]
+        ]
+
 testReadShowExact :: (Read a, Show a, Eq a) => String -> a -> TestTree
 testReadShowExact t v =
     nameTest
@@ -31,4 +116,6 @@
         , testReadShowExact "P-1Y-1M-1DT1S" $ CalendarDiffTime (-13) $ secondsToNominalDiffTime $ negate 86399
         , testReadShowExact "P-1Y-1M-1D" $ CalendarDiffTime (-13) $ secondsToNominalDiffTime $ negate 86400
         , testReadShowExact "P-1Y-1M-2DT23H59M59S" $ CalendarDiffTime (-13) $ secondsToNominalDiffTime $ negate 86401
+        , testAddDiffs
+        , testSpecifics
         ]
diff --git a/test/main/Test/TestUtil.hs b/test/main/Test/TestUtil.hs
--- a/test/main/Test/TestUtil.hs
+++ b/test/main/Test/TestUtil.hs
@@ -32,5 +32,8 @@
 instance (Arbitrary a, Show a, Testable b) => NameTest (a -> b) where
     nameTest name = nameTest name . property
 
+instance Testable a => NameTest (Gen a) where
+    nameTest name = nameTest name . property
+
 tgroup :: (Show a, NameTest t) => [a] -> (a -> t) -> [TestTree]
 tgroup aa f = fmap (\a -> nameTest (show a) $ f a) aa
diff --git a/test/template/Main.hs b/test/template/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/template/Main.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+import Data.Time.Clock
+#if __GLASGOW_HASKELL__ >= 914
+import qualified Language.Haskell.TH.Lift as TH
+#else
+import qualified Language.Haskell.TH.Syntax as TH
+#endif
+import Test.Tasty
+import Test.Tasty.HUnit
+import Test.TastyWrapper
+
+testLift :: TestTree
+testLift =
+    testGroup
+        "Lift instances"
+        [ testCase "DiffTime" $ $$(TH.liftTyped (secondsToDiffTime 100)) @?= secondsToDiffTime 100
+        , testCase "NominalDiffTime" $ $$(TH.liftTyped (secondsToNominalDiffTime 100)) @?= secondsToNominalDiffTime 100
+        ]
+
+tests :: TestTree
+tests =
+    testGroup
+        "time-template"
+        [ testLift
+        ]
+
+main :: IO ()
+main = tastyWrapper $ defaultMain tests
diff --git a/test/template/Test/TastyWrapper.hs b/test/template/Test/TastyWrapper.hs
new file mode 100644
--- /dev/null
+++ b/test/template/Test/TastyWrapper.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE CPP #-}
+
+module Test.TastyWrapper where
+
+#if defined(javascript_HOST_ARCH)
+import GHC.IO.Handle (hDuplicateTo)
+import System.IO (IOMode (ReadMode), stdin, withFile)
+#endif
+
+tastyWrapper :: IO a -> IO a
+#if defined(javascript_HOST_ARCH)
+-- Tasty's console reporter queries terminal width through ansi-terminal, which
+-- trips h$fdReady in the JS RTS when this test runs attached to a TTY.
+tastyWrapper action =
+    withFile "/dev/null" ReadMode $ \devNull -> do
+        hDuplicateTo devNull stdin
+        action
+#else
+tastyWrapper = id
+#endif
diff --git a/test/unix/Test/Format/Format.hs b/test/unix/Test/Format/Format.hs
--- a/test/unix/Test/Format/Format.hs
+++ b/test/unix/Test/Format/Format.hs
@@ -150,9 +150,14 @@
     isPadChar _ = False
 unixWorkarounds _ s = s
 
+lastM :: [a] -> Maybe a
+lastM [] = Nothing
+lastM [a] = Just a
+lastM (_ : aa) = lastM aa
+
 compareFormat :: (String -> String) -> String -> TimeZone -> UTCTime -> Result
 compareFormat _modUnix fmt zone _time
-    | last fmt == 'Z' && timeZoneName zone == "" = rejected
+    | lastM fmt == Just 'Z' && timeZoneName zone == "" = rejected
 compareFormat modUnix fmt zone time =
     let
         ctime = utcToZonedTime zone time
diff --git a/test/unix/Test/LocalTime/HsTime.h b/test/unix/Test/LocalTime/HsTime.h
new file mode 100644
--- /dev/null
+++ b/test/unix/Test/LocalTime/HsTime.h
@@ -0,0 +1,14 @@
+#ifndef TEST_UNIX_LOCALTIME_HSTIME_H
+#define TEST_UNIX_LOCALTIME_HSTIME_H
+
+#include <time.h>
+
+#define HAVE_LOCALTIME_R 1
+#define HAVE_TZSET 1
+#define HAVE_TM_ZONE 0
+#define HAVE_TZNAME 1
+#define HAVE_DECL_ALTZONE 0
+
+long int get_current_timezone_seconds(time_t, int *pdst, char const **pname);
+
+#endif
diff --git a/test/unix/Test/LocalTime/TimeZone.hs b/test/unix/Test/LocalTime/TimeZone.hs
--- a/test/unix/Test/LocalTime/TimeZone.hs
+++ b/test/unix/Test/LocalTime/TimeZone.hs
@@ -1,19 +1,103 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+
 module Test.LocalTime.TimeZone (
     testTimeZone,
 ) where
 
 import Data.Time
+import Foreign
+import Foreign.C
 import System.Environment (setEnv)
 import Test.Tasty
 import Test.Tasty.HUnit
 
+type GetCurrentTimezoneSeconds =
+    CTime -> Ptr CInt -> Ptr CString -> IO CLong
+
+foreign import ccall unsafe "test_time_zone_fallback_set_state"
+    test_time_zone_fallback_set_state :: CLong -> CLong -> CInt -> IO ()
+
+foreign import ccall unsafe "test_get_current_timezone_seconds_no_altzone"
+    test_get_current_timezone_seconds_no_altzone :: GetCurrentTimezoneSeconds
+
+foreign import ccall unsafe "test_get_current_timezone_seconds_altzone"
+    test_get_current_timezone_seconds_altzone :: GetCurrentTimezoneSeconds
+
 testTimeZone :: TestTree
 testTimeZone =
-    testCase "getTimeZone respects TZ env var" $ do
-        let
-            epoch = UTCTime (ModifiedJulianDay 57000) 0
-        setEnv "TZ" "UTC+0"
-        zone1 <- getTimeZone epoch
-        setEnv "TZ" "EST+5"
-        zone2 <- getTimeZone epoch
-        assertBool "zone not changed" $ zone1 /= zone2
+    testGroup
+        "TimeZone"
+        [ testCase "getTimeZone respects TZ env var" $ do
+            let
+                epoch = UTCTime (ModifiedJulianDay 57000) 0
+            setEnv "TZ" "UTC+0"
+            zone1 <- getTimeZone epoch
+            setEnv "TZ" "EST+5"
+            zone2 <- getTimeZone epoch
+            assertBool "zone not changed" $ zone1 /= zone2
+        , dependentTestGroup
+            "get_current_timezone_seconds fallback"
+            AllFinish
+            [ testFallback
+                "without altzone, standard time"
+                test_get_current_timezone_seconds_no_altzone
+                18000
+                14400
+                0
+                (-18000)
+                "EST"
+            , testFallback
+                "without altzone, daylight time"
+                test_get_current_timezone_seconds_no_altzone
+                18000
+                14400
+                1
+                (-14400)
+                "EDT"
+            , testFallback
+                "with altzone, standard time"
+                test_get_current_timezone_seconds_altzone
+                18000
+                14400
+                0
+                (-18000)
+                "EST"
+            , testFallback
+                "with altzone, daylight time"
+                test_get_current_timezone_seconds_altzone
+                18000
+                14400
+                1
+                (-14400)
+                "EDT"
+            ]
+        ]
+
+testFallback ::
+    String ->
+    GetCurrentTimezoneSeconds ->
+    CLong ->
+    CLong ->
+    CInt ->
+    CLong ->
+    String ->
+    TestTree
+testFallback
+    name
+    getTimezoneSeconds
+    timezoneSeconds
+    altzoneSeconds
+    isDst
+    expectedSeconds
+    expectedName =
+        testCase name $
+            with 0 $ \pdst ->
+                with nullPtr $ \pcname -> do
+                    test_time_zone_fallback_set_state timezoneSeconds altzoneSeconds isDst
+                    seconds <- getTimezoneSeconds 0 pdst pcname
+                    dst <- peek pdst
+                    cname <- peek pcname
+                    name' <- peekCString cname
+                    assertEqual "seconds" expectedSeconds seconds
+                    assertEqual "dst" isDst dst
+                    assertEqual "name" expectedName name'
diff --git a/test/unix/Test/LocalTime/TimeZoneFallback.c b/test/unix/Test/LocalTime/TimeZoneFallback.c
new file mode 100644
--- /dev/null
+++ b/test/unix/Test/LocalTime/TimeZoneFallback.c
@@ -0,0 +1,45 @@
+#include <time.h>
+
+static int test_time_zone_fallback_isdst = 0;
+static long int test_time_zone_fallback_timezone = 0;
+static long int test_time_zone_fallback_altzone = 0;
+static char test_time_zone_fallback_std_name[] = "EST";
+static char test_time_zone_fallback_dst_name[] = "EDT";
+static char *test_time_zone_fallback_tzname[] = {
+    test_time_zone_fallback_std_name,
+    test_time_zone_fallback_dst_name
+};
+
+void test_time_zone_fallback_set_state(long int timezone_seconds, long int altzone_seconds, int isdst)
+{
+    test_time_zone_fallback_timezone = timezone_seconds;
+    test_time_zone_fallback_altzone = altzone_seconds;
+    test_time_zone_fallback_isdst = isdst;
+}
+
+void test_time_zone_fallback_tzset(void)
+{
+}
+
+struct tm *test_time_zone_fallback_localtime_r(const time_t *timer, struct tm *result)
+{
+    (void) timer;
+    result->tm_isdst = test_time_zone_fallback_isdst;
+    return result;
+}
+
+#define tzset test_time_zone_fallback_tzset
+#define localtime_r test_time_zone_fallback_localtime_r
+#define timezone test_time_zone_fallback_timezone
+#define altzone test_time_zone_fallback_altzone
+#define tzname test_time_zone_fallback_tzname
+
+#define get_current_timezone_seconds test_get_current_timezone_seconds_no_altzone
+#include "../../../../lib/cbits/HsTime.c"
+#undef get_current_timezone_seconds
+
+#undef HAVE_DECL_ALTZONE
+#define HAVE_DECL_ALTZONE 1
+#define get_current_timezone_seconds test_get_current_timezone_seconds_altzone
+#include "../../../../lib/cbits/HsTime.c"
+#undef get_current_timezone_seconds
diff --git a/time.cabal b/time.cabal
--- a/time.cabal
+++ b/time.cabal
@@ -1,6 +1,6 @@
 cabal-version:  3.0
 name:           time
-version:        1.14
+version:        1.16.0.1
 stability:      stable
 license:        BSD-2-Clause
 license-file:   LICENSE
@@ -13,9 +13,10 @@
 category:       Time
 build-type:     Configure
 tested-with:
-    GHC == 9.4.8,
-    GHC == 9.6.4,
-    GHC == 9.8.2
+    GHC == 9.8.4,
+    GHC == 9.10.3,
+    GHC == 9.12.4,
+    GHC == 9.14.1
 x-follows-version-policy:
 
 extra-source-files:
@@ -24,8 +25,11 @@
     configure
     lib/include/HsTime.h
     lib/include/HsTimeConfig.h.in
+    test/main/Test/Calendar/EasterData/*.csv
+    test/main/Test/Calendar/EasterData/*.txt
     test/unix/Test/Format/*.c
     test/unix/Test/Format/*.h
+    test/unix/Test/LocalTime/*.h
 extra-tmp-files:
     config.log
     config.status
@@ -40,21 +44,27 @@
 
 library
     hs-source-dirs: lib
-    default-language: Haskell2010
+    default-language: GHC2021
     default-extensions:
-        Rank2Types
-        DeriveDataTypeable
-        DeriveLift
-        DeriveGeneric
-        StandaloneDeriving
+        NoGeneralizedNewtypeDeriving
+        LambdaCase
         PatternSynonyms
         ViewPatterns
-    ghc-options: -Wall -fwarn-tabs
+    ghc-options:
+        -Wall
+        -fwarn-tabs
+        -Wno-deriving-typeable
     c-sources: lib/cbits/HsTime.c
     build-depends:
-        base >= 4.14 && < 5,
+        base >= 4.19 && < 5,
         deepseq >= 1.1,
-        template-haskell >= 2.16,
+    -- template-haskell-lift was added as a boot library in GHC-9.14
+    -- once we no longer wish to backport releases to older major releases,
+    -- this conditional can be dropped
+    if impl(ghc < 9.14)
+        build-depends: template-haskell
+    elif impl(ghc)
+        build-depends: template-haskell-lift >= 0.1 && <0.2
     if os(windows)
         build-depends: Win32
     exposed-modules:
@@ -72,7 +82,6 @@
         Data.Time.Clock.TAI,
         Data.Time.LocalTime,
         Data.Time.Format,
-        Data.Time.Format.Internal,
         Data.Time.Format.ISO8601,
         Data.Time
     other-modules:
@@ -84,6 +93,7 @@
         Data.Time.Calendar.Private,
         Data.Time.Calendar.Types,
         Data.Time.Calendar.Week,
+        Data.Time.Format.Internal,
         Data.Time.Clock.Internal.DiffTime,
         Data.Time.Clock.Internal.AbsoluteTime,
         Data.Time.Clock.Internal.NominalDiffTime,
@@ -92,8 +102,8 @@
         Data.Time.Clock.Internal.SystemTime,
         Data.Time.Clock.Internal.UTCTime,
         Data.Time.Clock.Internal.CTimeval,
-        Data.Time.Clock.Internal.CTimespec,
         Data.Time.Clock.Internal.UTCDiff,
+        Data.Time.LocalTime.Internal.Foreign,
         Data.Time.LocalTime.Internal.TimeZone,
         Data.Time.LocalTime.Internal.TimeOfDay,
         Data.Time.LocalTime.Internal.CalendarDiffTime,
@@ -105,6 +115,9 @@
         Data.Time.Format.Format.Instances,
         Data.Time.Format.Parse.Class,
         Data.Time.Format.Parse.Instances
+    if !impl(mhs)
+        other-modules:
+            Data.Time.Clock.Internal.CTimespec
     include-dirs: lib/include
     if os(windows)
         install-includes:
@@ -119,7 +132,7 @@
 test-suite ShowDefaultTZAbbreviations
     type: exitcode-stdio-1.0
     hs-source-dirs: test
-    default-language: Haskell2010
+    default-language: GHC2021
     ghc-options: -Wall -fwarn-tabs
     build-depends:
         base,
@@ -129,32 +142,39 @@
 test-suite ShowTime
     type: exitcode-stdio-1.0
     hs-source-dirs: test
-    default-language: Haskell2010
+    default-language: GHC2021
     ghc-options: -Wall -fwarn-tabs
     build-depends:
         base,
         time
     main-is: ShowTime.hs
 
+test-suite ForeignCalls
+    type: exitcode-stdio-1.0
+    hs-source-dirs: test
+    default-language: GHC2021
+    ghc-options: -Wall -fwarn-tabs
+    build-depends:
+        base,
+        time
+    main-is: ForeignCalls.hs
+
 test-suite test-main
+    if arch (javascript)
+        -- blocked by splitmix
+        -- https://github.com/haskellari/splitmix/issues/93
+        buildable: False
     type: exitcode-stdio-1.0
     hs-source-dirs: test/main
-    default-language: Haskell2010
+    default-language: GHC2021
     default-extensions:
-        Rank2Types
-        TypeApplications
-        GeneralizedNewtypeDeriving
-        DeriveDataTypeable
-        StandaloneDeriving
+        DefaultSignatures
         DerivingStrategies
-        ExistentialQuantification
-        MultiParamTypeClasses
-        FlexibleInstances
-        UndecidableInstances
-        ScopedTypeVariables
-        TupleSections
         RecordWildCards
+        UndecidableInstances
     ghc-options: -Wall -fwarn-tabs
+    if !arch (wasm32)
+        ghc-options: -threaded -rtsopts -with-rtsopts=-N
     build-depends:
         base,
         deepseq,
@@ -164,9 +184,13 @@
         tasty,
         tasty-hunit,
         tasty-quickcheck,
-        template-haskell
+    if impl(ghc < 9.14)
+        build-depends: template-haskell
+    elif impl(ghc)
+        build-depends: template-haskell-lift >= 0.1 && <0.2
     main-is: Main.hs
     other-modules:
+        Test.AddDiff
         Test.Types
         Test.TestUtil
         Test.Arbitrary
@@ -180,7 +204,6 @@
         Test.Calendar.ConvertBack
         Test.Calendar.Duration
         Test.Calendar.Easter
-        Test.Calendar.EasterRef
         Test.Calendar.DayPeriod
         Test.Calendar.LongWeekYears
         Test.Calendar.LongWeekYearsRef
@@ -191,7 +214,7 @@
         Test.Calendar.Week
         Test.Calendar.Year
         Test.Clock.Conversion
-        Test.Clock.Lift
+        Test.Clock.Pattern
         Test.Clock.Resolution
         Test.Clock.TAI
         Test.Format.Compile
@@ -203,23 +226,50 @@
         Test.LocalTime.TimeOfDay
         Test.LocalTime.TimeRef
 
+test-suite test-template
+    if arch (wasm32)
+        buildable: False
+    type: exitcode-stdio-1.0
+    hs-source-dirs: test/template
+    default-language: GHC2021
+    default-extensions:
+        DerivingStrategies
+        UndecidableInstances
+        RecordWildCards
+        TemplateHaskell
+    ghc-options: -Wall -fwarn-tabs
+    build-depends:
+        base,
+        deepseq,
+        time,
+        random,
+        QuickCheck,
+        tasty,
+        tasty-hunit,
+        tasty-quickcheck,
+    if impl(ghc < 9.14)
+        build-depends: template-haskell
+    elif impl(ghc)
+        build-depends: template-haskell-lift >= 0.1 && <0.2
+    main-is: Main.hs
+    other-modules:
+        Test.TastyWrapper
+
 test-suite test-unix
-    if os(windows)
+    if os(windows) || arch (javascript) || arch (wasm32)
         buildable: False
     type: exitcode-stdio-1.0
     hs-source-dirs: test/unix
-    default-language: Haskell2010
+    default-language: GHC2021
     default-extensions:
-        Rank2Types
-        DeriveDataTypeable
-        StandaloneDeriving
-        ExistentialQuantification
-        MultiParamTypeClasses
-        FlexibleInstances
         UndecidableInstances
-        ScopedTypeVariables
     ghc-options: -Wall -fwarn-tabs
-    c-sources: test/unix/Test/Format/FormatStuff.c
+    if !arch (wasm32)
+        ghc-options: -threaded -rtsopts -with-rtsopts=-N
+    include-dirs: test/unix/Test/LocalTime
+    c-sources:
+        test/unix/Test/Format/FormatStuff.c
+        test/unix/Test/LocalTime/TimeZoneFallback.c
     build-depends:
         base,
         deepseq,
@@ -238,11 +288,15 @@
 benchmark time-bench
     type: exitcode-stdio-1.0
     hs-source-dirs: benchmark
-    default-language: Haskell2010
+    default-language: GHC2021
     ghc-options: -Wall -fwarn-tabs
     build-depends:
         base,
         deepseq,
         time,
         criterion
+    if impl(ghc < 9.14)
+        build-depends: template-haskell
+    elif impl(ghc)
+        build-depends: template-haskell-lift >= 0.1 && <0.2
     main-is: Main.hs
