{-# LANGUAGE CPP #-}

-- | A @TimeZoneSeries@ describes a timezone by specifying the various
-- clock settings that occurred in the past and are scheduled to occur
-- in the future for the timezone.

module Data.Time.LocalTime.TimeZone.Series
(
  -- * Representing a timezone
  -- $abouttzs
  TimeZoneSeries(..),
  timeZoneFromSeries,
  isValidLocalTime,
  isRedundantLocalTime,
  latestNonSummer,

  -- ** Converting between UTC and local time
  -- $aboutfuncs
  utcToLocalTime',
  localTimeToUTC',

  -- * Representing a moment in a timezone
  ZoneSeriesTime(..),
  zonedTimeToZoneSeriesTime,
  zoneSeriesTimeToLocalTime,
  zoneSeriesTimeZone,
  localTimeToZoneSeriesTime
)
where

import Data.Time (UTCTime, LocalTime, TimeZone(timeZoneSummerOnly),
                  ZonedTime(ZonedTime),
                  utcToLocalTime, localTimeToUTC)
#if MIN_VERSION_time(1,9,1)
import Data.Time.Format.Internal
  ( FormatTime(formatCharacter)
  , ParseTime( buildTime
             , parseTimeSpecifier
             , substituteTimeSpecifier
             )
  )
#else
import Data.Time (FormatTime(formatCharacter), ParseTime(buildTime))
#endif
import Data.List (partition)
import Data.Maybe (listToMaybe, fromMaybe)
import Data.Proxy (Proxy(Proxy))
import Data.Typeable (Typeable)
import Control.Arrow (first)
import Control.DeepSeq (NFData(..))

-- $abouttzs
-- A @TimeZoneSeries@ describes a timezone with a set of 'TimeZone'
-- objects. Each @TimeZone@ object describes the clock setting in the
-- timezone for a specific period of history during which the clocks
-- do not change.
--
-- Most operating systems provide information about timezone series
-- for the local timezone and for many other timezones of the world.
-- On MS Windows systems, this information can be read from the
-- registry. On other systems, this information is typically provided
-- in the form of Olson timezone files: \/etc\/localtime (or some
-- other file) for the local timezone, and files located in
-- \/usr\/share\/zoneinfo\/ or \/etc\/zoneinfo\/ (or some other
-- directory) for other timezones.

-- | A @TimeZoneSeries@ consists of a default @TimeZone@ object and a
-- sequence of pairs of a @UTCTime@ and a @TimeZone@ object. Each
-- @UTCTime@ indicates a moment at which the clocks changed, and the
-- corresponding @TimeZone@ object describes the new state of the
-- clocks after the change. The default @TimeZone@ object is used for
-- times preceding the earliest @UTCTime@, or if the sequence of pairs
-- is empty. The times in the sequence are in order from latest to
-- earlist (note that this is the opposite of the way that they are
-- stored in an Olson timezone file).
data TimeZoneSeries =
       TimeZoneSeries {
         TimeZoneSeries -> TimeZone
tzsTimeZone ::    TimeZone,
                             -- ^ The default timezone state
         TimeZoneSeries -> [(UTCTime, TimeZone)]
tzsTransitions :: [(UTCTime, TimeZone)]
                             -- ^ A list of pairs of the time of a
                             -- change of clocks and the new timezone
                             -- state after the change
    }
  deriving (TimeZoneSeries -> TimeZoneSeries -> Bool
(TimeZoneSeries -> TimeZoneSeries -> Bool)
-> (TimeZoneSeries -> TimeZoneSeries -> Bool) -> Eq TimeZoneSeries
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TimeZoneSeries -> TimeZoneSeries -> Bool
$c/= :: TimeZoneSeries -> TimeZoneSeries -> Bool
== :: TimeZoneSeries -> TimeZoneSeries -> Bool
$c== :: TimeZoneSeries -> TimeZoneSeries -> Bool
Eq, Eq TimeZoneSeries
Eq TimeZoneSeries
-> (TimeZoneSeries -> TimeZoneSeries -> Ordering)
-> (TimeZoneSeries -> TimeZoneSeries -> Bool)
-> (TimeZoneSeries -> TimeZoneSeries -> Bool)
-> (TimeZoneSeries -> TimeZoneSeries -> Bool)
-> (TimeZoneSeries -> TimeZoneSeries -> Bool)
-> (TimeZoneSeries -> TimeZoneSeries -> TimeZoneSeries)
-> (TimeZoneSeries -> TimeZoneSeries -> TimeZoneSeries)
-> Ord TimeZoneSeries
TimeZoneSeries -> TimeZoneSeries -> Bool
TimeZoneSeries -> TimeZoneSeries -> Ordering
TimeZoneSeries -> TimeZoneSeries -> TimeZoneSeries
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: TimeZoneSeries -> TimeZoneSeries -> TimeZoneSeries
$cmin :: TimeZoneSeries -> TimeZoneSeries -> TimeZoneSeries
max :: TimeZoneSeries -> TimeZoneSeries -> TimeZoneSeries
$cmax :: TimeZoneSeries -> TimeZoneSeries -> TimeZoneSeries
>= :: TimeZoneSeries -> TimeZoneSeries -> Bool
$c>= :: TimeZoneSeries -> TimeZoneSeries -> Bool
> :: TimeZoneSeries -> TimeZoneSeries -> Bool
$c> :: TimeZoneSeries -> TimeZoneSeries -> Bool
<= :: TimeZoneSeries -> TimeZoneSeries -> Bool
$c<= :: TimeZoneSeries -> TimeZoneSeries -> Bool
< :: TimeZoneSeries -> TimeZoneSeries -> Bool
$c< :: TimeZoneSeries -> TimeZoneSeries -> Bool
compare :: TimeZoneSeries -> TimeZoneSeries -> Ordering
$ccompare :: TimeZoneSeries -> TimeZoneSeries -> Ordering
$cp1Ord :: Eq TimeZoneSeries
Ord, Typeable)

instance Show TimeZoneSeries where
  show :: TimeZoneSeries -> String
show = TimeZone -> String
forall a. Show a => a -> String
show (TimeZone -> String)
-> (TimeZoneSeries -> TimeZone) -> TimeZoneSeries -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TimeZoneSeries -> TimeZone
latestNonSummer

instance Read TimeZoneSeries where
    readsPrec :: Int -> ReadS TimeZoneSeries
readsPrec Int
n = ((TimeZone, String) -> (TimeZoneSeries, String))
-> [(TimeZone, String)] -> [(TimeZoneSeries, String)]
forall a b. (a -> b) -> [a] -> [b]
map ((TimeZone -> TimeZoneSeries)
-> (TimeZone, String) -> (TimeZoneSeries, String)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
first ((TimeZone -> TimeZoneSeries)
 -> (TimeZone, String) -> (TimeZoneSeries, String))
-> (TimeZone -> TimeZoneSeries)
-> (TimeZone, String)
-> (TimeZoneSeries, String)
forall a b. (a -> b) -> a -> b
$ (TimeZone -> [(UTCTime, TimeZone)] -> TimeZoneSeries)
-> [(UTCTime, TimeZone)] -> TimeZone -> TimeZoneSeries
forall a b c. (a -> b -> c) -> b -> a -> c
flip TimeZone -> [(UTCTime, TimeZone)] -> TimeZoneSeries
TimeZoneSeries []) ([(TimeZone, String)] -> [(TimeZoneSeries, String)])
-> (String -> [(TimeZone, String)]) -> ReadS TimeZoneSeries
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> String -> [(TimeZone, String)]
forall a. Read a => Int -> ReadS a
readsPrec Int
n

instance NFData TimeZoneSeries where
  rnf :: TimeZoneSeries -> ()
rnf TimeZoneSeries
tzs = (TimeZone, [(UTCTime, TimeZone)]) -> ()
forall a. NFData a => a -> ()
rnf (TimeZoneSeries -> TimeZone
tzsTimeZone TimeZoneSeries
tzs, TimeZoneSeries -> [(UTCTime, TimeZone)]
tzsTransitions TimeZoneSeries
tzs)

instance ParseTime TimeZoneSeries where
  buildTime :: TimeLocale -> [(Char, String)] -> Maybe TimeZoneSeries
buildTime TimeLocale
locale = (TimeZone -> TimeZoneSeries)
-> Maybe TimeZone -> Maybe TimeZoneSeries
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
mapBuiltTime ((TimeZone -> [(UTCTime, TimeZone)] -> TimeZoneSeries)
-> [(UTCTime, TimeZone)] -> TimeZone -> TimeZoneSeries
forall a b c. (a -> b -> c) -> b -> a -> c
flip TimeZone -> [(UTCTime, TimeZone)] -> TimeZoneSeries
TimeZoneSeries []) (Maybe TimeZone -> Maybe TimeZoneSeries)
-> ([(Char, String)] -> Maybe TimeZone)
-> [(Char, String)]
-> Maybe TimeZoneSeries
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TimeLocale -> [(Char, String)] -> Maybe TimeZone
forall t. ParseTime t => TimeLocale -> [(Char, String)] -> Maybe t
buildTime TimeLocale
locale
#if MIN_VERSION_time(1,9,1)
  parseTimeSpecifier :: proxy TimeZoneSeries
-> TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
parseTimeSpecifier proxy TimeZoneSeries
_ = Proxy TimeZone
-> TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
forall t (proxy :: * -> *).
ParseTime t =>
proxy t
-> TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
parseTimeSpecifier (Proxy TimeZone
forall k (t :: k). Proxy t
Proxy :: Proxy TimeZone)
  substituteTimeSpecifier :: proxy TimeZoneSeries -> TimeLocale -> Char -> Maybe String
substituteTimeSpecifier proxy TimeZoneSeries
_ =
    Proxy TimeZone -> TimeLocale -> Char -> Maybe String
forall t (proxy :: * -> *).
ParseTime t =>
proxy t -> TimeLocale -> Char -> Maybe String
substituteTimeSpecifier (Proxy TimeZone
forall k (t :: k). Proxy t
Proxy :: Proxy TimeZone)
#endif

#if MIN_VERSION_time(1,6,0)
mapBuiltTime :: Functor f => (a -> b) -> f a -> f b
mapBuiltTime :: (a -> b) -> f a -> f b
mapBuiltTime = (a -> b) -> f a -> f b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap
#else
mapBuiltTime :: a -> a
mapBuiltTime = id
#endif

-- | The latest non-summer @TimeZone@ in a @TimeZoneSeries@ is in some
-- sense representative of the timezone.
latestNonSummer :: TimeZoneSeries -> TimeZone
latestNonSummer :: TimeZoneSeries -> TimeZone
latestNonSummer (TimeZoneSeries TimeZone
d [(UTCTime, TimeZone)]
cs) = TimeZone -> Maybe TimeZone -> TimeZone
forall a. a -> Maybe a -> a
fromMaybe TimeZone
d (Maybe TimeZone -> TimeZone)
-> ([TimeZone] -> Maybe TimeZone) -> [TimeZone] -> TimeZone
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TimeZone] -> Maybe TimeZone
forall a. [a] -> Maybe a
listToMaybe ([TimeZone] -> TimeZone) -> [TimeZone] -> TimeZone
forall a b. (a -> b) -> a -> b
$
    (TimeZone -> Bool) -> [TimeZone] -> [TimeZone]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (TimeZone -> Bool) -> TimeZone -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TimeZone -> Bool
