diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,33 @@
 # Change Log
 
+## [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.
 #
 # 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'
+PACKAGE_STRING='Haskell time package 1.16'
 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 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:";;
    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
 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, 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, 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
 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],[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,
@@ -177,6 +178,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, "")
 
@@ -222,36 +230,44 @@
                     return $ '.' : dd
     return $ sign $ read $ digits ++ moredigits
 
-zeroPad :: Maybe Int -> String -> String
-zeroPad Nothing s = s
-zeroPad (Just i) s = replicate (i - length s) '0' ++ s
+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)
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/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
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,
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/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)
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,24 +36,98 @@
 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 _ "UTC" = Just utc
+readSpec_Z _ [c] | Just zone <- getMilZone c = Just zone
+readSpec_Z _ _ = Nothing
+
+makeDayFact :: TimeLocale -> Char -> String -> Maybe [DayFact]
+makeDayFact l c x =
     let
         ra :: Read a => Maybe a
         ra = readMaybe x
@@ -65,177 +142,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 +359,150 @@
     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 :: Read a => Maybe a
+        ra = readMaybe x
+        getAmPm =
+            let
+                upx = map toUpper x
+                (amStr, pmStr) = amPm l
+            in
+                if upx == amStr
+                    then Just [AMAPMTimeFact AM]
+                    else
+                        if upx == 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
@@ -359,17 +549,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 +599,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,119 @@
+{-# 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) => { return new Date(dy,dm,dd,th,tm,ts).getTimezoneOffset(); })"
+  js_get_timezone_minutes :: Int -> Int -> Int -> Int -> Int -> Int -> IO Int
+
+get_timezone_minutes :: UTCTime -> IO Int
+get_timezone_minutes ut = let
+    lt :: LocalTime
+    lt = utcToLocalTime utc ut
+    in case lt of
+        LocalTime (YearMonthDay dy dm dd) (TimeOfDay th tm ts) ->
+            js_get_timezone_minutes (fromInteger dy) (pred dm) dd th tm (floor ts)
+
+getTimeZoneCTime :: CTime -> IO TimeZone
+getTimeZoneCTime ct = do
+    let
+        ut :: UTCTime
+        ut = posixSecondsToUTCTime $ secondsToNominalDiffTime $ fromIntegral $ fromCTime ct
+    mins <- get_timezone_minutes ut
+    return $ TimeZone mins False ""
+
+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,7 +35,7 @@
         *pname = dst ? _tzname[1] : _tzname[0];
         return - (dst ? _timezone - 3600 : _timezone);
 #else
-# if HAVE_TZNAME
+# if HAVE_TZNAME || defined(__MHS__)
         *pname = *tzname;
 # else
 #  error "Don't know how to get timezone name on your OS"
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,50 @@
+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
+import System.IO
+
+data Test = MkTest String (IO ())
+
+tests :: [Test]
+tests =
+    [ 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
+    ]
+
+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,11 @@
         "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) =
+testSpecific :: CalendarAddDiff -> (Integer, Int, Int) -> (Integer, Int, Int) -> (Integer, Integer) -> TestTree
+testSpecific (MkAddDiff{..}, fromYMD) (y2, m2, d2) (y1, m1, d1) (em, ed) =
     let
-        day1 = adFromYMD y1 m1 d1
-        day2 = adFromYMD y2 m2 d2
+        day1 = fromYMD y1 m1 d1
+        day2 = fromYMD y2 m2 d2
         expected = CalendarDiffDays em ed
         found = adDifference day2 day1
     in
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/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 =
@@ -281,6 +295,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 +314,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
@@ -480,6 +480,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 +554,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 +565,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 +576,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 +622,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 +776,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 +850,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/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
 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:
@@ -40,21 +41,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 +79,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 +90,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 +99,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 +112,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 +129,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 +139,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 +181,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
@@ -191,7 +212,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,22 +224,46 @@
         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
+    if !arch (wasm32)
+        ghc-options: -threaded -rtsopts -with-rtsopts=-N
     c-sources: test/unix/Test/Format/FormatStuff.c
     build-depends:
         base,
@@ -238,11 +283,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