timeZoneSummerOnly) [TimeZone]
tzs [TimeZone] -> [TimeZone] -> [TimeZone]
forall a. [a] -> [a] -> [a]
++ [TimeZone]
tzs
  where
    tzs :: [TimeZone]
tzs = ((UTCTime, TimeZone) -> TimeZone)
-> [(UTCTime, TimeZone)] -> [TimeZone]
forall a b. (a -> b) -> [a] -> [b]
map (UTCTime, TimeZone) -> TimeZone
forall a b. (a, b) -> b
snd [(UTCTime, TimeZone)]
cs

instance FormatTime TimeZoneSeries where
  formatCharacter :: Bool -> Char -> Maybe (FormatOptions -> TimeZoneSeries -> String)
formatCharacter =
    (Maybe (FormatOptions -> TimeZone -> String)
 -> Maybe (FormatOptions -> TimeZoneSeries -> String))
-> (Char -> Maybe (FormatOptions -> TimeZone -> String))
-> Char
-> Maybe (FormatOptions -> TimeZoneSeries -> String)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((TimeZoneSeries -> TimeZone)
-> Maybe (FormatOptions -> TimeZone -> String)
-> Maybe (FormatOptions -> TimeZoneSeries -> String)
forall a b x.
(a -> b) -> Maybe (x -> b -> String) -> Maybe (x -> a -> String)
mapFormatCharacter TimeZoneSeries -> TimeZone
latestNonSummer) ((Char -> Maybe (FormatOptions -> TimeZone -> String))
 -> Char -> Maybe (FormatOptions -> TimeZoneSeries -> String))
-> (Bool -> Char -> Maybe (FormatOptions -> TimeZone -> String))
-> Bool
-> Char
-> Maybe (FormatOptions -> TimeZoneSeries -> String)
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
    Bool -> Char -> Maybe (FormatOptions -> TimeZone -> String)
forall t.
FormatTime t =>
Bool -> Char -> Maybe (FormatOptions -> t -> String)
formatCharacter

#if MIN_VERSION_time(1,9,1)
mapFormatCharacter ::
  (a -> b) -> Maybe (x -> b -> String) -> Maybe (x -> a -> String)
mapFormatCharacter :: (a -> b) -> Maybe (x -> b -> String) -> Maybe (x -> a -> String)
mapFormatCharacter a -> b
f = ((x -> b -> String) -> x -> a -> String)
-> Maybe (x -> b -> String) -> Maybe (x -> a -> String)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\x -> b -> String
g x
x -> x -> b -> String
g x
x (b -> String) -> (a -> b) -> a -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> b
f)
#elif MIN_VERSION_time(1,8,0)
mapFormatCharacter ::
  (a -> b) -> (c -> d -> e -> b -> z) -> c -> d -> e -> a -> z
mapFormatCharacter f g locale mpado mwidth = g locale mpado mwidth . f
#else
mapFormatCharacter :: (a -> b) -> (c -> d -> b -> z) -> c -> d -> a -> z
mapFormatCharacter f g locale mpado = g locale mpado . f
#endif

-- | Given a timezone represented by a @TimeZoneSeries@, and a @UTCTime@,
-- provide the state of the timezone's clocks at that time.
timeZoneFromSeries :: TimeZoneSeries -> UTCTime -> TimeZone
timeZoneFromSeries :: TimeZoneSeries -> UTCTime -> TimeZone
timeZoneFromSeries (TimeZoneSeries TimeZone
dfault [(UTCTime, TimeZone)]
changes) UTCTime
t = TimeZone -> Maybe TimeZone -> TimeZone
forall a. a -> Maybe a -> a
fromMaybe TimeZone
dfault (Maybe TimeZone -> TimeZone)
-> ([(UTCTime, TimeZone)] -> Maybe TimeZone)
-> [(UTCTime, TimeZone)]
-> TimeZone
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  ((UTCTime, TimeZone) -> TimeZone)
-> Maybe (UTCTime, TimeZone) -> Maybe TimeZone
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (UTCTime, TimeZone) -> TimeZone
forall a b. (a, b) -> b
snd (Maybe (UTCTime, TimeZone) -> Maybe TimeZone)
-> ([(UTCTime, TimeZone)] -> Maybe (UTCTime, TimeZone))
-> [(UTCTime, TimeZone)]
-> Maybe TimeZone
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(UTCTime, TimeZone)] -> Maybe (UTCTime, TimeZone)
forall a. [a] -> Maybe a
listToMaybe ([(UTCTime, TimeZone)] -> Maybe (UTCTime, TimeZone))
-> ([(UTCTime, TimeZone)] -> [(UTCTime, TimeZone)])
-> [(UTCTime, TimeZone)]
-> Maybe (UTCTime, TimeZone)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((UTCTime, TimeZone) -> Bool)
-> [(UTCTime, TimeZone)] -> [(UTCTime, TimeZone)]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile ((UTCTime -> UTCTime -> Bool
forall a. Ord a => a -> a -> Bool
> UTCTime
t) (UTCTime -> Bool)
-> ((UTCTime, TimeZone) -> UTCTime) -> (UTCTime, TimeZone) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (UTCTime, TimeZone) -> UTCTime
forall a b. (a, b) -> a
fst) ([(UTCTime, TimeZone)] -> TimeZone)
-> [(UTCTime, TimeZone)] -> TimeZone
forall a b. (a -> b) -> a -> b
$ [(UTCTime, TimeZone)]
changes

-- The following functions attempt to deal correctly with corner cases
-- where multiple clock changes overlap with each other, even though
-- as far as I know such weird cases have never actually occurred in
-- any timezone. So far.
--
-- The theorem is that if G is the set of individual time changes
-- for which t is invalid ("in the Gap"), and O is the set of
-- individual time changes for which t is redundant ("in the Overlap"),
-- then t is invalid for the full set of changes iff #G > #O, and
-- t is redundant for the full set of changes iff #G < #O.
-- A proof for this theorem, supplied by Aristid Breitkreuz,
-- is available at:
-- http://projects.haskell.org/time-ng/gaps_and_overlaps.html

-- | When a clock change moves the clock forward, local times that
-- are between the wall clock time before the change and the wall
-- clock time after the change cannot occur.
isValidLocalTime :: TimeZoneSeries -> LocalTime -> Bool
isValidLocalTime :: TimeZoneSeries -> LocalTime -> Bool
isValidLocalTime TimeZoneSeries
tzs LocalTime
lt = TimeZoneSeries -> LocalTime -> Ordering
gapsVsOverlaps TimeZoneSeries
tzs LocalTime
lt Ordering -> Ordering -> Bool
forall a. Eq a => a -> a -> Bool
/= Ordering
GT

-- | When a clock change moves the clock backward, local times that
-- are between the wall clock time before the change and the wall
-- clock time after the change occur twice.
isRedundantLocalTime :: TimeZoneSeries -> LocalTime -> Bool
isRedundantLocalTime :: TimeZoneSeries -> LocalTime -> Bool
isRedundantLocalTime TimeZoneSeries
tzs LocalTime
lt = TimeZoneSeries -> LocalTime -> Ordering
gapsVsOverlaps TimeZoneSeries
tzs LocalTime
lt Ordering -> Ordering -> Bool
forall a. Eq a => a -> a -> Bool
== Ordering
LT

-- Compare the number of gaps to the number of overlaps in the
-- timezone series for the given local time, as described above.
gapsVsOverlaps :: TimeZoneSeries -> LocalTime -> Ordering
gapsVsOverlaps :: TimeZoneSeries -> LocalTime -> Ordering
gapsVsOverlaps tzs :: TimeZoneSeries
tzs@(TimeZoneSeries TimeZone
d [(UTCTime, TimeZone)]
cs) LocalTime
lt = [(LocalTime, LocalTime)] -> [(LocalTime, LocalTime)] -> Ordering
forall a. [a] -> [a] -> Ordering
compareLengths [(LocalTime, LocalTime)]
gaps [(LocalTime, LocalTime)]
overlaps
  where
    ([(LocalTime, LocalTime)]
gaps, [(LocalTime, LocalTime)]
overlaps) = ((LocalTime, LocalTime) -> Bool)
-> [(LocalTime, LocalTime)]
-> ([(LocalTime, LocalTime)], [(LocalTime, LocalTime)])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition ((LocalTime -> LocalTime -> Bool) -> (LocalTime, LocalTime) -> Bool
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry LocalTime -> LocalTime -> Bool
forall a. Ord a => a -> a -> Bool
(<)) [(LocalTime, LocalTime)]
relevantIntervals
    relevantIntervals :: [(LocalTime, LocalTime)]
relevantIntervals = ((LocalTime, LocalTime) -> Bool)
-> [(LocalTime, LocalTime)] -> [(LocalTime, LocalTime)]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (LocalTime, LocalTime) -> Bool
notTooEarly ([(LocalTime, LocalTime)] -> [(LocalTime, LocalTime)])
-> ([(LocalTime, LocalTime)] -> [(LocalTime, LocalTime)])
-> [(LocalTime, LocalTime)]
-> [(LocalTime, LocalTime)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((LocalTime, LocalTime) -> Bool)
-> [(LocalTime, LocalTime)] -> [(LocalTime, LocalTime)]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (LocalTime, LocalTime) -> Bool
tooLate ([(LocalTime, LocalTime)] -> [(LocalTime, LocalTime)])
-> [(LocalTime, LocalTime)] -> [(LocalTime, LocalTime)]
forall a b. (a -> b) -> a -> b
$
                        TimeZoneSeries -> [(LocalTime, LocalTime)]
gapsAndOverlaps TimeZoneSeries
tzs
    tooLate :: (LocalTime, LocalTime) -> Bool
tooLate (LocalTime
a, LocalTime
b) = LocalTime
a LocalTime -> LocalTime -> Bool
forall a. Ord a => a -> a -> Bool
> LocalTime
lt Bool -> Bool -> Bool
&& LocalTime
b LocalTime -> LocalTime -> Bool
forall a. Ord a => a -> a -> Bool
> LocalTime
lt
    notTooEarly :: (LocalTime, LocalTime) -> Bool
notTooEarly (LocalTime
a, LocalTime
b) = LocalTime
a LocalTime -> LocalTime -> Bool
forall a. Ord a => a -> a -> Bool
> LocalTime
lt Bool -> Bool -> Bool
|| LocalTime
b LocalTime -> LocalTime -> Bool
forall a. Ord a => a -> a -> Bool
> LocalTime
lt

-- Each pair (ltBefore, ltAfter) represents a time change
-- where the clock is moved from ltBefore to ltAfter. If
-- ltBefore < ltAfter the clock moves forward for that clock
-- change, and otherwise backward.
gapsAndOverlaps :: TimeZoneSeries -> [(LocalTime, LocalTime)]
gapsAndOverlaps :: TimeZoneSeries -> [(LocalTime, LocalTime)]
gapsAndOverlaps (TimeZoneSeries TimeZone
d [(UTCTime, TimeZone)]
cs) = [LocalTime] -> [LocalTime] -> [(LocalTime, LocalTime)]
forall a b. [a] -> [b] -> [(a, b)]
zip
      ((TimeZone -> UTCTime -> LocalTime)
-> [TimeZone] -> [UTCTime] -> [LocalTime]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith TimeZone -> UTCTime -> LocalTime
utcToLocalTime (((UTCTime, TimeZone) -> TimeZone)
-> [(UTCTime, TimeZone)] -> [TimeZone]
forall a b. (a -> b) -> [a] -> [b]
map (UTCTime, TimeZone) -> TimeZone
forall a b. (a, b) -> b
snd (Int -> [(UTCTime, TimeZone)] -> [(UTCTime, TimeZone)]
forall a. Int -> [a] -> [a]
drop Int
1 [(UTCTime, TimeZone)]
cs) [TimeZone] -> [TimeZone] -> [TimeZone]
forall a. [a] -> [a] -> [a]
++ [TimeZone
d]) (((UTCTime, TimeZone) -> UTCTime)
-> [(UTCTime, TimeZone)] -> [UTCTime]
forall a b. (a -> b) -> [a] -> [b]
map (UTCTime, TimeZone) -> UTCTime
forall a b. (a, b) -> a
fst [(UTCTime, TimeZone)]
cs))
      (((UTCTime, TimeZone) -> LocalTime)
-> [(UTCTime, TimeZone)] -> [LocalTime]
forall a b. (a -> b) -> [a] -> [b]
map ((UTCTime -> TimeZone -> LocalTime)
-> (UTCTime, TimeZone) -> LocalTime
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry ((UTCTime -> TimeZone -> LocalTime)
 -> (UTCTime, TimeZone) -> LocalTime)
-> (UTCTime -> TimeZone -> LocalTime)
-> (UTCTime, TimeZone)
-> LocalTime
forall a b. (a -> b) -> a -> b
$ (TimeZone -> UTCTime -> LocalTime)
-> UTCTime -> TimeZone -> LocalTime
forall a b c. (a -> b -> c) -> b -> a -> c
flip TimeZone -> UTCTime -> LocalTime
utcToLocalTime) [(UTCTime, TimeZone)]
cs)

-- Fast lazy comparison of list lengths
compareLengths :: [a] -> [a] -> Ordering
compareLengths :: [a] -> [a] -> Ordering
compareLengths (a
_:[a]
xs) (a
_:[a]
ys) = [a] -> [a] -> Ordering
forall a. [a] -> [a] -> Ordering
compareLengths [a]
xs [a]
ys
compareLengths (a
_:[a]
_ ) [a]
_      = Ordering
GT
compareLengths [a]
_      (a
_:[a]
_ ) = Ordering
LT
compareLengths [a]
_      [a]
_      = Ordering
EQ

-- | A @ZoneSeriesTime@ represents a moment of time in the context of
-- a particular timezone.
data ZoneSeriesTime = ZoneSeriesTime {
       ZoneSeriesTime -> UTCTime
zoneSeriesTimeToUTC :: UTCTime,
       ZoneSeriesTime -> TimeZoneSeries
zoneSeriesTimeSeries :: TimeZoneSeries
    }
  deriving (ZoneSeriesTime -> ZoneSeriesTime -> Bool
(ZoneSeriesTime -> ZoneSeriesTime -> Bool)
-> (ZoneSeriesTime -> ZoneSeriesTime -> Bool) -> Eq ZoneSeriesTime
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
$c/= :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
== :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
$c== :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
Eq, Eq ZoneSeriesTime
Eq ZoneSeriesTime
-> (ZoneSeriesTime -> ZoneSeriesTime -> Ordering)
-> (ZoneSeriesTime -> ZoneSeriesTime -> Bool)
-> (ZoneSeriesTime -> ZoneSeriesTime -> Bool)
-> (ZoneSeriesTime -> ZoneSeriesTime -> Bool)
-> (ZoneSeriesTime -> ZoneSeriesTime -> Bool)
-> (ZoneSeriesTime -> ZoneSeriesTime -> ZoneSeriesTime)
-> (ZoneSeriesTime -> ZoneSeriesTime -> ZoneSeriesTime)
-> Ord ZoneSeriesTime
ZoneSeriesTime -> ZoneSeriesTime -> Bool
ZoneSeriesTime -> ZoneSeriesTime -> Ordering
ZoneSeriesTime -> ZoneSeriesTime -> ZoneSeriesTime
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: ZoneSeriesTime -> ZoneSeriesTime -> ZoneSeriesTime
$cmin :: ZoneSeriesTime -> ZoneSeriesTime -> ZoneSeriesTime
max :: ZoneSeriesTime -> ZoneSeriesTime -> ZoneSeriesTime
$cmax :: ZoneSeriesTime -> ZoneSeriesTime -> ZoneSeriesTime
>= :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
$c>= :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
> :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
$c> :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
<= :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
$c<= :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
< :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
$c< :: ZoneSeriesTime -> ZoneSeriesTime -> Bool
compare :: ZoneSeriesTime -> ZoneSeriesTime -> Ordering
$ccompare :: ZoneSeriesTime -> ZoneSeriesTime -> Ordering
$cp1Ord :: Eq ZoneSeriesTime
Ord, Typeable)

instance Show ZoneSeriesTime where
  show :: ZoneSeriesTime -> String
show ZoneSeriesTime
tzs = ZonedTime -> String
forall a. Show a => a -> String
show (ZonedTime -> String) -> ZonedTime -> String
forall a b. (a -> b) -> a -> b
$ LocalTime -> TimeZone -> ZonedTime
ZonedTime (ZoneSeriesTime -> LocalTime
zoneSeriesTimeToLocalTime ZoneSeriesTime
tzs)
                              (ZoneSeriesTime -> TimeZone
zoneSeriesTimeZone ZoneSeriesTime
tzs)

instance Read ZoneSeriesTime where
    readsPrec :: Int -> ReadS ZoneSeriesTime
readsPrec Int
n = ((ZonedTime, String) -> (ZoneSeriesTime, String))
-> [(ZonedTime, String)] -> [(ZoneSeriesTime, String)]
forall a b. (a -> b) -> [a] -> [b]
map ((ZonedTime -> ZoneSeriesTime)
-> (ZonedTime, String) -> (ZoneSeriesTime, String)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
first ZonedTime -> ZoneSeriesTime
zonedTimeToZoneSeriesTime) ([(ZonedTime, String)] -> [(ZoneSeriesTime, String)])
-> (String -> [(ZonedTime, String)]) -> ReadS ZoneSeriesTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> String -> [(ZonedTime, String)]
forall a. Read a => Int -> ReadS a
readsPrec Int
n

instance ParseTime ZoneSeriesTime where
  buildTime :: TimeLocale -> [(Char, String)] -> Maybe ZoneSeriesTime
buildTime TimeLocale
locale = (ZonedTime -> ZoneSeriesTime)
-> Maybe ZonedTime -> Maybe ZoneSeriesTime
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
mapBuiltTime ZonedTime -> ZoneSeriesTime
zonedTimeToZoneSeriesTime (Maybe ZonedTime -> Maybe ZoneSeriesTime)
-> ([(Char, String)] -> Maybe ZonedTime)
-> [(Char, String)]
-> Maybe ZoneSeriesTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TimeLocale -> [(Char, String)] -> Maybe ZonedTime
forall t. ParseTime t => TimeLocale -> [(Char, String)] -> Maybe t
buildTime TimeLocale
locale
#if MIN_VERSION_time(1,9,1)
  parseTimeSpecifier :: proxy ZoneSeriesTime
-> TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
parseTimeSpecifier proxy ZoneSeriesTime
_ = Proxy ZonedTime
-> TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
forall t (proxy :: * -> *).
ParseTime t =>
proxy t
-> TimeLocale -> Maybe ParseNumericPadding -> Char -> ReadP String
parseTimeSpecifier (Proxy ZonedTime
forall k (t :: k). Proxy t
Proxy :: Proxy ZonedTime)
  substituteTimeSpecifier :: proxy ZoneSeriesTime -> TimeLocale -> Char -> Maybe String
substituteTimeSpecifier proxy ZoneSeriesTime
_ =
    Proxy ZonedTime -> TimeLocale -> Char -> Maybe String
forall t (proxy :: * -> *).
ParseTime t =>
proxy t -> TimeLocale -> Char -> Maybe String
substituteTimeSpecifier (Proxy ZonedTime
forall k (t :: k). Proxy t
Proxy :: Proxy ZonedTime)
#endif

instance FormatTime ZoneSeriesTime where
  formatCharacter :: Bool -> Char -> Maybe (FormatOptions -> ZoneSeriesTime -> String)
formatCharacter =
    (Maybe (FormatOptions -> ZonedTime -> String)
 -> Maybe (FormatOptions -> ZoneSeriesTime -> String))
-> (Char -> Maybe (FormatOptions -> ZonedTime -> String))
-> Char
-> Maybe (FormatOptions -> ZoneSeriesTime -> String)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((ZoneSeriesTime -> ZonedTime)
-> Maybe (FormatOptions -> ZonedTime -> String)
-> Maybe (FormatOptions -> ZoneSeriesTime -> String)
forall a b x.
(a -> b) -> Maybe (x -> b -> String) -> Maybe (x -> a -> String)
mapFormatCharacter ZoneSeriesTime -> ZonedTime
zoneSeriesTimeToZonedTime) ((Char -> Maybe (FormatOptions -> ZonedTime -> String))
 -> Char -> Maybe (FormatOptions -> ZoneSeriesTime -> String))
-> (Bool -> Char -> Maybe (FormatOptions -> ZonedTime -> String))
-> Bool
-> Char
-> Maybe (FormatOptions -> ZoneSeriesTime -> String)
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
    Bool -> Char -> Maybe (FormatOptions -> ZonedTime -> String)
forall t.
FormatTime t =>
Bool -> Char -> Maybe (FormatOptions -> t -> String)
formatCharacter

-- | The @ZonedTime@ of a @ZoneSeriesTime@.
zoneSeriesTimeToZonedTime :: ZoneSeriesTime -> ZonedTime
zoneSeriesTimeToZonedTime :: ZoneSeriesTime -> ZonedTime
zoneSeriesTimeToZonedTime ZoneSeriesTime
zst =
  LocalTime -> TimeZone -> ZonedTime
ZonedTime (ZoneSeriesTime -> LocalTime
zoneSeriesTimeToLocalTime ZoneSeriesTime
zst) (ZoneSeriesTime -> TimeZone
zoneSeriesTimeZone ZoneSeriesTime
zst)

-- | Use a trivial @TimeZoneSeries@ containing only the @TimeZone@
-- of the @ZonedTime@, and use it to define a @ZoneSeriesTime@.
zonedTimeToZoneSeriesTime :: ZonedTime -> ZoneSeriesTime
zonedTimeToZoneSeriesTime :: ZonedTime -> ZoneSeriesTime
zonedTimeToZoneSeriesTime (ZonedTime LocalTime
t TimeZone
tz) =
  UTCTime -> TimeZoneSeries -> ZoneSeriesTime
ZoneSeriesTime (TimeZone -> LocalTime -> UTCTime
localTimeToUTC TimeZone
tz LocalTime
t) (TimeZone -> [(UTCTime, TimeZone)] -> TimeZoneSeries
TimeZoneSeries TimeZone
tz [])

-- | The local time represented by a @ZoneSeriesTime@
zoneSeriesTimeToLocalTime :: ZoneSeriesTime -> LocalTime
zoneSeriesTimeToLocalTime :: ZoneSeriesTime -> LocalTime
zoneSeriesTimeToLocalTime (ZoneSeriesTime UTCTime
t TimeZoneSeries
tzs) = TimeZoneSeries -> UTCTime -> LocalTime
utcToLocalTime' TimeZoneSeries
tzs UTCTime
t

-- | The @TimeZone@ that is in effect at the moment represented by
-- a @ZoneSeriesTime@.
zoneSeriesTimeZone :: ZoneSeriesTime -> TimeZone
zoneSeriesTimeZone :: ZoneSeriesTime -> TimeZone
zoneSeriesTimeZone (ZoneSeriesTime UTCTime
t TimeZoneSeries
tzs) = TimeZoneSeries -> UTCTime -> TimeZone
timeZoneFromSeries TimeZoneSeries
tzs UTCTime
t

-- | The @ZoneSeriesTime@ that represents the given local time in the
-- given timezone. Local times that are invalid or redundant are treated
-- as described below.
localTimeToZoneSeriesTime :: TimeZoneSeries -> LocalTime -> ZoneSeriesTime
localTimeToZoneSeriesTime :: TimeZoneSeries -> LocalTime -> ZoneSeriesTime
localTimeToZoneSeriesTime TimeZoneSeries
tzs LocalTime
lt = UTCTime -> TimeZoneSeries -> ZoneSeriesTime
ZoneSeriesTime (TimeZoneSeries -> LocalTime -> UTCTime
localTimeToUTC' TimeZoneSeries
tzs LocalTime
lt) TimeZoneSeries
tzs

-- $aboutfuncs
-- The following functions are variants on functions in
-- "Data.Time.LocalTime" that convert between UTC and local time. The
-- originals can give a wrong result if the 'TimeZone' used for the
-- conversion is not actually in effect at the specified time. These
-- variants use a 'TimeZoneSeries' instead of a 'TimeZone'.
--
-- When converting from an invalid local time, the local time is
-- interpreted as if the time change that made it invalid never
-- happened.  When converting from a redundant local time, the latest
-- possible interpretation is used. Use the functions
-- 'isValidLocalTime' and 'isRedundantLocalTime' to detect these
-- conditions.

-- | Convert a UTC time to local time using the "TimeZone" that is in
-- effect at that time in the timezone represented by TimeZoneSeries.
utcToLocalTime' :: TimeZoneSeries -> UTCTime -> LocalTime
utcToLocalTime' :: TimeZoneSeries -> UTCTime -> LocalTime
utcToLocalTime' TimeZoneSeries
tzs UTCTime
utc = TimeZone -> UTCTime -> LocalTime
utcToLocalTime (TimeZoneSeries -> UTCTime -> TimeZone
timeZoneFromSeries TimeZoneSeries
tzs UTCTime
utc) UTCTime
utc

-- | Convert a local time to UTC using the "TimeZone" that is in
-- effect at that time in the timezone represented by TimeZoneSeries.
-- Local times that are invalid or redundant are treated as described above.
localTimeToUTC' :: TimeZoneSeries -> LocalTime -> UTCTime
localTimeToUTC' :: TimeZoneSeries -> LocalTime -> UTCTime
localTimeToUTC' (TimeZoneSeries TimeZone
dfault [(UTCTime, TimeZone)]
changes) LocalTime
lt =
  UTCTime -> Maybe UTCTime -> UTCTime
forall a. a -> Maybe a -> a
fromMaybe (TimeZone -> LocalTime -> UTCTime
localTimeToUTC TimeZone
dfault LocalTime
lt) (Maybe UTCTime -> UTCTime)
-> ([(UTCTime, UTCTime)] -> Maybe UTCTime)
-> [(UTCTime, UTCTime)]
-> UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((UTCTime, UTCTime) -> UTCTime)
-> Maybe (UTCTime, UTCTime) -> Maybe UTCTime
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (UTCTime, UTCTime) -> UTCTime
forall a b. (a, b) -> b
snd (Maybe (UTCTime, UTCTime) -> Maybe UTCTime)
-> ([(UTCTime, UTCTime)] -> Maybe (UTCTime, UTCTime))
-> [(UTCTime, UTCTime)]
-> Maybe UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(UTCTime, UTCTime)] -> Maybe (UTCTime, UTCTime)
forall a. [a] -> Maybe a
listToMaybe ([(UTCTime, UTCTime)] -> Maybe (UTCTime, UTCTime))
-> ([(UTCTime, UTCTime)] -> [(UTCTime, UTCTime)])
-> [(UTCTime, UTCTime)]
-> Maybe (UTCTime, UTCTime)
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  ((UTCTime, UTCTime) -> Bool)
-> [(UTCTime, UTCTime)] -> [(UTCTime, UTCTime)]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile ((UTCTime -> UTCTime -> Bool) -> (UTCTime, UTCTime) -> Bool
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry UTCTime -> UTCTime -> Bool
forall a. Ord a => a -> a -> Bool
(>)) ([(UTCTime, UTCTime)] -> UTCTime)
-> [(UTCTime, UTCTime)] -> UTCTime
forall a b. (a -> b) -> a -> b
$
  [UTCTime] -> [UTCTime] -> [(UTCTime, UTCTime)]
forall a b. [a] -> [b] -> [(a, b)]
zip (((UTCTime, TimeZone) -> UTCTime)
-> [(UTCTime, TimeZone)] -> [UTCTime]
forall a b. (a -> b) -> [a] -> [b]
map (UTCTime, TimeZone) -> UTCTime
forall a b. (a, b) -> a
fst [(UTCTime, TimeZone)]
changes) (((UTCTime, TimeZone) -> UTCTime)
-> [(UTCTime, TimeZone)] -> [UTCTime]
forall a b. (a -> b) -> [a] -> [b]
map ((TimeZone -> LocalTime -> UTCTime)
-> LocalTime -> TimeZone -> UTCTime
forall a b c. (a -> b -> c) -> b -> a -> c
flip TimeZone -> LocalTime -> UTCTime
localTimeToUTC LocalTime
lt (TimeZone -> UTCTime)
-> ((UTCTime, TimeZone) -> TimeZone)
-> (UTCTime, TimeZone)
-> UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (UTCTime, TimeZone) -> TimeZone
forall a b. (a, b) -> b
snd) [(UTCTime, TimeZone)]
changes)