packages feed

time-hourglass 0.2.12 → 0.2.13

raw patch · 46 files changed

+2939/−2428 lines, 46 filesdep +tasty-benchdep −bytestringdep −gaugedep −mtldep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: tasty-bench

Dependencies removed: bytestring, gauge, mtl, old-locale

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.Hourglass.Epoch: ElapsedSince :: Seconds -> ElapsedSince epoch
- Data.Hourglass.Epoch: ElapsedSinceP :: {-# UNPACK #-} !ElapsedSince epoch -> {-# UNPACK #-} !NanoSeconds -> ElapsedSinceP epoch
- Data.Hourglass.Epoch: UnixEpoch :: UnixEpoch
- Data.Hourglass.Epoch: WindowsEpoch :: WindowsEpoch
- Data.Hourglass.Epoch: class Epoch epoch
- Data.Hourglass.Epoch: data ElapsedSinceP epoch
- Data.Hourglass.Epoch: data UnixEpoch
- Data.Hourglass.Epoch: data WindowsEpoch
- Data.Hourglass.Epoch: epochDiffToUnix :: Epoch epoch => epoch -> Seconds
- Data.Hourglass.Epoch: epochName :: Epoch epoch => epoch -> String
- Data.Hourglass.Epoch: instance Control.DeepSeq.NFData (Data.Hourglass.Epoch.ElapsedSince epoch)
- Data.Hourglass.Epoch: instance Control.DeepSeq.NFData (Data.Hourglass.Epoch.ElapsedSinceP e)
- Data.Hourglass.Epoch: instance Data.Data.Data epoch => Data.Data.Data (Data.Hourglass.Epoch.ElapsedSince epoch)
- Data.Hourglass.Epoch: instance Data.Data.Data epoch => Data.Data.Data (Data.Hourglass.Epoch.ElapsedSinceP epoch)
- Data.Hourglass.Epoch: instance Data.Hourglass.Epoch.Epoch Data.Hourglass.Epoch.UnixEpoch
- Data.Hourglass.Epoch: instance Data.Hourglass.Epoch.Epoch Data.Hourglass.Epoch.WindowsEpoch
- Data.Hourglass.Epoch: instance Data.Hourglass.Epoch.Epoch epoch => Data.Hourglass.Time.Time (Data.Hourglass.Epoch.ElapsedSince epoch)
- Data.Hourglass.Epoch: instance Data.Hourglass.Epoch.Epoch epoch => Data.Hourglass.Time.Time (Data.Hourglass.Epoch.ElapsedSinceP epoch)
- Data.Hourglass.Epoch: instance Data.Hourglass.Epoch.Epoch epoch => Data.Hourglass.Time.Timeable (Data.Hourglass.Epoch.ElapsedSince epoch)
- Data.Hourglass.Epoch: instance Data.Hourglass.Epoch.Epoch epoch => Data.Hourglass.Time.Timeable (Data.Hourglass.Epoch.ElapsedSinceP epoch)
- Data.Hourglass.Epoch: instance GHC.Classes.Eq (Data.Hourglass.Epoch.ElapsedSince epoch)
- Data.Hourglass.Epoch: instance GHC.Classes.Eq (Data.Hourglass.Epoch.ElapsedSinceP epoch)
- Data.Hourglass.Epoch: instance GHC.Classes.Eq Data.Hourglass.Epoch.UnixEpoch
- Data.Hourglass.Epoch: instance GHC.Classes.Eq Data.Hourglass.Epoch.WindowsEpoch
- Data.Hourglass.Epoch: instance GHC.Classes.Ord (Data.Hourglass.Epoch.ElapsedSince epoch)
- Data.Hourglass.Epoch: instance GHC.Classes.Ord (Data.Hourglass.Epoch.ElapsedSinceP epoch)
- Data.Hourglass.Epoch: instance GHC.Num.Num (Data.Hourglass.Epoch.ElapsedSince epoch)
- Data.Hourglass.Epoch: instance GHC.Num.Num (Data.Hourglass.Epoch.ElapsedSinceP e)
- Data.Hourglass.Epoch: instance GHC.Read.Read (Data.Hourglass.Epoch.ElapsedSince epoch)
- Data.Hourglass.Epoch: instance GHC.Read.Read (Data.Hourglass.Epoch.ElapsedSinceP epoch)
- Data.Hourglass.Epoch: instance GHC.Show.Show (Data.Hourglass.Epoch.ElapsedSince epoch)
- Data.Hourglass.Epoch: instance GHC.Show.Show (Data.Hourglass.Epoch.ElapsedSinceP epoch)
- Data.Hourglass.Epoch: instance GHC.Show.Show Data.Hourglass.Epoch.UnixEpoch
- Data.Hourglass.Epoch: instance GHC.Show.Show Data.Hourglass.Epoch.WindowsEpoch
- Data.Hourglass.Epoch: newtype ElapsedSince epoch
+ Data.Hourglass: April :: Month
+ Data.Hourglass: August :: Month
+ Data.Hourglass: Date :: {-# UNPACK #-} !Int -> !Month -> {-# UNPACK #-} !Int -> Date
+ Data.Hourglass: DateTime :: Date -> TimeOfDay -> DateTime
+ Data.Hourglass: December :: Month
+ Data.Hourglass: February :: Month
+ Data.Hourglass: Friday :: WeekDay
+ Data.Hourglass: Hours :: Int64 -> Hours
+ Data.Hourglass: January :: Month
+ Data.Hourglass: July :: Month
+ Data.Hourglass: June :: Month
+ Data.Hourglass: March :: Month
+ Data.Hourglass: May :: Month
+ Data.Hourglass: Minutes :: Int64 -> Minutes
+ Data.Hourglass: Monday :: WeekDay
+ Data.Hourglass: NanoSeconds :: Int64 -> NanoSeconds
+ Data.Hourglass: November :: Month
+ Data.Hourglass: October :: Month
+ Data.Hourglass: Saturday :: WeekDay
+ Data.Hourglass: Seconds :: Int64 -> Seconds
+ Data.Hourglass: September :: Month
+ Data.Hourglass: Sunday :: WeekDay
+ Data.Hourglass: Thursday :: WeekDay
+ Data.Hourglass: TimeOfDay :: {-# UNPACK #-} !Hours -> {-# UNPACK #-} !Minutes -> {-# UNPACK #-} !Seconds -> {-# UNPACK #-} !NanoSeconds -> TimeOfDay
+ Data.Hourglass: TimezoneOffset :: Int -> TimezoneOffset
+ Data.Hourglass: Tuesday :: WeekDay
+ Data.Hourglass: Wednesday :: WeekDay
+ Data.Hourglass: [dateDay] :: Date -> {-# UNPACK #-} !Int
+ Data.Hourglass: [dateMonth] :: Date -> !Month
+ Data.Hourglass: [dateYear] :: Date -> {-# UNPACK #-} !Int
+ Data.Hourglass: [dtDate] :: DateTime -> Date
+ Data.Hourglass: [dtTime] :: DateTime -> TimeOfDay
+ Data.Hourglass: [timezoneOffsetToMinutes] :: TimezoneOffset -> Int
+ Data.Hourglass: [todHour] :: TimeOfDay -> {-# UNPACK #-} !Hours
+ Data.Hourglass: [todMin] :: TimeOfDay -> {-# UNPACK #-} !Minutes
+ Data.Hourglass: [todNSec] :: TimeOfDay -> {-# UNPACK #-} !NanoSeconds
+ Data.Hourglass: [todSec] :: TimeOfDay -> {-# UNPACK #-} !Seconds
+ Data.Hourglass: data Date
+ Data.Hourglass: data DateTime
+ Data.Hourglass: data Month
+ Data.Hourglass: data TimeOfDay
+ Data.Hourglass: data WeekDay
+ Data.Hourglass: localTimeGetTimezone :: LocalTime t -> TimezoneOffset
+ Data.Hourglass: localTimeUnwrap :: LocalTime t -> t
+ Data.Hourglass: newtype Hours
+ Data.Hourglass: newtype Minutes
+ Data.Hourglass: newtype NanoSeconds
+ Data.Hourglass: newtype Seconds
+ Data.Hourglass: newtype TimezoneOffset
+ Data.Hourglass: timezoneOffsetToSeconds :: TimezoneOffset -> Seconds
+ Data.Hourglass: timezone_UTC :: TimezoneOffset
+ Time.Compat: dateFromMJDEpoch :: Integer -> Date
+ Time.Compat: dateFromUnixEpoch :: Integer -> Date
+ Time.Epoch: ElapsedSince :: Seconds -> ElapsedSince epoch
+ Time.Epoch: ElapsedSinceP :: {-# UNPACK #-} !ElapsedSince epoch -> {-# UNPACK #-} !NanoSeconds -> ElapsedSinceP epoch
+ Time.Epoch: UnixEpoch :: UnixEpoch
+ Time.Epoch: WindowsEpoch :: WindowsEpoch
+ Time.Epoch: class Epoch epoch
+ Time.Epoch: data ElapsedSinceP epoch
+ Time.Epoch: data UnixEpoch
+ Time.Epoch: data WindowsEpoch
+ Time.Epoch: epochDiffToUnix :: Epoch epoch => epoch -> Seconds
+ Time.Epoch: epochName :: Epoch epoch => epoch -> String
+ Time.Epoch: instance Control.DeepSeq.NFData (Time.Epoch.ElapsedSince epoch)
+ Time.Epoch: instance Control.DeepSeq.NFData (Time.Epoch.ElapsedSinceP e)
+ Time.Epoch: instance Data.Data.Data epoch => Data.Data.Data (Time.Epoch.ElapsedSince epoch)
+ Time.Epoch: instance Data.Data.Data epoch => Data.Data.Data (Time.Epoch.ElapsedSinceP epoch)
+ Time.Epoch: instance GHC.Classes.Eq (Time.Epoch.ElapsedSince epoch)
+ Time.Epoch: instance GHC.Classes.Eq (Time.Epoch.ElapsedSinceP epoch)
+ Time.Epoch: instance GHC.Classes.Eq Time.Epoch.UnixEpoch
+ Time.Epoch: instance GHC.Classes.Eq Time.Epoch.WindowsEpoch
+ Time.Epoch: instance GHC.Classes.Ord (Time.Epoch.ElapsedSince epoch)
+ Time.Epoch: instance GHC.Classes.Ord (Time.Epoch.ElapsedSinceP epoch)
+ Time.Epoch: instance GHC.Num.Num (Time.Epoch.ElapsedSince epoch)
+ Time.Epoch: instance GHC.Num.Num (Time.Epoch.ElapsedSinceP e)
+ Time.Epoch: instance GHC.Read.Read (Time.Epoch.ElapsedSince epoch)
+ Time.Epoch: instance GHC.Read.Read (Time.Epoch.ElapsedSinceP epoch)
+ Time.Epoch: instance GHC.Show.Show (Time.Epoch.ElapsedSince epoch)
+ Time.Epoch: instance GHC.Show.Show (Time.Epoch.ElapsedSinceP epoch)
+ Time.Epoch: instance GHC.Show.Show Time.Epoch.UnixEpoch
+ Time.Epoch: instance GHC.Show.Show Time.Epoch.WindowsEpoch
+ Time.Epoch: instance Time.Epoch.Epoch Time.Epoch.UnixEpoch
+ Time.Epoch: instance Time.Epoch.Epoch Time.Epoch.WindowsEpoch
+ Time.Epoch: instance Time.Epoch.Epoch epoch => Time.Time.Time (Time.Epoch.ElapsedSince epoch)
+ Time.Epoch: instance Time.Epoch.Epoch epoch => Time.Time.Time (Time.Epoch.ElapsedSinceP epoch)
+ Time.Epoch: instance Time.Epoch.Epoch epoch => Time.Time.Timeable (Time.Epoch.ElapsedSince epoch)
+ Time.Epoch: instance Time.Epoch.Epoch epoch => Time.Time.Timeable (Time.Epoch.ElapsedSinceP epoch)
+ Time.Epoch: newtype ElapsedSince epoch

Files

CHANGELOG.md view
@@ -6,6 +6,27 @@ and this project adheres to the
 [Haskell Package Versioning Policy](https://pvp.haskell.org/).
 
+## 0.2.13
+
+* Drop support for GHC < 8.4.
+* Move library modules to directory `src` and benchmark module to directory
+  `benchmarks`.
+* Move module `Example.Time.Compat` to directory `examples`.
+* Expose module `Time.Epoch` and deprecate equivalent module
+  `Data.Hourglass.Epoch`.
+* Renamed non-exposed library modules under the `Time.*` hierarchy.
+* Use `LANGUAGE PackageImports` in module `Example.Time.Compat`, allowing
+  `stack ghci examples/Example/Time/Compat.hs`.
+* Eliminate the use of CPP to vary source code for different operating systems.
+* Fix `other-modules` of `bench-hourglass` benchmark.
+* `bench-hourglass` benchmark depends on `tasty-bench`, drop dependency on
+  `gauge`.
+* Improve Haddock and other documentation.
+* Export new `dateFromUnixEpoch` and deprecate identical `dateFromPOSIXEpoch` to
+  name epoch consistently.
+* Export new `dateFromMJDEpoch` and deprecate identical `dateFromTAIEpoch` to
+  fix the latter being a misnomer.
+
 ## 0.2.12
 
 * Rename `hourglass-0.2.12` package as `time-hourglass-0.2.12`.
− Data/Hourglass.hs
@@ -1,41 +0,0 @@--- |
--- Module      : Data.Hourglass
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Types and methods for time manipulation.
---
--- The most basic type for time representation is Elapsed, which
--- represent a number of elapsed seconds since the unix epoch.
---
--- Every other defined types can be convert to and from Elapsed type:
---
--- > timeGetElapsed (Date 1 2 3) :: Elapsed
--- > timeFromElapsed 123         :: DateTime
---
--- Local time is represented by any other time types (Elapsed, Date, DateTime, ..),
--- but augmented by a Timezone offset in minutes.
---
--- > localTime (Date 2014 May 4) 600 -- local time at UTC+10 of May 4th 2014
---
-module Data.Hourglass
-    ( module Data.Hourglass.Time
-    , module Data.Hourglass.Types
-    , module Data.Hourglass.Format
-    , module Data.Hourglass.Local
-    , module Data.Hourglass.Zone
-    -- * Calendar misc functions
-    , isLeapYear
-    , getWeekDay
-    , getDayOfTheYear
-    , daysInMonth
-    ) where
-
-import Data.Hourglass.Time
-import Data.Hourglass.Format
-import Data.Hourglass.Types
-import Data.Hourglass.Local
-import Data.Hourglass.Zone
-import Data.Hourglass.Calendar (isLeapYear, getWeekDay, getDayOfTheYear, daysInMonth)
− Data/Hourglass/Calendar.hs
@@ -1,86 +0,0 @@--- |
--- Module      : Data.Hourglass.Calendar
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Misc calendar functions
---
-module Data.Hourglass.Calendar
-    ( isLeapYear
-    , getWeekDay
-    , getDayOfTheYear
-    , daysInMonth
-    , dateToUnixEpoch
-    , dateFromUnixEpoch
-    , todToSeconds
-    , dateTimeToUnixEpoch
-    , dateTimeFromUnixEpoch
-    , dateTimeFromUnixEpochP
-    ) where
-
-import Data.Hourglass.Types
-import Data.Hourglass.Internal
-
--- | Return if this year is a leap year (366 days)
--- or not (365 days in a year)
-isLeapYear :: Int -> Bool
-isLeapYear year
-    | year `mod` 4 /= 0   = False
-    | year `mod` 100 /= 0 = True
-    | year `mod` 400 == 0 = True
-    | otherwise           = False
-
--- | Return the day of the week a specific date fall in
-getWeekDay :: Date -> WeekDay
-getWeekDay date = toEnum (d `mod` 7)
-  where d = daysOfDate date
-
--- | return the number of days until the beggining of the month specified for a specific year.
-daysUntilMonth :: Int -> Month -> Int
-daysUntilMonth y m
-    | isLeapYear y = leapYears !! fromEnum m
-    | otherwise    = normalYears !! fromEnum m
-  where normalYears = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ]
-        leapYears   = [ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 ]
-
--- | Return the number of days in a month.
-daysInMonth :: Int -> Month -> Int
-daysInMonth y m
-    | m == February && isLeapYear y = 29
-    | otherwise                     = days !! fromEnum m
-  where days = [31,28,31,30,31,30,31,31,30,31,30,31]
-
--- | return the day of the year where Jan 1 is 0
---
--- between 0 and 364. 365 for leap years
-getDayOfTheYear :: Date -> Int
-getDayOfTheYear (Date y m d) = daysUntilMonth y m + d
-
--- | return the number of days before Jan 1st of the year
-daysBeforeYear :: Int -> Int
-daysBeforeYear year = y * 365 + (y `div` 4) - (y `div` 100) + (y `div` 400)
-  where y = year - 1
-
--- | Return the number of day since 1 january 1
-daysOfDate :: Date -> Int
-daysOfDate (Date y m d) = daysBeforeYear y + daysUntilMonth y m + d
-
--- | Return the number of seconds to unix epoch of a date considering hour=0,minute=0,second=0
-dateToUnixEpoch :: Date -> Elapsed
-dateToUnixEpoch date = Elapsed $ Seconds (fromIntegral (daysOfDate date - epochDays) * secondsPerDay)
-  where epochDays     = 719163
-        secondsPerDay = 86400 -- julian day is 24h
-
--- | Return the Date associated with the unix epoch
-dateFromUnixEpoch :: Elapsed -> Date
-dateFromUnixEpoch e = dtDate $ dateTimeFromUnixEpoch e
-
--- | Return the number of seconds from a time structure
-todToSeconds :: TimeOfDay -> Seconds
-todToSeconds (TimeOfDay h m s _) = toSeconds h + toSeconds m + s
-
--- | Return the number of seconds to unix epoch of a date time
-dateTimeToUnixEpoch :: DateTime -> Elapsed
-dateTimeToUnixEpoch (DateTime d t) = dateToUnixEpoch d + Elapsed (todToSeconds t)
− Data/Hourglass/Compat.hs
@@ -1,38 +0,0 @@--- |
--- Module      : Data.Hourglass.Compat
--- License     : BSD-style
--- Maintainer  : Nicolas DI PRIMA <nicolas@di-prima.fr>
---
--- Basic Time conversion compatibility.
---
--- This module aims to help conversion between the types from the package
--- time to the package hourglass.
---
--- Example of use (extracted from file Example/Time/Compat.hs):
---
--- > import Data.Hourglass        as H
--- > import Data.Hourglass.Compat as C
--- > import Data.Time             as T
--- >
--- > transpose :: T.ZonedTime
--- >           -> H.LocalTime H.DateTime
--- > transpose oldTime =
--- >     H.localTime
--- >         offsetTime
--- >         (H.DateTime newDate timeofday)
--- >   where
--- >     newDate :: H.Date
--- >     newDate = C.dateFromTAIEpoch $ T.toModifiedJulianDay $ T.localDay $ T.zonedTimeToLocalTime oldTime
--- >
--- >     timeofday :: H.TimeOfDay
--- >     timeofday = C.diffTimeToTimeOfDay $ T.timeOfDayToTime $ T.localTimeOfDay $ T.zonedTimeToLocalTime oldTime
--- >
--- >     offsetTime = H.TimezoneOffset $ fromIntegral $ T.timeZoneMinutes $ T.zonedTimeZone oldTime
---
--- This module will be depreciated in favor of Time.Compat
---
-module Data.Hourglass.Compat
-    ( module Time.Compat
-    ) where
-
-import Time.Compat
− Data/Hourglass/Diff.hs
@@ -1,162 +0,0 @@-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DeriveDataTypeable #-}
--- |
--- Module      : Data.Hourglass.Diff
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- time arithmetic methods
---
-module Data.Hourglass.Diff
-    ( Duration(..)
-    , Period(..)
-    , durationNormalize
-    , durationFlatten
-    , elapsedTimeAddSeconds
-    , elapsedTimeAddSecondsP
-    , dateAddPeriod
-    ) where
-
-import Data.Data
-import Data.Monoid
-import Data.Hourglass.Types
-import Data.Hourglass.Calendar
-import Control.DeepSeq
-
--- | An amount of conceptual calendar time in terms of years, months and days.
---
--- This allow calendar manipulation, representing things like days and months
--- irrespective on how long those are related to timezone and daylight changes.
---
--- See 'Duration' for the time-based equivalent to this class.
-data Period = Period
-    { periodYears  :: !Int
-    , periodMonths :: !Int
-    , periodDays   :: !Int
-    } deriving (Show,Read,Eq,Ord,Data,Typeable)
-
-instance NFData Period where
-    rnf (Period y m d) = y `seq` m `seq` d `seq` ()
-#if (MIN_VERSION_base(4,11,0))
-instance Semigroup Period where
-    (<>) (Period y1 m1 d1) (Period y2 m2 d2) =
-        Period (y1+y2) (m1+m2) (d1+d2)
-#endif
-instance Monoid Period where
-    mempty = Period 0 0 0
-    mappend (Period y1 m1 d1) (Period y2 m2 d2) =
-        Period (y1+y2) (m1+m2) (d1+d2)
-
--- | An amount of time in terms of constant value like hours (3600 seconds),
--- minutes (60 seconds), seconds and nanoseconds.
-data Duration = Duration
-    { durationHours   :: !Hours       -- ^ number of hours
-    , durationMinutes :: !Minutes     -- ^ number of minutes
-    , durationSeconds :: !Seconds     -- ^ number of seconds
-    , durationNs      :: !NanoSeconds -- ^ number of nanoseconds
-    } deriving (Show,Read,Eq,Ord,Data,Typeable)
-
-instance NFData Duration where
-    rnf (Duration h m s ns) = h `seq` m `seq` s `seq` ns `seq` ()
-#if (MIN_VERSION_base(4,11,0))
-instance Semigroup Duration where
-    (<>) (Duration h1 m1 s1 ns1) (Duration h2 m2 s2 ns2) =
-        Duration (h1+h2) (m1+m2) (s1+s2) (ns1+ns2)
-#endif
-instance Monoid Duration where
-    mempty = Duration 0 0 0 0
-    mappend (Duration h1 m1 s1 ns1) (Duration h2 m2 s2 ns2) =
-        Duration (h1+h2) (m1+m2) (s1+s2) (ns1+ns2)
-instance TimeInterval Duration where
-    fromSeconds s = (durationNormalize (Duration 0 0 s 0), 0)
-    toSeconds d   = fst $ durationFlatten d
-
--- | Flatten a duration to a number of seconds, nanoseconds
-durationFlatten :: Duration -> (Seconds, NanoSeconds)
-durationFlatten (Duration h m s (NanoSeconds ns)) =
-    (toSeconds h + toSeconds m + s + Seconds sacc, NanoSeconds ns')
-  where (sacc, ns') = ns `divMod` 1000000000
-
--- | Normalize all fields to represent the same value
--- with the biggest units possible.
---
--- For example, 62 minutes is normalized as 1h 2minutes
-durationNormalize :: Duration -> Duration
-durationNormalize (Duration (Hours h) (Minutes mi) (Seconds s) (NanoSeconds ns)) =
-    Duration (Hours (h+hacc)) (Minutes mi') (Seconds s') (NanoSeconds ns')
-  where (hacc, mi') = (mi+miacc) `divMod` 60
-        (miacc, s') = (s+sacc) `divMod` 60
-        (sacc, ns') = ns `divMod` 1000000000
-
--- | add a period of time to a date
-dateAddPeriod :: Date -> Period -> Date
-dateAddPeriod (Date yOrig mOrig dOrig) (Period yDiff mDiff dDiff) =
-    loop (yOrig + yDiff + yDiffAcc) mStartPos (dOrig+dDiff)
-  where
-    (yDiffAcc,mStartPos) = (fromEnum mOrig + mDiff) `divMod` 12
-    loop y m d
-        | d <= 0 =
-            let (m', y') = if m == 0
-                then (11, y - 1)
-                else (m - 1, y)
-            in
-            loop y' m' (daysInMonth y' (toEnum m') + d)
-        | d <= dMonth = Date y (toEnum m) d
-        | otherwise  =
-            let newDiff = d - dMonth
-             in if m == 11
-                    then loop (y+1) 0 newDiff
-                    else loop y (m+1) newDiff
-      where dMonth = daysInMonth y (toEnum m)
-
--- | Add a number of seconds to an Elapsed type
-elapsedTimeAddSeconds :: Elapsed -> Seconds -> Elapsed
-elapsedTimeAddSeconds (Elapsed s1) s2 = Elapsed (s1+s2)
-
--- | Add a number of seconds to an ElapsedP type
-elapsedTimeAddSecondsP :: ElapsedP -> Seconds -> ElapsedP
-elapsedTimeAddSecondsP (ElapsedP (Elapsed s1) ns1) s2 =
-    ElapsedP (Elapsed (s1+s2)) ns1
-
-{- disabled for warning purpose. to be implemented
-
--- | Duration string to time diff
---
--- <http://en.wikipedia.org/wiki/ISO_8601#Durations>
---
--- * P is the duration designator (historically called "period") placed at the start of the duration representation.
---
--- * Y is the year designator that follows the value for the number of years.
---
--- * M is the month designator that follows the value for the number of months.
---
--- * W is the week designator that follows the value for the number of weeks.
---
--- * D is the day designator that follows the value for the number of days.
---
--- * T is the time designator that precedes the time components of the representation.
---
--- * H is the hour designator that follows the value for the number of hours.
---
--- * M is the minute designator that follows the value for the number of minutes.
---
--- * S is the second designator that follows the value for the number of seconds.
---
-timeDiffFromDuration :: String -> TimeDiff
-timeDiffFromDuration _ = undefined
-
-timeDiffFromString :: String -> (
-
--- | Human description string to time diff
---
--- examples:
---
--- * "1 day"
---
--- * "2 months, 5 days and 1 second"
---
-timeDiffFromDescription :: String -> TimeDiff
-timeDiffFromDescription _ = undefined
--}
− Data/Hourglass/Epoch.hs
@@ -1,129 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE DeriveDataTypeable #-}
--- |
--- Module      : Data.Hourglass.Epoch
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Epoch tracking
---
-module Data.Hourglass.Epoch
-    (
-    -- * computer time tracking with various epoch
-      ElapsedSince(..)
-    , ElapsedSinceP(..)
-    -- * Epoch
-    , Epoch(..)
-    -- ** standard and usual epochs
-    , UnixEpoch(..)
-    , WindowsEpoch(..)
-    ) where
-
-import Data.Data
-import Control.DeepSeq
-import Data.Hourglass.Types
-import Data.Hourglass.Time
-
--- | A number of seconds elapsed since an epoch.
-newtype ElapsedSince epoch = ElapsedSince Seconds
-    deriving (Show,Read,Eq,Ord,Num,Data,Typeable,NFData)
-
--- | A number of seconds and nanoseconds elapsed since an epoch.
-data ElapsedSinceP epoch = ElapsedSinceP {-# UNPACK #-} !(ElapsedSince epoch)
-                                         {-# UNPACK #-} !NanoSeconds
-    deriving (Show,Read,Eq,Ord,Data,Typeable)
-
-instance NFData (ElapsedSinceP e) where rnf e = e `seq` ()
-
-instance Num (ElapsedSinceP e) where
-    (ElapsedSinceP e1 ns1) + (ElapsedSinceP e2 ns2) = ElapsedSinceP (e1+e2) (ns1+ns2)
-    (ElapsedSinceP e1 ns1) - (ElapsedSinceP e2 ns2) = ElapsedSinceP (e1-e2) (ns1-ns2)
-    (ElapsedSinceP e1 ns1) * (ElapsedSinceP e2 ns2) = ElapsedSinceP (e1*e2) (ns1*ns2)
-    negate (ElapsedSinceP e ns) = ElapsedSinceP (negate e) ns
-    abs (ElapsedSinceP e ns)    = ElapsedSinceP (abs e) ns
-    signum (ElapsedSinceP e ns) = ElapsedSinceP (signum e) ns
-    fromInteger i          = ElapsedSinceP (ElapsedSince (fromIntegral i)) 0
-
--- FIXME instance Real (ElapsedSinceP e)
-
--- | epoch related.
---
--- We use the well known Unix epoch as the
--- reference timezone for doing conversion between epochs.
---
--- Each methods of this typeclass should not use the actual value,
--- but only get the information needed from the type itself.
-class Epoch epoch where
-    -- | The name of this epoch
-    epochName :: epoch -> String
-
-    -- | number of seconds of difference with 1st January 1970.
-    --
-    -- a negative number means that this epoch start before
-    -- the unix epoch.
-    epochDiffToUnix :: epoch -> Seconds
-
--- | Unix Epoch, starting 1st January 1970
-data UnixEpoch = UnixEpoch
-    deriving (Show,Eq)
-
-instance Epoch UnixEpoch where
-    epochName _ = "unix"
-    epochDiffToUnix _ = 0
-
--- | Windows Epoch, starting 1st January 1601
-data WindowsEpoch = WindowsEpoch
-    deriving (Show,Eq)
-
-instance Epoch WindowsEpoch where
-    epochName _ = "windows"
-    epochDiffToUnix _ = -11644473600
-
-instance Epoch epoch => Timeable (ElapsedSince epoch) where
-    timeGetElapsedP es = ElapsedP (Elapsed e) 0
-      where ElapsedSince e = convertEpoch es :: ElapsedSince UnixEpoch
-    timeGetElapsed   es = Elapsed e
-      where ElapsedSince e = convertEpoch es :: ElapsedSince UnixEpoch
-    timeGetNanoSeconds _ = 0
-
-instance Epoch epoch => Time (ElapsedSince epoch) where
-    timeFromElapsedP (ElapsedP (Elapsed e) _) =
-        convertEpoch (ElapsedSince e :: ElapsedSince UnixEpoch)
-
-instance Epoch epoch => Timeable (ElapsedSinceP epoch) where
-    timeGetElapsedP es = ElapsedP (Elapsed e) ns
-      where ElapsedSinceP (ElapsedSince e) ns = convertEpochP es :: ElapsedSinceP UnixEpoch
-    timeGetNanoSeconds (ElapsedSinceP _ ns) = ns
-instance Epoch epoch => Time (ElapsedSinceP epoch) where
-    timeFromElapsedP (ElapsedP (Elapsed e) ns) = convertEpochP (ElapsedSinceP (ElapsedSince e) ns :: ElapsedSinceP UnixEpoch)
-
--- | Convert Elapsed seconds to another epoch with explicit epochs specified
-convertEpochWith :: (Epoch e1, Epoch e2) => (e1,e2) -> ElapsedSince e1 -> ElapsedSince e2
-convertEpochWith (e1,e2) (ElapsedSince s1) = ElapsedSince (s1 + diff)
-  where diff = d1 - d2
-        d1 = epochDiffToUnix e1
-        d2 = epochDiffToUnix e2
-
--- | Convert Elapsed seconds to another epoch.
---
--- the actual epochs need to be known somehow by the context, otherwise this function
--- will yield a compilation errors as the epoch are not chosen.
---
--- If you want to force specific epoch conversion, use convertEpochWith
-convertEpoch :: (Epoch e1, Epoch e2) => ElapsedSince e1 -> ElapsedSince e2
-convertEpoch = convertEpochWith (undefined, undefined)
-
--- | Convert Precise Elapsed seconds to another epoch with explicit epochs specified
-convertEpochPWith :: (Epoch e1, Epoch e2) => (e1,e2) -> ElapsedSinceP e1 -> ElapsedSinceP e2
-convertEpochPWith es (ElapsedSinceP e1 n1) = ElapsedSinceP (convertEpochWith es e1) n1
-
--- | Convert Elapsed seconds to another epoch.
---
--- the actual epochs need to be known somehow by the context, otherwise this function
--- will yield a compilation errors as the epoch are not chosen.
---
--- If you want to force specific epoch conversion, use convertEpochWith
-convertEpochP :: (Epoch e1, Epoch e2) => ElapsedSinceP e1 -> ElapsedSinceP e2
-convertEpochP = convertEpochPWith (undefined, undefined)
− Data/Hourglass/Format.hs
@@ -1,403 +0,0 @@--- |
--- Module      : Data.Hourglass.Format
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Time formatting : printing and parsing
---
--- Built-in format strings
---
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE TypeSynonymInstances #-}
-module Data.Hourglass.Format
-    (
-    -- * Parsing and Printing
-    -- ** Format strings
-      TimeFormatElem(..)
-    , TimeFormatFct(..)
-    , TimeFormatString(..)
-    , TimeFormat(..)
-    -- ** Common built-in formats
-    , ISO8601_Date(..)
-    , ISO8601_DateAndTime(..)
-    -- ** Format methods
-    , timePrint
-    , timeParse
-    , timeParseE
-    , localTimePrint
-    , localTimeParse
-    , localTimeParseE
-    ) where
-
-import Data.Hourglass.Types
-import Data.Hourglass.Time
-import Data.Hourglass.Calendar
-import Data.Hourglass.Local
-import Data.Hourglass.Utils
-import Data.Char (isDigit, ord)
-import Data.Int
-
--- | All the various formatter that can be part
--- of a time format string
-data TimeFormatElem =
-      Format_Year2      -- ^ 2 digit years (70 is 1970, 69 is 2069)
-    | Format_Year4      -- ^ 4 digits years
-    | Format_Year       -- ^ any digits years
-    | Format_Month      -- ^ months (1 to 12)
-    | Format_Month2     -- ^ months padded to 2 chars (01 to 12)
-    | Format_MonthName_Short -- ^ name of the month short ('Jan', 'Feb' ..)
-    | Format_DayYear    -- ^ day of the year (1 to 365, 366 for leap years)
-    | Format_Day        -- ^ day of the month (1 to 31)
-    | Format_Day2       -- ^ day of the month (01 to 31)
-    | Format_Hour       -- ^ hours (0 to 23)
-    | Format_Minute     -- ^ minutes (0 to 59)
-    | Format_Second     -- ^ seconds (0 to 59, 60 for leap seconds)
-    | Format_UnixSecond -- ^ number of seconds since 1 jan 1970. unix epoch.
-    | Format_MilliSecond -- ^ Milliseconds (000 to 999)
-    | Format_MicroSecond -- ^ MicroSeconds (000000 to 999999)
-    | Format_NanoSecond  -- ^ NanoSeconds (000000000 to 999999999)
-    | Format_Precision Int -- ^ sub seconds display with a precision of N digits. with N between 1 and 9
-    | Format_TimezoneName   -- ^ timezone name (e.g. GMT, PST). not implemented yet
-    -- | Format_TimezoneOffset -- ^ timeoffset offset (+02:00)
-    | Format_TzHM_Colon_Z -- ^ zero UTC offset (Z) or timeoffset with colon (+02:00)
-    | Format_TzHM_Colon -- ^ timeoffset offset with colon (+02:00)
-    | Format_TzHM       -- ^ timeoffset offset (+0200)
-    | Format_Tz_Offset  -- ^ timeoffset in minutes
-    | Format_Spaces     -- ^ one or many space-like chars
-    | Format_Text Char  -- ^ a verbatim char
-    | Format_Fct TimeFormatFct
-    deriving (Show,Eq)
-
--- | A generic format function composed of a parser and a printer.
-data TimeFormatFct = TimeFormatFct
-    { timeFormatFctName :: String
-    , timeFormatParse   :: DateTime -> String -> Either String (DateTime, String)
-    , timeFormatPrint   :: DateTime -> String
-    }
-
-instance Show TimeFormatFct where
-    show = timeFormatFctName
-instance Eq TimeFormatFct where
-    t1 == t2 = timeFormatFctName t1 == timeFormatFctName t2
-
--- | A time format string, composed of list of 'TimeFormatElem'
-newtype TimeFormatString = TimeFormatString [TimeFormatElem]
-    deriving (Show,Eq)
-
--- | A generic class for anything that can be considered a Time Format string.
-class TimeFormat format where
-    toFormat :: format -> TimeFormatString
-
--- | ISO8601 Date format string.
---
--- e.g. 2014-04-05
-data ISO8601_Date = ISO8601_Date
-    deriving (Show,Eq)
-
--- | ISO8601 Date and Time format string.
---
--- e.g. 2014-04-05T17:25:04+00:00
---      2014-04-05T17:25:04Z
-data ISO8601_DateAndTime = ISO8601_DateAndTime
-    deriving (Show,Eq)
-
-instance TimeFormat [TimeFormatElem] where
-    toFormat = TimeFormatString
-
-instance TimeFormat TimeFormatString where
-    toFormat = id
-
-instance TimeFormat String where
-    toFormat = TimeFormatString . toFormatElem
-      where toFormatElem []                  = []
-            toFormatElem ('Y':'Y':'Y':'Y':r) = Format_Year4  : toFormatElem r
-            toFormatElem ('Y':'Y':r)         = Format_Year2  : toFormatElem r
-            toFormatElem ('M':'M':r)         = Format_Month2 : toFormatElem r
-            toFormatElem ('M':'o':'n':r)     = Format_MonthName_Short : toFormatElem r
-            toFormatElem ('M':'I':r)         = Format_Minute : toFormatElem r
-            toFormatElem ('M':r)             = Format_Month  : toFormatElem r
-            toFormatElem ('D':'D':r)         = Format_Day2   : toFormatElem r
-            toFormatElem ('H':r)             = Format_Hour   : toFormatElem r
-            toFormatElem ('S':r)             = Format_Second : toFormatElem r
-            toFormatElem ('m':'s':r)         = Format_MilliSecond : toFormatElem r
-            toFormatElem ('u':'s':r)         = Format_MicroSecond : toFormatElem r
-            toFormatElem ('μ':r)             = Format_MicroSecond : toFormatElem r
-            toFormatElem ('n':'s':r)         = Format_NanoSecond : toFormatElem r
-            toFormatElem ('p':'1':r)         = Format_Precision 1 : toFormatElem r
-            toFormatElem ('p':'2':r)         = Format_Precision 2 : toFormatElem r
-            toFormatElem ('p':'3':r)         = Format_Precision 3 : toFormatElem r
-            toFormatElem ('p':'4':r)         = Format_Precision 4 : toFormatElem r
-            toFormatElem ('p':'5':r)         = Format_Precision 5 : toFormatElem r
-            toFormatElem ('p':'6':r)         = Format_Precision 6 : toFormatElem r
-            toFormatElem ('p':'7':r)         = Format_Precision 7 : toFormatElem r
-            toFormatElem ('p':'8':r)         = Format_Precision 8 : toFormatElem r
-            toFormatElem ('p':'9':r)         = Format_Precision 9 : toFormatElem r
-            -----------------------------------------------------------
-            toFormatElem ('E':'P':'O':'C':'H':r) = Format_UnixSecond : toFormatElem r
-            -----------------------------------------------------------
-            toFormatElem ('T':'Z':'H':'M':r)     = Format_TzHM : toFormatElem r
-            toFormatElem ('T':'Z':'H':':':'M':r) = Format_TzHM_Colon : toFormatElem r
-            toFormatElem ('T':'Z':'O':'F':'S':r) = Format_Tz_Offset : toFormatElem r
-            -----------------------------------------------------------
-            toFormatElem ('\\':c:r)          = Format_Text c : toFormatElem r
-            toFormatElem (' ':r)             = Format_Spaces : toFormatElem r
-            toFormatElem (c:r)               = Format_Text c : toFormatElem r
-
-instance TimeFormat ISO8601_Date where
-    toFormat _ = TimeFormatString [Format_Year,dash,Format_Month2,dash,Format_Day2]
-      where dash = Format_Text '-'
-
-instance TimeFormat ISO8601_DateAndTime where
-    toFormat _ = TimeFormatString
-        [Format_Year,dash,Format_Month2,dash,Format_Day2 -- date
-        ,Format_Text 'T'
-        ,Format_Hour,colon,Format_Minute,colon,Format_Second -- time
-        ,Format_TzHM_Colon_Z -- zero UTC offset (Z) or timezone offset with colon +HH:MM
-        ]
-      where dash = Format_Text '-'
-            colon = Format_Text ':'
-
-monthFromShort :: String -> Either String Month
-monthFromShort str =
-    case str of
-        "Jan" -> Right January
-        "Feb" -> Right February
-        "Mar" -> Right March
-        "Apr" -> Right April
-        "May" -> Right May
-        "Jun" -> Right June
-        "Jul" -> Right July
-        "Aug" -> Right August
-        "Sep" -> Right September
-        "Oct" -> Right October
-        "Nov" -> Right November
-        "Dec" -> Right December
-        _     -> Left $ "unknown month: " ++ str
-
-printWith :: (TimeFormat format, Timeable t)
-          => format
-          -> TimezoneOffset
-          -> t
-          -> String
-printWith fmt tzOfs@(TimezoneOffset tz) t = concatMap fmtToString fmtElems
-  where fmtToString Format_Year     = show (dateYear date)
-        fmtToString Format_Year4    = pad4 (dateYear date)
-        fmtToString Format_Year2    = pad2 (dateYear date-1900)
-        fmtToString Format_Month2   = pad2 (fromEnum (dateMonth date)+1)
-        fmtToString Format_Month    = show (fromEnum (dateMonth date)+1)
-        fmtToString Format_MonthName_Short = take 3 $ show (dateMonth date)
-        fmtToString Format_Day2     = pad2 (dateDay date)
-        fmtToString Format_Day      = show (dateDay date)
-        fmtToString Format_Hour     = pad2 (fromIntegral (todHour tm) :: Int)
-        fmtToString Format_Minute   = pad2 (fromIntegral (todMin tm) :: Int)
-        fmtToString Format_Second   = pad2 (fromIntegral (todSec tm) :: Int)
-        fmtToString Format_MilliSecond = padN 3 (ns `div` 1000000)
-        fmtToString Format_MicroSecond = padN 3 ((ns `div` 1000) `mod` 1000)
-        fmtToString Format_NanoSecond = padN 3 (ns `mod` 1000)
-        fmtToString (Format_Precision n)
-            | n >= 1 && n <= 9 = padN n (ns `div` (10 ^ (9 - n)))
-            | otherwise        = error "invalid precision format"
-        fmtToString Format_UnixSecond = show unixSecs
-        fmtToString Format_TimezoneName   = "" --
-        fmtToString Format_Tz_Offset = show tz
-        fmtToString Format_TzHM = show tzOfs
-        fmtToString Format_TzHM_Colon_Z
-            | tz == 0   = "Z"
-            | otherwise = fmtToString Format_TzHM_Colon
-        fmtToString Format_TzHM_Colon =
-            let (tzH, tzM) = abs tz `divMod` 60
-                sign = if tz < 0 then "-" else "+"
-             in sign ++ pad2 tzH ++ ":" ++ pad2 tzM
-        fmtToString Format_Spaces   = " "
-        fmtToString (Format_Text c) = [c]
-        fmtToString f = error ("implemented printing format: " ++ show f)
-
-        (TimeFormatString fmtElems) = toFormat fmt
-
-        (Elapsed (Seconds unixSecs)) = timeGetElapsed t
-        (DateTime date tm) = timeGetDateTimeOfDay t
-        (NanoSeconds ns) = timeGetNanoSeconds t
-
--- | Pretty print local time to a string.
---
--- The actual output is determined by the format used.
-localTimePrint :: (TimeFormat format, Timeable t)
-               => format      -- ^ the format to use for printing
-               -> LocalTime t -- ^ the local time to print
-               -> String      -- ^ the resulting local time string
-localTimePrint fmt lt = localTimeUnwrap $ fmap (printWith fmt (localTimeGetTimezone lt)) lt
-
--- | Pretty print time to a string
---
--- The actual output is determined by the format used
-timePrint :: (TimeFormat format, Timeable t)
-          => format -- ^ the format to use for printing
-          -> t      -- ^ the global time to print
-          -> String -- ^ the resulting string
-timePrint fmt t = printWith fmt timezone_UTC t
-
--- | Try parsing a string as time using the format explicitely specified
---
--- On failure, the parsing function returns the reason of the failure.
--- If parsing is successful, return the date parsed with the remaining unparsed string
-localTimeParseE :: TimeFormat format
-                => format -- ^ the format to use for parsing
-                -> String -- ^ the string to parse
-                -> Either (TimeFormatElem, String) (LocalTime DateTime, String)
-localTimeParseE fmt timeString = loop ini fmtElems timeString
-  where (TimeFormatString fmtElems) = toFormat fmt
-
-        toLocal (dt, tz) = localTime tz dt
-
-        loop acc []    s  = Right (toLocal acc, s)
-        loop _   (x:_) [] = Left (x, "empty")
-        loop acc (x:xs) s =
-            case processOne acc x s of
-                Left err         -> Left (x, err)
-                Right (nacc, s') -> loop nacc xs s'
-
-        processOne _   _               []     = Left "empty"
-        processOne acc (Format_Text c) (x:xs)
-            | c == x    = Right (acc, xs)
-            | otherwise = Left ("unexpected char, got: " ++ show c)
-
-        processOne acc Format_Year s =
-            onSuccess (\y -> modDate (setYear y) acc) $ isNumber s
-        processOne acc Format_Year4 s =
-            onSuccess (\y -> modDate (setYear y) acc) $ getNDigitNum 4 s
-        processOne acc Format_Year2 s = onSuccess
-            (\y -> let year = if y < 70 then y + 2000 else y + 1900 in modDate (setYear year) acc)
-            $ getNDigitNum 2 s
-        processOne acc Format_Month2 s =
-            onSuccess (\m -> modDate (setMonth $ toEnum ((fromIntegral m - 1) `mod` 12)) acc) $ getNDigitNum 2 s
-        processOne acc Format_MonthName_Short s =
-            onSuccess (\m -> modDate (setMonth m) acc) $ getMonth s
-        processOne acc Format_Day2 s =
-            onSuccess (\d -> modDate (setDay d) acc) $ getNDigitNum 2 s
-        processOne acc Format_Hour s =
-            onSuccess (\h -> modTime (setHour h) acc) $ getNDigitNum 2 s
-        processOne acc Format_Minute s =
-            onSuccess (\mi -> modTime (setMin mi) acc) $ getNDigitNum 2 s
-        processOne acc Format_Second s =
-            onSuccess (\sec -> modTime (setSec sec) acc) $ getNDigitNum 2 s
-        processOne acc Format_MilliSecond s =
-            onSuccess (\ms -> modTime (setNsMask (6,3) ms) acc) $ getNDigitNum 3 s
-        processOne acc Format_MicroSecond s =
-            onSuccess (\us -> modTime (setNsMask (3,3) us) acc) $ getNDigitNum 3 s
-        processOne acc Format_NanoSecond s =
-            onSuccess (\ns -> modTime (setNsMask (0,3) ns) acc) $ getNDigitNum 3 s
-        processOne acc (Format_Precision p) s =
-            onSuccess (\num -> modTime (setNS num) acc) $ getNDigitNum p s
-        processOne acc Format_UnixSecond s =
-            onSuccess (\sec ->
-                let newDate = dateTimeFromUnixEpochP $ flip ElapsedP 0 $ Elapsed $ Seconds sec
-                 in modDT (const newDate) acc) $ isNumber s
-        processOne acc Format_TzHM_Colon_Z a@(c:s)
-            | c == 'Z'  = Right (acc, s)
-            | otherwise = processOne acc Format_TzHM_Colon a
-        processOne acc Format_TzHM_Colon (c:s) =
-            parseHMSign True acc c s
-        processOne acc Format_TzHM (c:s) =
-            parseHMSign False acc c s
-
-        processOne acc Format_Spaces (' ':s) = Right (acc, s)
-        -- catch all for unimplemented format.
-        processOne _ f _ = error ("unimplemened parsing format: " ++ show f)
-
-        parseHMSign expectColon acc signChar afterSign =
-            case signChar of
-                '+' -> parseHM False expectColon afterSign acc
-                '-' -> parseHM True expectColon afterSign acc
-                _   -> parseHM False expectColon (signChar:afterSign) acc
-
-        parseHM isNeg True (h1:h2:':':m1:m2:xs) acc
-            | allDigits [h1,h2,m1,m2] = let tz = toTZ isNeg h1 h2 m1 m2
-                                         in Right (modTZ (const tz) acc, xs)
-            | otherwise               = Left ("not digits chars: " ++ show [h1,h2,m1,m2])
-        parseHM isNeg False (h1:h2:m1:m2:xs) acc
-            | allDigits [h1,h2,m1,m2] = let tz = toTZ isNeg h1 h2 m1 m2
-                                         in Right (modTZ (const tz) acc, xs)
-            | otherwise               = Left ("not digits chars: " ++ show [h1,h2,m1,m2])
-        parseHM _ _    _ _ = Left "invalid timezone format"
-
-        toTZ isNeg h1 h2 m1 m2 = TimezoneOffset ((if isNeg then negate else id) minutes)
-          where minutes = (toInt [h1,h2] * 60) + toInt [m1,m2]
-
-        onSuccess f (Right (v, s')) = Right (f v, s')
-        onSuccess _ (Left s)        = Left s
-
-        isNumber :: Num a => String -> Either String (a, String)
-        isNumber s =
-            case span isDigit s of
-                ("",s2) -> Left ("no digits chars:" ++ s2)
-                (s1,s2) -> Right (toInt s1, s2)
-
-        getNDigitNum :: Int -> String -> Either String (Int64, String)
-        getNDigitNum n s =
-            case getNChar n s of
-                Left err                            -> Left err
-                Right (s1, s2) | not (allDigits s1) -> Left ("not a digit chars in " ++ show s1)
-                               | otherwise          -> Right (toInt s1, s2)
-
-        getMonth :: String -> Either String (Month, String)
-        getMonth s =
-            getNChar 3 s >>= \(s1, s2) -> monthFromShort s1 >>= \m -> Right (m, s2)
-
-        getNChar :: Int -> String -> Either String (String, String)
-        getNChar n s
-            | length s1 < n = Left ("not enough chars: expecting " ++ show n ++ " got " ++ show s1)
-            | otherwise     = Right (s1, s2)
-          where
-                (s1, s2) = splitAt n s
-
-        toInt :: Num a => String -> a
-        toInt = foldl (\acc w -> acc * 10 + fromIntegral (ord w - ord '0')) 0
-
-        allDigits = and . map isDigit
-
-        ini = (DateTime (Date 0 (toEnum 0) 0) (TimeOfDay 0 0 0 0), TimezoneOffset 0)
-
-        modDT   f (dt, tz) = (f dt, tz)
-        modDate f (DateTime d tp, tz) = (DateTime (f d) tp, tz)
-        modTime f (DateTime d tp, tz) = (DateTime d (f tp), tz)
-        modTZ   f (dt, tz) = (dt, f tz)
-
-        setYear :: Int64 -> Date -> Date
-        setYear  y (Date _ m d) = Date (fromIntegral y) m d
-        setMonth m (Date y _ d) = Date y m d
-        setDay   d (Date y m _) = Date y m (fromIntegral d)
-        setHour  h (TimeOfDay _ m s ns) = TimeOfDay (Hours h) m s ns
-        setMin   m (TimeOfDay h _ s ns) = TimeOfDay h (Minutes m) s ns
-        setSec   s (TimeOfDay h m _ ns) = TimeOfDay h m (Seconds s) ns
-        setNS    v (TimeOfDay h m s _ ) = TimeOfDay h m s (NanoSeconds v)
-
-        setNsMask :: (Int, Int) -> Int64 -> TimeOfDay -> TimeOfDay
-        setNsMask (shift, mask) val (TimeOfDay h mins seconds (NanoSeconds ns)) =
-            let (nsD,keepL) = ns `divMod` s
-                (keepH,_)   = nsD `divMod` m
-                v           = ((keepH * m + fromIntegral val) * s) + keepL
-             in TimeOfDay h mins seconds (NanoSeconds v)
-          where s = 10 ^ shift
-                m = 10 ^ mask
--- | Try parsing a string as time using the format explicitely specified
---
--- Unparsed characters are ignored and the error handling is simplified
---
--- for more elaborate need use 'localTimeParseE'.
-localTimeParse :: TimeFormat format
-               => format -- ^ the format to use for parsing
-               -> String -- ^ the string to parse
-               -> Maybe (LocalTime DateTime)
-localTimeParse fmt s = either (const Nothing) (Just . fst) $ localTimeParseE fmt s
-
--- | like 'localTimeParseE' but the time value is automatically converted to global time.
-timeParseE :: TimeFormat format => format -> String
-           -> Either (TimeFormatElem, String) (DateTime, String)
-timeParseE fmt timeString = either Left (\(d,s) -> Right (localTimeToGlobal d, s))
-                          $ localTimeParseE fmt timeString
-
--- | Just like 'localTimeParse' but the time is automatically converted to global time.
-timeParse :: TimeFormat format => format -> String -> Maybe DateTime
-timeParse fmt s = localTimeToGlobal `fmap` localTimeParse fmt s
− Data/Hourglass/Internal.hs
@@ -1,23 +0,0 @@--- |
--- Module      : Data.Hourglass.Internal
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- System lowlevel functions
---
-{-# LANGUAGE CPP #-}
-module Data.Hourglass.Internal
-    ( dateTimeFromUnixEpochP
-    , dateTimeFromUnixEpoch
-    , systemGetTimezone
-    , systemGetElapsed
-    , systemGetElapsedP
-    ) where
-
-#ifdef WINDOWS
-import Data.Hourglass.Internal.Win
-#else
-import Data.Hourglass.Internal.Unix
-#endif
− Data/Hourglass/Internal/Unix.hs
@@ -1,168 +0,0 @@--- |
--- Module      : Data.Hourglass.Internal.Unix
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Time lowlevel helpers for the unix operating system
---
--- depend on localtime_r and gmtime_r.
--- Some obscure unix system might not support them.
---
-{-# LANGUAGE ForeignFunctionInterface #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE EmptyDataDecls #-}
-module Data.Hourglass.Internal.Unix
-    ( dateTimeFromUnixEpochP
-    , dateTimeFromUnixEpoch
-    , systemGetTimezone
-    , systemGetElapsed
-    , systemGetElapsedP
-    ) where
-
-import Control.Applicative
-import Foreign.C.Types
-import Foreign.Storable
-import Foreign.Marshal.Alloc
-import Foreign.Ptr
-import Data.Hourglass.Types
-import System.IO.Unsafe
-
--- | convert a unix epoch precise to DateTime
-dateTimeFromUnixEpochP :: ElapsedP -> DateTime
-dateTimeFromUnixEpochP (ElapsedP e ns) = fromCP ns $ rawGmTime e
-
--- | convert a unix epoch to DateTime
-dateTimeFromUnixEpoch :: Elapsed -> DateTime
-dateTimeFromUnixEpoch e = fromC $ rawGmTime e
-
--- | return the timezone offset in minutes
-systemGetTimezone :: IO TimezoneOffset
-systemGetTimezone = TimezoneOffset . fromIntegral . flip div 60 <$> localTime 0
-
-----------------------------------------------------------------------------------------
--- | return the current elapsedP
-systemGetElapsedP :: IO ElapsedP
-systemGetElapsedP = allocaBytesAligned sofTimespec 8 $ \ptr -> do
-    c_clock_get ptr
-    toElapsedP <$> peek (castPtr ptr) <*> peekByteOff (castPtr ptr) sofCTime
-  where sofTimespec = sofCTime + sofCLong
-        sofCTime = sizeOf (0 :: CTime)
-        sofCLong = sizeOf (0 :: CLong)
-#if (MIN_VERSION_base(4,5,0))
-        toElapsedP :: CTime -> CLong -> ElapsedP
-        toElapsedP (CTime sec) nsec = ElapsedP (Elapsed $ Seconds (fromIntegral sec)) (fromIntegral nsec)
-#else
-        toElapsedP :: CLong -> CLong -> ElapsedP
-        toElapsedP sec         nsec = ElapsedP (Elapsed $ Seconds (fromIntegral sec)) (fromIntegral nsec)
-#endif
-
--- | return the current elapsed
-systemGetElapsed :: IO Elapsed
-systemGetElapsed = allocaBytesAligned sofTimespec 8 $ \ptr -> do
-    c_clock_get ptr
-    toElapsed <$> peek (castPtr ptr)
-  where sofTimespec = sizeOf (0 :: CTime) + sizeOf (0 :: CLong)
-#if (MIN_VERSION_base(4,5,0))
-        toElapsed :: CTime -> Elapsed
-        toElapsed (CTime sec) = Elapsed $ Seconds (fromIntegral sec)
-#else
-        toElapsed :: CLong -> Elapsed
-        toElapsed sec         = Elapsed $ Seconds (fromIntegral sec)
-#endif
-
-foreign import ccall unsafe "hourglass_clock_calendar"
-    c_clock_get :: Ptr CLong -> IO ()
-
-#if (MIN_VERSION_base(4,5,0))
-foreign import ccall unsafe "gmtime_r"
-    c_gmtime_r :: Ptr CTime -> Ptr CTm -> IO (Ptr CTm)
-
-foreign import ccall unsafe "localtime_r"
-    c_localtime_r :: Ptr CTime -> Ptr CTm -> IO (Ptr CTm)
-#else
-foreign import ccall unsafe "gmtime_r"
-    c_gmtime_r :: Ptr CLong -> Ptr CTm -> IO (Ptr CTm)
-
-foreign import ccall unsafe "localtime_r"
-    c_localtime_r :: Ptr CLong -> Ptr CTm -> IO (Ptr CTm)
-#endif
-
--- | Return a global time's struct tm based on the number of elapsed second since unix epoch.
-rawGmTime :: Elapsed -> CTm
-rawGmTime (Elapsed (Seconds s)) = unsafePerformIO callTime
-  where callTime =
-            alloca $ \ctmPtr -> do
-            alloca $ \ctimePtr -> do
-                poke ctimePtr ctime
-                r <- c_gmtime_r ctimePtr ctmPtr
-                if r == nullPtr
-                    then error "gmTime failed"
-                    else peek ctmPtr
-        ctime = fromIntegral s
-{-# NOINLINE rawGmTime #-}
-
--- | Return a local time's gmtoff (seconds east of UTC)
---
--- use the ill defined gmtoff (at offset 40) that might or might not be
--- available for your platform. worst case scenario it's not initialized
--- properly.
-localTime :: Elapsed -> IO CLong
-localTime (Elapsed (Seconds s)) = callTime
-  where callTime =
-            alloca $ \ctmPtr -> do
-            alloca $ \ctimePtr -> do
-                poke ctimePtr ctime
-                r <- c_localtime_r ctimePtr ctmPtr
-                if r == nullPtr
-                    then error "localTime failed"
-                    else peekByteOff ctmPtr 40
-        ctime = fromIntegral s
-
--- | Represent the beginning of struct tm
-data CTm = CTm
-    { ctmSec    :: CInt
-    , ctmMin    :: CInt
-    , ctmHour   :: CInt
-    , ctmMDay   :: CInt
-    , ctmMon    :: CInt
-    , ctmYear   :: CInt
-    } deriving (Show,Eq)
-
--- | Convert a C structure to a DateTime structure
-fromC :: CTm -> DateTime
-fromC ctm = DateTime date time
-  where date = Date
-            { dateYear  = fromIntegral $ ctmYear ctm + 1900
-            , dateMonth = toEnum $ fromIntegral $ ctmMon ctm
-            , dateDay   = fromIntegral $ ctmMDay ctm
-            }
-        time = TimeOfDay
-            { todHour = fromIntegral $ ctmHour ctm
-            , todMin  = fromIntegral $ ctmMin ctm
-            , todSec  = fromIntegral $ ctmSec ctm
-            , todNSec = 0
-            }
-
--- | Similar to 'fromC' except with nanosecond precision
-fromCP :: NanoSeconds -> CTm -> DateTime
-fromCP ns ctm = DateTime d (t { todNSec = ns })
-  where (DateTime d t) = fromC ctm
-
-instance Storable CTm where
-    alignment _ = 8
-    sizeOf _    = 60 -- account for 9 ints, alignment + 2 unsigned long at end.
-    peek ptr    = do
-        CTm <$> peekByteOff intPtr 0
-            <*> peekByteOff intPtr 4
-            <*> peekByteOff intPtr 8
-            <*> peekByteOff intPtr 12
-            <*> peekByteOff intPtr 16
-            <*> peekByteOff intPtr 20
-      where intPtr = castPtr ptr
-    poke ptr (CTm f0 f1 f2 f3 f4 f5) = do
-        mapM_ (uncurry (pokeByteOff intPtr))
-            [(0,f0), (4,f1), (8,f2), (12,f3), (16,f4), (20,f5)]
-        --pokeByteOff (castPtr ptr) 36 f9
-      where intPtr = castPtr ptr
− Data/Hourglass/Internal/Win.hs
@@ -1,79 +0,0 @@--- |
--- Module      : Data.Hourglass.Internal.Win
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Time lowlevel helpers binding to Windows
---
-{-# LANGUAGE ForeignFunctionInterface #-}
-{-# LANGUAGE CPP #-}
-module Data.Hourglass.Internal.Win
-    ( dateTimeFromUnixEpochP
-    , dateTimeFromUnixEpoch
-    , systemGetTimezone
-    , systemGetElapsed
-    , systemGetElapsedP
-    ) where
-
-import System.IO.Unsafe
-import System.Win32.Time
-import Data.Hourglass.Types
-import Data.Int (Int64)
-
-unixDiff :: Int64
-unixDiff = 11644473600
-
-toFileTime :: Elapsed -> FILETIME
-toFileTime (Elapsed (Seconds s)) = FILETIME val
-  where val = fromIntegral (s + unixDiff) * 10000000
-
-toElapsedP :: FILETIME -> ElapsedP
-toElapsedP (FILETIME w) = ElapsedP (Elapsed $ Seconds s) (NanoSeconds ns)
-  where (sWin, hundredNs) = w `divMod` 10000000
-        ns = fromIntegral (hundredNs * 100)
-        s = fromIntegral sWin - unixDiff
-
-toElapsed :: FILETIME -> Elapsed
-toElapsed (FILETIME w) = Elapsed (Seconds s)
-  where s = fromIntegral (fst (w `divMod` 10000000)) - unixDiff
-
-callSystemTime :: Elapsed -> SYSTEMTIME
-callSystemTime e = unsafePerformIO (fileTimeToSystemTime (toFileTime e))
-{-# NOINLINE callSystemTime #-}
-
-dateTimeFromUnixEpochP :: ElapsedP -> DateTime
-dateTimeFromUnixEpochP (ElapsedP e ns) = toDateTime $ callSystemTime e
-  where toDateTime (SYSTEMTIME wY wM _ wD wH wMin wS _) =
-            DateTime (Date (fi wY) (toEnum $ fi $ wM - 1) (fi wD))
-                     (TimeOfDay (fi wH) (fi wMin) (fi wS) ns)
-        fi :: (Integral a, Num b) => a -> b
-        fi = fromIntegral
-
-dateTimeFromUnixEpoch :: Elapsed -> DateTime
-dateTimeFromUnixEpoch e = toDateTime $ callSystemTime e
-  where toDateTime (SYSTEMTIME wY wM _ wD wH wMin wS _) =
-            DateTime (Date (fi wY) (toEnum $ fi $ wM - 1) (fi wD))
-                     (TimeOfDay (fi wH) (fi wMin) (fi wS) 0)
-        fi :: (Integral a, Num b) => a -> b
-        fi = fromIntegral
-
-systemGetTimezone :: IO TimezoneOffset
-systemGetTimezone = do
-    (tzMode,tzInfo) <- getTimeZoneInformation
-    case tzMode of
-        TzIdDaylight -> return $ toTO (tziBias tzInfo + tziDaylightBias tzInfo)
-        TzIdStandard -> return $ toTO (tziBias tzInfo + tziStandardBias tzInfo)
-        TzIdUnknown  -> return $ toTO (tziBias tzInfo)
-  where
-    -- a negative value represent value how to go from local to UTC,
-    -- whereas TimezoneOffset represent the offset to go from UTC to local.
-    -- here we negate the bias to get the proper value represented.
-    toTO = TimezoneOffset . fromIntegral . negate
-
-systemGetElapsedP :: IO ElapsedP
-systemGetElapsedP = toElapsedP `fmap` getSystemTimeAsFileTime
-
-systemGetElapsed :: IO Elapsed
-systemGetElapsed = toElapsed `fmap` getSystemTimeAsFileTime
− Data/Hourglass/Local.hs
@@ -1,86 +0,0 @@--- |
--- Module      : Data.Hourglass.Local
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Local time = global time + timezone
---
-{-# LANGUAGE FlexibleInstances #-}
-module Data.Hourglass.Local
-    (
-    -- * Local time
-    -- ** Local time type
-      LocalTime
-    -- ** Local time creation and manipulation
-    , localTime
-    , localTimeUnwrap
-    , localTimeToGlobal
-    , localTimeFromGlobal
-    , localTimeGetTimezone
-    , localTimeSetTimezone
-    , localTimeConvert
-    ) where
-
-import Data.Hourglass.Types
-import Data.Hourglass.Time
-import Data.Hourglass.Diff
-
--- | Local time representation
---
--- this is a time representation augmented by a timezone
--- to get back to a global time, the timezoneOffset needed to be added to the local time.
---
-data LocalTime t = LocalTime
-    { localTimeUnwrap      :: t              -- ^ unwrap the LocalTime value. the time value is local.
-    , localTimeGetTimezone :: TimezoneOffset -- ^ get the timezone associated with LocalTime
-    }
-
--- FIXME add instance Read too.
-
-instance Show t => Show (LocalTime t) where
-    show (LocalTime t tz) = show t ++ show tz
-instance Eq t => Eq (LocalTime t) where
-    LocalTime t1 tz1 == LocalTime t2 tz2 = tz1 == tz2 && t1 == t2
-
-instance (Ord t, Time t) => Ord (LocalTime t) where
-    compare l1@(LocalTime g1 tz1) l2@(LocalTime g2 tz2) =
-        case compare tz1 tz2 of
-            EQ -> compare g1 g2
-            _  -> let t1 = localTimeToGlobal l1
-                      t2 = localTimeToGlobal l2
-                   in compare t1 t2
-
-instance Functor LocalTime where
-    fmap f (LocalTime t tz) = LocalTime (f t) tz
-
--- | Create a local time type from a timezone and a time type.
---
--- The time value is assumed to be local to the timezone offset set,
--- so no transformation is done.
-localTime :: Time t => TimezoneOffset -> t -> LocalTime t
-localTime tz t = LocalTime t tz
-
--- | Get back a global time value
-localTimeToGlobal :: Time t => LocalTime t -> t
-localTimeToGlobal (LocalTime local tz)
-    | tz == TimezoneOffset 0 = local
-    | otherwise              = timeConvert $ elapsedTimeAddSecondsP (timeGetElapsedP local) tzSecs
-  where tzSecs = negate $ timezoneOffsetToSeconds tz
-
--- | create a local time value from a global one
-localTimeFromGlobal :: Time t => t -> LocalTime t
-localTimeFromGlobal = localTime (TimezoneOffset 0)
-
--- | Change the timezone, and adjust the local value to represent the new local value.
-localTimeSetTimezone :: Time t => TimezoneOffset -> LocalTime t -> LocalTime t
-localTimeSetTimezone tz currentLocal@(LocalTime t currentTz)
-    | diffTz == 0 = currentLocal
-    | otherwise   = LocalTime (timeConvert t') tz
-  where t'        = elapsedTimeAddSecondsP (timeGetElapsedP t) diffTz
-        diffTz    = timezoneOffsetToSeconds tz - timezoneOffsetToSeconds currentTz
-
--- | convert the local time representation to another time representation determined by context.
-localTimeConvert :: (Time t1, Time t2) => LocalTime t1 -> LocalTime t2
-localTimeConvert = fmap timeConvert
− Data/Hourglass/Time.hs
@@ -1,197 +0,0 @@--- |
--- Module      : Data.Hourglass.Time
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- generic time representation interface to allow
--- arbitrary conversion between different time representation
---
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE ExistentialQuantification #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-module Data.Hourglass.Time
-    (
-    -- * Generic time classes
-      Time(..)
-    , Timeable(..)
-
-    -- * Elapsed time
-    , Elapsed(..)
-    , ElapsedP(..)
-
-    -- * Generic conversion
-    , timeConvert
-
-    -- * Date and Time
-    , timeGetDate
-    , timeGetDateTimeOfDay
-    , timeGetTimeOfDay
-
-    -- * Arithmetic
-    , Duration(..)
-    , Period(..)
-    , TimeInterval(..)
-    , timeAdd
-    , timeDiff
-    , timeDiffP
-    , dateAddPeriod
-    ) where
-
-import Data.Data ()
-import Data.Hourglass.Types
-import Data.Hourglass.Calendar
-import Data.Hourglass.Diff
-import Foreign.C.Types (CTime(..))
-
--- | Timeable represent every type that can be made to look like time types.
---
--- * can be converted to ElapsedP and Elapsed
---
--- * optionally have a timezone associated
---
--- * have nanoseconds accessor (which can return 0 when the type is not more precise than seconds)
---
-class Timeable t where
-    -- | convert a time representation to the number of elapsed seconds and nanoseconds to a specific epoch
-    timeGetElapsedP :: t -> ElapsedP
-
-    -- | convert a time representation to the number of elapsed seconds to a specific epoch.
-    -- 
-    -- defaults to timeGetElapsedP unless defined explicitely by an instance
-    timeGetElapsed :: t -> Elapsed
-    timeGetElapsed t = e where ElapsedP e _ = timeGetElapsedP t
-
-    -- | return the number of optional nanoseconds.
-    --
-    -- If the underlaying type is not precise enough to record nanoseconds
-    -- (or any variant between seconds and nanoseconds), 0 should be returned
-    --
-    -- defaults to 'timeGetElapsedP' unless defined explicitely by an instance,
-    -- for efficiency reason, it's a good idea to override this methods if
-    -- you know the type is not more precise than Seconds.
-    timeGetNanoSeconds :: t -> NanoSeconds
-    timeGetNanoSeconds t = ns where ElapsedP _ ns = timeGetElapsedP t
-
--- | Represent time types that can be created from other time types.
---
--- Every conversion happens throught ElapsedP or Elapsed types.
-class Timeable t => Time t where
-    -- | convert from a number of elapsed seconds and nanoseconds to another time representation
-    timeFromElapsedP :: ElapsedP -> t
-
-    -- | convert from a number of elapsed seconds and nanoseconds to another time representation
-    --
-    -- defaults to timeFromElapsedP unless defined explicitely by an instance.
-    timeFromElapsed :: Elapsed -> t
-    timeFromElapsed e = timeFromElapsedP (ElapsedP e 0)
-
-#if (MIN_VERSION_base(4,5,0))
-instance Timeable CTime where
-    timeGetElapsedP c         = ElapsedP (timeGetElapsed c) 0
-    timeGetElapsed  (CTime c) = Elapsed (Seconds $ fromIntegral c)
-    timeGetNanoSeconds _ = 0
-instance Time CTime where
-    timeFromElapsedP (ElapsedP e _)       = timeFromElapsed e
-    timeFromElapsed (Elapsed (Seconds c)) = CTime (fromIntegral c)
-#endif
-
-instance Timeable Elapsed where
-    timeGetElapsedP  e = ElapsedP e 0
-    timeGetElapsed   e = e
-    timeGetNanoSeconds _ = 0
-instance Time Elapsed where
-    timeFromElapsedP (ElapsedP e _) = e
-    timeFromElapsed  e = e
-
-instance Timeable ElapsedP where
-    timeGetElapsedP    e               = e
-    timeGetNanoSeconds (ElapsedP _ ns) = ns
-instance Time ElapsedP where
-    timeFromElapsedP   e               = e
-
-instance Timeable Date where
-    timeGetElapsedP d  = timeGetElapsedP (DateTime d (TimeOfDay 0 0 0 0))
-instance Time Date where
-    timeFromElapsedP (ElapsedP elapsed _) = d
-      where (DateTime d _) = dateTimeFromUnixEpoch elapsed
-
-instance Timeable DateTime where
-    timeGetElapsedP d = ElapsedP (dateTimeToUnixEpoch d) (timeGetNanoSeconds d)
-    timeGetElapsed d  = dateTimeToUnixEpoch d
-    timeGetNanoSeconds (DateTime _ (TimeOfDay _ _ _ ns)) = ns
-instance Time DateTime where
-    timeFromElapsedP elapsed = dateTimeFromUnixEpochP elapsed
-
--- | Convert one time representation into another one
--- 
--- The return type need to be infer by the context.
---
--- If the context cannot be infer through this, some specialized functions
--- are available for built-in types:
---
--- * 'timeGetDate'
---
--- * 'timeGetDateTimeOfDay'
---
--- * 'timeGetElapsed', 'timeGetElapsedP'
-timeConvert :: (Timeable t1, Time t2) => t1 -> t2
-timeConvert t1 = timeFromElapsedP (timeGetElapsedP t1)
-{-# INLINE[2] timeConvert #-}
-{-# RULES "timeConvert/ID" timeConvert = id #-}
-{-# RULES "timeConvert/ElapsedP" timeConvert = timeGetElapsedP #-}
-{-# RULES "timeConvert/Elapsed" timeConvert = timeGetElapsed #-}
-
--- | Get the calendar Date (year-month-day) from a time representation
---
--- specialization of 'timeConvert'
-timeGetDate :: Timeable t => t -> Date
-timeGetDate t = d where (DateTime d _) = timeGetDateTimeOfDay t
-{-# INLINE[2] timeGetDate #-}
-{-# RULES "timeGetDate/ID" timeGetDate = id #-}
-{-# RULES "timeGetDate/DateTime" timeGetDate = dtDate #-}
-
--- | Get the day time (hours:minutes:seconds) from a time representation
---
--- specialization of 'timeConvert'
-timeGetTimeOfDay :: Timeable t => t -> TimeOfDay
-timeGetTimeOfDay t = tod where (DateTime _ tod) = timeGetDateTimeOfDay t
-{-# INLINE[2] timeGetTimeOfDay #-}
-{-# RULES "timeGetTimeOfDay/Date" timeGetTimeOfDay = const (TimeOfDay 0 0 0 0) #-}
-{-# RULES "timeGetTimeOfDay/DateTime" timeGetTimeOfDay = dtTime #-}
-
--- | Get the date and time of day from a time representation
---
--- specialization of 'timeConvert'
-timeGetDateTimeOfDay :: Timeable t => t -> DateTime
-timeGetDateTimeOfDay t = dateTimeFromUnixEpochP $ timeGetElapsedP t
-{-# INLINE[2] timeGetDateTimeOfDay #-}
-{-# RULES "timeGetDateTimeOfDay/ID" timeGetDateTimeOfDay = id #-}
-{-# RULES "timeGetDateTimeOfDay/Date" timeGetDateTimeOfDay = flip DateTime (TimeOfDay 0 0 0 0) #-}
-
--- | add some time interval to a time representation and returns this new time representation
---
--- example:
---
--- > t1 `timeAdd` mempty { durationHours = 12 }
-timeAdd :: (Time t, TimeInterval ti) => t -> ti -> t
-timeAdd t ti = timeFromElapsedP $ elapsedTimeAddSecondsP (timeGetElapsedP t) (toSeconds ti)
-
--- | Get the difference in seconds between two time representation
---
--- effectively:
---
--- > t2 `timeDiff` t1 = t2 - t1
-timeDiff :: (Timeable t1, Timeable t2) => t1 -> t2 -> Seconds
-timeDiff t1 t2 = sec where (Elapsed sec) = timeGetElapsed t1 - timeGetElapsed t2
-
--- | Get the difference in seconds and nanoseconds between two time representation
---
--- effectively:
---
--- > @t2 `timeDiffP` t1 = t2 - t1
-timeDiffP :: (Timeable t1, Timeable t2) => t1 -> t2 -> (Seconds, NanoSeconds)
-timeDiffP t1 t2 = (sec, ns)
-  where (ElapsedP (Elapsed sec) ns) = timeGetElapsedP t1 - timeGetElapsedP t2
− Data/Hourglass/Types.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE DeriveDataTypeable #-}
--- |
--- Module      : Data.Hourglass.Types
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
---
--- Basic times units and types.
---
--- While pratically some units could hold infinite values, for practical
--- and efficient purpose they are limited to int64 types for seconds
--- and int types for years.
---
--- Most units use the unix epoch referential, but by no means reduce portability.
--- the unix referential works under the Windows platform or any other platforms.
---
--- This module will be depreciated in favor of Time.Types
---
-module Data.Hourglass.Types
-    ( module Time.Types
-    ) where
-
-import Time.Types
− Data/Hourglass/Utils.hs
@@ -1,33 +0,0 @@--- |
--- Module      : Data.Hourglass.Utils
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- some padding / formatting functions
---
-module Data.Hourglass.Utils where
-
--- | pad a number to 2 digits
-pad2 :: (Show a, Ord a, Num a, Integral a) => a -> String
-pad2 v | v >= 100   = pad2 (v `mod` 100)
-       | v >= 10    = show v
-       | otherwise  = '0' : show v
-
--- | pad a number to 4 digits
-pad4 :: (Show a, Ord a, Num a, Integral a) => a -> String
-pad4 v | v >= 1000  = show v
-       | v >= 100   = '0' : show v
-       | v >= 10    = '0':'0' : show v
-       | otherwise  = '0':'0':'0': show v
-
--- | Pad a number to at least N digits.
---
--- if the number is greater, no truncation happens.
-padN :: (Show a, Ord a, Num a, Integral a) => Int -> a -> String
-padN n v
-    | vlen >= n = vs
-    | otherwise = replicate (n - vlen) '0' ++ vs
-  where vs = show v
-        vlen = length vs
− Data/Hourglass/Zone.hs
@@ -1,52 +0,0 @@--- |
--- Module      : Data.Hourglass.Zone
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Timezone utility
---
-{-# LANGUAGE ExistentialQuantification #-}
-module Data.Hourglass.Zone
-    ( Timezone(..)
-    , UTC(..)
-    , TimezoneMinutes(..)
-    ) where
-
--- | standard representation for timezone
-class Timezone tz where
-    -- | offset in minutes from UTC. valid values should be between -12*60 to +14*60
-    timezoneOffset :: tz -> Int
-    -- | the name of the timezone. by default will be +-HH:MM encoding.
-    timezoneName :: tz -> String
-    timezoneName = tzMinutesPrint . timezoneOffset
-
--- | Simple timezone containing the number of minutes difference
--- with UTC.
---
--- Valid values should be between -12*60 to +14*60
-newtype TimezoneMinutes = TimezoneMinutes Int
-    deriving (Show,Eq,Ord)
-
--- | Universal Time Coordinated. The generic computer "timezone".
-data UTC = UTC
-    deriving (Show,Eq,Ord)
-
-instance Timezone UTC where
-    timezoneOffset _ = 0
-    timezoneName _   = "UTC"
-
-instance Timezone TimezoneMinutes where
-    timezoneOffset (TimezoneMinutes minutes) = minutes
-
--- | print a minute offset in format:
--- (+-)HH:MM
-tzMinutesPrint :: Int -> String
-tzMinutesPrint offset =
-      (if offset > 0 then '+' else '-')
-    : (pad0 h ++ ":" ++ pad0 m)
-  where (h,m)  = abs offset `divMod` 60
-        pad0 v
-            | v < 10    = '0':show v
-            | otherwise = show v
README.md view
@@ -8,22 +8,38 @@ Originally forked from
 [`hourglass-0.2.12`](https://hackage.haskell.org/package/hourglass-0.2.12).
 
-Hourglass is a simple time library.
+`time-hourglass` (originally `hourglass`) is a simple and efficient time
+library.
 
-Documentation: [time-hourglass on hackage](http://hackage.haskell.org/package/time-hourglass)
+Documentation
+-------------
 
+See the Haddock documentation on [Hackage](http://hackage.haskell.org/package/time-hourglass).
+
 Design
 ------
-Key parts of the design are the Timeable and Time typeclasses.
-Time representations of the same time values are interchangeable and easy to convert
-between each other. This also allows the user to define new time types that
-interact with the same functions as the built-in types.
 
+A key part of the design is the `Timeable` and `Time` type classes. Types
+representing time values that are instances of these classes allow easy
+conversion between values of one time type and another.
+
 For example:
-```haskell
+
+~~~haskell
 let dateTime0 =
-      DateTime { dtDate = Date { dateYear = 1970, dateMonth = January, dateDay = 1 }
-               , dtTime = TimeOfDay {todHour = 0, todMin = 0, todSec = 0, todNSec = 0 }}
+      DateTime
+        { dtDate = Date
+            { dateYear = 1970
+            , dateMonth = January
+            , dateDay = 1
+            }
+        , dtTime = TimeOfDay
+            { todHour = 0
+            , todMin = 0
+            , todSec = 0
+            , todNSec = 0
+            }
+        }
     elapsed0 = Elasped 0
 
 > timeGetElapsed elapsed0 == timeGetElapsed dateTime0
@@ -34,58 +50,64 @@ "1970-01"
 > timePrint "YYYY-MM" dateTime0
 "1970-01"
-```
+~~~
 
-Hourglass has the same limitations as your system:
+The library has the same limitations as your operating system, namely:
 
-* On 32 bit linux, you can't get a date after the year 2038.
-* In Windows 7, you can't get the date before the year 1601.
+* on 32-bit Linux, you can't get a date after the year 2038; and
+* on Windows, you can't get a date before the year 1601.
 
-Comparaison with time
----------------------
-* Getting posix time:
-```haskell
--- With time
-import Data.Time.Clock.POSIX
+Comparaison with the `time` package
+-----------------------------------
 
-ptime <- getPOSIXTime
+*   Getting the elapsed time since 1970-01-01 00:00:00 UTC (POSIX time) from the
+    system clock:
 
--- With hourglass
-import System.Hourglass
+    ~~~haskell
+    -- With time:
+    import Data.Time.Clock.POSIX ( getPOSIXTime )
 
-ptime <- timeCurrent
-```
+    ptime <- getPOSIXTime
 
-* Getting the current year:
-```haskell
--- With time
-import Data.Time.Clock
-import Data.Time.Calendar
+    -- With time-hourglass:
+    import System.Hourglass ( timeCurrent )
 
-currentYear <- (\(y,_,_) -> y) . toGregorian . utcDay <$> getCurrentTime
+    ptime <- timeCurrent
+    ~~~
 
--- With hourglass
-import System.Hourglass
-import Data.Time
+*   Getting the current year:
 
-currentYear <- dateYear . timeGetDate <$> timeCurrent
-```
+    ~~~haskell
+    -- With time:
+    import Data.Time.Clock ( UTCTime (..) )
+    import Data.Time.Clock.POSIX ( getCurrentTime )
+    import Data.Time.Calendar ( toGregorian )
 
-* Representating "4th May 1970 15:12:24"
-```haskell
--- With time
-import Data.Time.Clock
-import Date.Time.Calendar
+    currentYear <- (\(y, _, _) -> y) . toGregorian . utcDay <$> getCurrentTime
 
-let day = fromGregorian 1970 5 4
-    diffTime = secondsToDiffTime (15 * 3600 + 12 * 60 + 24)
-in UTCTime day diffTime
+    -- With time-hourglass:
+    import System.Hourglass ( timeCurrent )
+    import Data.Hourglass ( Date (..), timeGetDate )
 
--- With hourglass
-import Date.Time
+    currentYear <- dateYear . timeGetDate <$> timeCurrent
+    ~~~
 
-DateTime (Date 1970 May 4) (TimeOfDay 15 12 24 0)
-```
+*   Representating "4th May 1970 15:12:24"
+
+    ~~~haskell
+    -- With time:
+    import Data.Time.Clock ( UTCTime (..), secondsToDiffTime )
+    import Date.Time.Calendar ( fromGregorian )
+
+    let day = fromGregorian 1970 5 4
+        diffTime = secondsToDiffTime (15 * 3600 + 12 * 60 + 24)
+    in  UTCTime day diffTime
+
+    -- With time-hourglass:
+    import Date.Time ( Date (..), DateTime (..), TimeOfDay (..) )
+
+    DateTime (Date 1970 May 4) (TimeOfDay 15 12 24 0)
+    ~~~
 
 History
 -------
− System/Hourglass.hs
@@ -1,16 +0,0 @@--- |
--- Module      : System.Hourglass
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Get the system timezone and current time value in multiple formats
---
--- This module will be depreciated in favor of Time.System
---
-module System.Hourglass
-    ( module Time.System
-    ) where
-
-import Time.System
− Time/Compat.hs
@@ -1,104 +0,0 @@--- |
--- Module      : Time.Compat
--- License     : BSD-style
--- Maintainer  : Nicolas DI PRIMA <nicolas@di-prima.fr>
---
--- Basic Time conversion compatibility.
---
--- This module aims to help conversion between the types from the package
--- time to the package hourglass.
---
--- Example of use (extracted from file Example/Time/Compat.hs):
---
--- > import Data.Hourglass        as H
--- > import Data.Hourglass.Compat as C
--- > import Data.Time             as T
--- >
--- > transpose :: T.ZonedTime
--- >           -> H.LocalTime H.DateTime
--- > transpose oldTime =
--- >     H.localTime
--- >         offsetTime
--- >         (H.DateTime newDate timeofday)
--- >   where
--- >     newDate :: H.Date
--- >     newDate = C.dateFromTAIEpoch $ T.toModifiedJulianDay $ T.localDay $ T.zonedTimeToLocalTime oldTime
--- >
--- >     timeofday :: H.TimeOfDay
--- >     timeofday = C.diffTimeToTimeOfDay $ T.timeOfDayToTime $ T.localTimeOfDay $ T.zonedTimeToLocalTime oldTime
--- >
--- >     offsetTime = H.TimezoneOffset $ fromIntegral $ T.timeZoneMinutes $ T.zonedTimeZone oldTime
---
-module Time.Compat
-    ( dateFromPOSIXEpoch
-    , dateFromTAIEpoch
-    , diffTimeToTimeOfDay
-    ) where
-
-import Data.Hourglass
-
--- | Convert an integer which represent the Number of days (To/From) POSIX Epoch
--- to a Date (POSIX Epoch is 1970-01-01).
-dateFromPOSIXEpoch :: Integer -- ^ number of days since POSIX Epoch
-                   -> Date
-dateFromPOSIXEpoch day = do
-    let sec = Elapsed $ fromIntegral $ day * 86400
-    timeConvert sec
-
--- | Number of days between POSIX Epoch and TAI Epoch
--- (between 1858-11-17 and 1970-01-01)
-daysTAItoPOSIX :: Integer
-daysTAItoPOSIX = 40587
-
--- | Convert an integer which represents the Number of days (To/From) TAI Epoch
--- This function allows use of the package time to easily convert the Day into
--- the Hourglass Date representation (TAI Epoch is 1858-11-17).
--- 
--- This function allows user to easily convert a Data.Time.Calendar.Day into Date
---
--- > import qualified Data.Time.Calendar as T
--- >
--- > timeDay :: T.Day
--- >
--- > dateFromTAIEpoch $ T.toModifiedJulianDay timeDay
-dateFromTAIEpoch :: Integer -- ^ number of days since TAI Epoch
-                 -> Date
-dateFromTAIEpoch dtai =
-    dateFromPOSIXEpoch (dtai - daysTAItoPOSIX)
-
--- | Convert of differential of time of a day.
--- (it convers a Data.Time.Clock.DiffTime into a TimeOfDay)
---
--- Example with DiffTime type from time:
---
--- > import qualified Data.Time.Clock as T
--- >
--- > difftime :: T.DiffTime
--- >
--- > diffTimeToTimeOfDay difftime
---
--- Example with the TimeOfDay type from time:
---
--- > import qualified Data.Time.Clock as T
--- >
--- > timeofday :: T.TimeOfDay
--- >
--- > diffTimeToTimeOfDay $ T.timeOfDayToTime timeofday
-diffTimeToTimeOfDay :: Real t
-                    => t         -- ^ number of seconds of the time of the day
-                    -> TimeOfDay
-diffTimeToTimeOfDay dt = do
-    TimeOfDay
-        { todHour = fromIntegral hours
-        , todMin  = fromIntegral minutes
-        , todSec  = fromIntegral seconds
-        , todNSec = fromIntegral nsecs
-        }
-  where
-    r :: Rational
-    r = toRational dt
-    (secs, nR) = properFraction r :: (Integer, Rational)
-    nsecs :: Integer
-    nsecs = round (nR * 1000000000)
-    (minsofday, seconds) = secs `divMod` 60 :: (Integer, Integer)
-    (hours, minutes) = minsofday `divMod` 60 :: (Integer, Integer)
− Time/System.hs
@@ -1,58 +0,0 @@--- |
--- Module      : Time.System
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
--- Stability   : experimental
--- Portability : unknown
---
--- Get the system timezone and current time value in multiple formats
---
-module Time.System
-    (
-    -- * Current time in computer friendly format
-      timeCurrent
-    , timeCurrentP
-    -- * Current time in human friendly DateTime format
-    , dateCurrent
-    , localDateCurrent
-    , localDateCurrentAt
-    -- * System timezone
-    , timezoneCurrent
-    ) where
-
-import Control.Applicative
-import Time.Types
-import Data.Hourglass.Time
-import Data.Hourglass.Local
-import Data.Hourglass.Internal (systemGetElapsedP, systemGetElapsed, systemGetTimezone)
-
--- | Get the current elapsed seconds since epoch
-timeCurrent :: IO Elapsed
-timeCurrent = systemGetElapsed
-
--- | Get the current elapsed seconds (precise to the nanosecond) since epoch
-timeCurrentP :: IO ElapsedP
-timeCurrentP = systemGetElapsedP
-
--- | Get the current global date
---
--- This is equivalent to:
---
--- > timeGetDateTimeOfDay `fmap` timeCurrentP
-dateCurrent :: IO DateTime
-dateCurrent = timeGetDateTimeOfDay <$> timeCurrentP
-
--- | Get the localized date by using 'timezoneCurrent' and 'dateCurrent'
-localDateCurrent :: IO (LocalTime DateTime)
-localDateCurrent = localTimeSetTimezone <$> timezoneCurrent
-                                        <*> (localTimeFromGlobal <$> dateCurrent)
-
--- | Get the localized date at a specific timezone offset.
-localDateCurrentAt :: TimezoneOffset -> IO (LocalTime DateTime)
-localDateCurrentAt tz = localTimeSetTimezone tz . localTimeFromGlobal <$> dateCurrent
-
--- | Get the current timezone offset
---
--- This include daylight saving time when in operation.
-timezoneCurrent :: IO TimezoneOffset
-timezoneCurrent = systemGetTimezone
− Time/Types.hs
@@ -1,245 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE DeriveDataTypeable #-}
--- |
--- Module      : Time.Types
--- License     : BSD-style
--- Maintainer  : Vincent Hanquez <vincent@snarc.org>
---
--- Basic times units and types.
---
--- While pratically some units could hold infinite values, for practical
--- and efficient purpose they are limited to int64 types for seconds
--- and int types for years.
---
--- Most units use the unix epoch referential, but by no means reduce portability.
--- the unix referential works under the Windows platform or any other platforms.
---
-module Time.Types
-    (
-    -- * Time units
-      NanoSeconds(..)
-    , Seconds(..)
-    , Minutes(..)
-    , Hours(..)
-    , TimeInterval(..)
-    -- * Time enumeration
-    , Month(..)
-    , WeekDay(..)
-    -- * Timezone
-    , TimezoneOffset(..)
-    , timezoneOffsetToSeconds
-    , timezone_UTC
-    -- * Computer friendly format
-    -- ** Unix elapsed
-    , Elapsed(..)
-    , ElapsedP(..)
-    -- * Human friendly format
-    -- ** Calendar time
-    , Date(..)
-    , TimeOfDay(..)
-    , DateTime(..)
-    ) where
-
-import Data.Int
-import Data.Data
-import Data.Ratio
-import Control.DeepSeq
-import Data.Hourglass.Utils (pad2)
-
--- | Represent any time interval that has an
--- equivalent value to a number of seconds.
-class TimeInterval i where
-    toSeconds   :: i -> Seconds
-    fromSeconds :: Seconds -> (i, Seconds)
-
--- | Nanoseconds
-newtype NanoSeconds = NanoSeconds Int64
-    deriving (Read,Eq,Ord,Num,Data,Typeable,NFData)
-
-instance Show NanoSeconds where
-    show (NanoSeconds v) = shows v "ns"
-
-instance TimeInterval NanoSeconds where
-    toSeconds (NanoSeconds ns) = Seconds (ns `div` 1000000000)
-    fromSeconds (Seconds s) = (NanoSeconds (s * 1000000000), 0)
-
--- | Number of seconds without a referential.
---
--- Can hold a number between [-2^63,2^63-1], which should
--- be good for some billions of years.
---
--- However, because of limitation in the calendar conversion
--- currently used, seconds should be in the range [-2^55,2^55-1],
--- which is good for only 1 billion of year.
-newtype Seconds = Seconds Int64
-    deriving (Read,Eq,Ord,Enum,Num,Real,Integral,Data,Typeable,NFData)
-
-instance Show Seconds where
-    show (Seconds s) = shows s "s"
-
-instance TimeInterval Seconds where
-    toSeconds   = id
-    fromSeconds s = (s,0)
-
--- | Number of minutes without a referential.
-newtype Minutes = Minutes Int64
-    deriving (Read,Eq,Ord,Enum,Num,Real,Integral,Data,Typeable,NFData)
-
-instance Show Minutes where
-    show (Minutes s) = shows s "m"
-
-instance TimeInterval Minutes where
-    toSeconds (Minutes m)   = Seconds (m * 60)
-    fromSeconds (Seconds s) = (Minutes m, Seconds s')
-      where (m, s') = s `divMod` 60
-
--- | Number of hours without a referential.
-newtype Hours = Hours Int64
-    deriving (Read,Eq,Ord,Enum,Num,Real,Integral,Data,Typeable,NFData)
-
-instance Show Hours where
-    show (Hours s) = shows s "h"
-
-instance TimeInterval Hours where
-    toSeconds (Hours h)     = Seconds (h * 3600)
-    fromSeconds (Seconds s) = (Hours h, Seconds s')
-      where (h, s') = s `divMod` 3600
-
--- | A number of seconds elapsed since the unix epoch.
-newtype Elapsed = Elapsed Seconds
-    deriving (Read,Eq,Ord,Num,Data,Typeable,NFData)
-
-instance Show Elapsed where
-    show (Elapsed s) = show s
-
--- | A number of seconds and nanoseconds elapsed since the unix epoch.
-data ElapsedP = ElapsedP {-# UNPACK #-} !Elapsed {-# UNPACK #-} !NanoSeconds
-    deriving (Read,Eq,Ord,Data,Typeable)
-
-instance Show ElapsedP where
-    show (ElapsedP e ns) = shows e ('.' : show ns)
-
-instance NFData ElapsedP where rnf e = e `seq` ()
-
-instance Num ElapsedP where
-    (+) = addElapsedP
-    (-) = subElapsedP
-    (ElapsedP e1 ns1) * (ElapsedP e2 ns2) = ElapsedP (e1*e2) (ns1*ns2)
-    negate (ElapsedP e ns) = ElapsedP (negate e) ns
-    abs (ElapsedP e ns)    = ElapsedP (abs e) ns
-    signum (ElapsedP e ns) = ElapsedP (signum e) ns
-    fromInteger i          = ElapsedP (Elapsed (fromIntegral i)) 0
-
-addElapsedP :: ElapsedP -> ElapsedP -> ElapsedP
-addElapsedP (ElapsedP e1 (NanoSeconds ns1)) (ElapsedP e2 (NanoSeconds ns2)) =
-    let notNormalizedNS = ns1 + ns2
-        (retainedNS, ns) = notNormalizedNS `divMod` 1000000000
-    in  ElapsedP (e1 + e2 + (Elapsed $ Seconds retainedNS)) (NanoSeconds ns)
-
-subElapsedP :: ElapsedP -> ElapsedP -> ElapsedP
-subElapsedP (ElapsedP e1 (NanoSeconds ns1)) (ElapsedP e2 (NanoSeconds ns2)) =
-    let notNormalizedNS = ns1 - ns2
-        notNormalizedS  = e1 - e2
-    in  if notNormalizedNS < 0
-            then ElapsedP (notNormalizedS - oneSecond) (NanoSeconds (1000000000 + notNormalizedNS))
-            else ElapsedP notNormalizedS (NanoSeconds notNormalizedNS)
-  where
-    oneSecond :: Elapsed
-    oneSecond = Elapsed $ Seconds 1
-
-instance Real ElapsedP where
-    toRational (ElapsedP (Elapsed (Seconds s)) (NanoSeconds 0)) =
-        fromIntegral s
-    toRational (ElapsedP (Elapsed (Seconds s)) (NanoSeconds ns)) =
-        fromIntegral s + (fromIntegral ns % 1000000000)
-
--- | Month of the year
-data Month =
-      January
-    | February
-    | March
-    | April
-    | May
-    | June
-    | July
-    | August
-    | September
-    | October
-    | November
-    | December
-    deriving (Show,Read,Eq,Ord,Enum,Data,Typeable,Bounded)
-
--- | Day of the week
---
--- the enumeration starts on Sunday.
-data WeekDay =
-      Sunday
-    | Monday
-    | Tuesday
-    | Wednesday
-    | Thursday
-    | Friday
-    | Saturday
-    deriving (Show,Read,Eq,Ord,Enum,Data,Typeable,Bounded)
-
--- | Offset against UTC in minutes to obtain from UTC time, local time.
---
--- * a positive number represent a location East of UTC.
---
--- * a negative number represent a location West of UTC.
---
--- LocalTime t (-300) = t represent a time at UTC-5
--- LocalTime t (+480) = t represent a time at UTC+8
---
--- should be between -11H and +14H
---
--- Example:
---    in AUSEDT (UTC+1000 with daylight = UTC+1100), local time is 15:47;
---    Thus, UTC time is 04:47, and TimezoneOffset is +660 (minutes)
---
-newtype TimezoneOffset = TimezoneOffset
-    { timezoneOffsetToMinutes :: Int -- ^ return the number of minutes
-    } deriving (Eq,Ord,Data,Typeable,NFData)
-
--- | Return the number of seconds associated with a timezone
-timezoneOffsetToSeconds :: TimezoneOffset -> Seconds
-timezoneOffsetToSeconds (TimezoneOffset ofs) = Seconds (fromIntegral ofs * 60)
-
-instance Show TimezoneOffset where
-    show (TimezoneOffset tz) =
-        concat [if tz < 0 then "-" else "+", pad2 tzH, pad2 tzM]
-      where (tzH, tzM) = abs tz `divMod` 60
-
--- | The UTC timezone. offset of 0
-timezone_UTC :: TimezoneOffset
-timezone_UTC = TimezoneOffset 0
-
--- | human date representation using common calendar
-data Date = Date
-    { dateYear  :: {-# UNPACK #-} !Int   -- ^ year (Common Era)
-    , dateMonth :: !Month                -- ^ month of the year
-    , dateDay   :: {-# UNPACK #-} !Int   -- ^ day of the month, between 1 to 31
-    } deriving (Show,Read,Eq,Ord,Data,Typeable)
-
-instance NFData Date where
-    rnf (Date y m d) = y `seq` m `seq` d `seq` ()
-
--- | human time representation of hour, minutes, seconds in a day.
-data TimeOfDay = TimeOfDay
-    { todHour :: {-# UNPACK #-} !Hours   -- ^ hours, between 0 and 23
-    , todMin  :: {-# UNPACK #-} !Minutes -- ^ minutes, between 0 and 59
-    , todSec  :: {-# UNPACK #-} !Seconds -- ^ seconds, between 0 and 59. 60 when having leap second */
-    , todNSec :: {-# UNPACK #-} !NanoSeconds -- ^ nanoseconds, between 0 and 999999999 */
-    } deriving (Show,Read,Eq,Ord,Data,Typeable)
-
-instance NFData TimeOfDay where
-    rnf (TimeOfDay h m s ns) = h `seq` m `seq` s `seq` ns `seq` ()
-
--- | Date and Time
-data DateTime = DateTime
-    { dtDate :: Date
-    , dtTime :: TimeOfDay
-    } deriving (Show,Read,Eq,Ord,Data,Typeable)
-
-instance NFData DateTime where
-    rnf (DateTime d t) = rnf d `seq` rnf t `seq` ()
+ benchmarks/Bench.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE BangPatterns #-}
+
+module Main
+  ( main
+  ) where
+
+import           Data.Hourglass
+                   ( Date (..), DateTime (..), Elapsed (..), TimeOfDay (..)
+                   , timeGetDate, timeGetDateTimeOfDay, timeGetElapsed
+                   )
+import           Data.List ( intercalate )
+import qualified Data.Time.Calendar as T
+import qualified Data.Time.Clock as T
+import qualified Data.Time.Clock.POSIX as T
+import           System.Hourglass ( timeCurrent, timeCurrentP )
+import           Test.Tasty.Bench ( bench, bgroup, defaultMain, nf, nfIO )
+
+timeToTuple :: T.UTCTime -> (Int, Int, Int, Int, Int, Int)
+timeToTuple utcTime = (fromIntegral y, m, d, h, mi, sec)
+ where
+  (!y, !m, !d) = T.toGregorian (T.utctDay utcTime)
+  !daytime     = floor $ T.utctDayTime utcTime
+  (!dt, !sec)  = daytime `divMod` 60
+  (!h , !mi)   = dt `divMod` 60
+
+timeToTupleDate :: T.UTCTime -> (Int, Int, Int)
+timeToTupleDate utcTime = (fromIntegral y, m, d)
+ where
+  (!y, !m, !d) = T.toGregorian (T.utctDay utcTime)
+
+timePosixDict :: [ (Elapsed, T.POSIXTime) ]
+timePosixDict =
+  [-- (Elapsed 0, 0)
+  --, (Elapsed 1000000, 1000000)
+  --, (Elapsed 9000099, 9000099)
+  {-,-} (Elapsed 1398232846, 1398232846) -- currentish time (at the time of writing)
+  --, (Elapsed 5134000099, 5134000099)
+  --, (Elapsed 10000000000000, 10000000000000) -- year 318857 ..
+  ]
+
+dateDict :: [ (Int, Int, Int, Int, Int, Int) ]
+dateDict =
+  [{- (1970, 1, 1, 1, 1, 1)
+  , -}(2014, 5, 5, 5, 5, 5)
+  --, (2114, 11, 5, 5, 5, 5)
+  ]
+
+main :: IO ()
+main = defaultMain
+  [ bgroup "highlevel"   $ concatMap toHighLevel timePosixDict
+  , bgroup "to-dateTime" $ concatMap toCalendar timePosixDict
+  , bgroup "to-date"     $ concatMap toCalendarDate timePosixDict
+  , bgroup "utc-to-date" $ concatMap toCalendarUTC timePosixDict
+  , bgroup "to-posix"    $ concatMap toPosix dateDict
+  , bgroup "system"      fromSystem
+  ]
+ where
+  toHighLevel (posixHourglass, posixTime) =
+    [ bench (showH posixHourglass) $ nf timeGetDateTimeOfDay posixHourglass
+    , bench (showT posixTime) $ nf T.posixSecondsToUTCTime posixTime
+    ]
+  toCalendar (posixHourglass, posixTime) =
+    [ bench (showH posixHourglass) $ nf timeGetDateTimeOfDay posixHourglass
+    , bench (showT posixTime) $ nf (timeToTuple . T.posixSecondsToUTCTime) posixTime
+    ]
+  toCalendarDate (posixHourglass, posixTime) =
+    [ bench (showH posixHourglass) $ nf timeGetDate posixHourglass
+    , bench (showT posixTime) $ nf (timeToTupleDate . T.posixSecondsToUTCTime) posixTime
+    ]
+  toCalendarUTC (posixHourglass, posixTime) =
+    [ bench (showH posixHourglass) $ nf timeGetDateTimeOfDay posixHourglass
+    , bench (showT utcTime) $ nf timeToTuple utcTime
+    ]
+   where
+    !utcTime = T.posixSecondsToUTCTime posixTime
+
+  toPosix v =
+    [ bench ("hourglass/" ++ n v) $ nf hourglass v
+    , bench ("time/" ++ n v) $ nf time v
+    ]
+   where
+    n (y, m, d, h, mi, s) =
+         intercalate "-" (map show [y, m, d])
+      ++ " "
+      ++ intercalate ":" (map show [h, mi, s])
+    hourglass (y, m, d, h, mi, s) =
+      timeGetElapsed $
+        DateTime
+          (Date y (toEnum (m - 1)) d)
+          (TimeOfDay (fromIntegral h) (fromIntegral mi) (fromIntegral s) 0)
+    time (y, m, d, h, mi, s) =
+      let day = T.fromGregorian (fromIntegral y) m d
+          diffTime = T.secondsToDiffTime $ fromIntegral (h * 3600 + mi * 60 + s)
+      in  T.utcTimeToPOSIXSeconds (T.UTCTime day diffTime)
+
+  fromSystem =
+    [ bench "hourglass/p"    $ nfIO timeCurrent
+    , bench "hourglass/ns"   $ nfIO timeCurrentP
+    , bench "time/posixTime" $ nfIO T.getPOSIXTime
+    , bench "time/utcTime"   $ nfIO T.getCurrentTime
+    ]
+
+  showH :: Show a => a -> String
+  showH a = "hourglass/" ++ show a
+
+  showT :: Show a => a -> String
+  showT a = "time/" ++ show a
+ examples/Example/Time/Compat.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE PackageImports #-}
+
+{- |
+Module      : Example.Time.Compat
+License     : BSD-style
+Copyright   : (c) 2015 Nicolas DI PRIMA <nicolas@di-prima.fr>
+
+This file provides an example of how to use the Data.Hourglass.Compat module to
+transpose a ZonedTime (from time) into a LocalTime of DateTime (from
+time-hourglass).
+-}
+
+module Example.Time.Compat
+  ( transpose
+  ) where
+
+import           "time-hourglass" Data.Hourglass as H
+import           "time-hourglass" Time.Compat as C
+import           Data.Time as T
+
+transpose :: T.ZonedTime -> H.LocalTime H.DateTime
+transpose oldTime =
+  H.localTime
+    offsetTime
+    (H.DateTime newDate timeofday)
+ where
+  T.ZonedTime (T.LocalTime day tod) (T.TimeZone tzmin _ _) = oldTime
+
+  newDate :: H.Date
+  newDate = C.dateFromMJDEpoch $ T.toModifiedJulianDay day
+
+  timeofday :: H.TimeOfDay
+  timeofday = C.diffTimeToTimeOfDay $ toRational $ T.timeOfDayToTime tod
+
+  offsetTime = H.TimezoneOffset $ fromIntegral tzmin
+ src/Data/Hourglass.hs view
@@ -0,0 +1,107 @@+{- |
+Module      : Data.Hourglass
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Time-related types and functions.
+
+Basic types for representing points in time are t'Elapsed' and t'ElapsedP`. The
+\'P\' is short for \'precise\'. t'Elapsed' represents numbers of seconds elapsed
+since the start of the Unix epoch (1970-01-01 00:00:00 UTC). t'ElapsedP'
+represents numbers of seconds and nanoseconds elapsed.
+
+Values of other types representing points in time can be converted to and from
+values of the t'Elapsed' and t'ElapsedP' types. For example:
+
+> d = timeGetElapsed (Date 1955 April 18) :: Elapsed
+> timeFromElapsed d :: DateTime
+
+Local time is represented by t'LocalTime' @t@, parameterised by any other type
+representing time (for example, t'Elapsed', t'Date' or t'DateTime'). A local
+time value is augmented by a timezone offset in minutes. For example:
+
+> localTime (Date 2014 May 4) 600 -- local time at UTC+10 of 4th May 2014
+-}
+
+module Data.Hourglass
+  ( -- * Time units
+    NanoSeconds (..)
+  , Seconds (..)
+  , Minutes (..)
+  , Hours (..)
+    -- * Calendar enumerations
+  , Month (..)
+  , WeekDay (..)
+    -- * Points in time
+    -- ** Elapsed time since the start of the Unix epoch
+  , Elapsed (..)
+  , ElapsedP (..)
+    -- ** Date, time, and date and time
+  , Date (..)
+  , TimeOfDay (..)
+  , DateTime (..)
+    -- ** Local time and timezone-related
+  , LocalTime
+  , Timezone (..)
+  , TimezoneOffset (..)
+  , timezoneOffsetToSeconds
+  , UTC (..)
+  , timezone_UTC
+  , TimezoneMinutes (..)
+    -- *** Constructors
+  , localTime
+  , localTimeFromGlobal
+  , localTimeSetTimezone
+    -- *** Accessors
+  , localTimeUnwrap
+  , localTimeGetTimezone
+    -- ** Miscellaneous calandar functions
+  , isLeapYear
+  , getWeekDay
+  , getDayOfTheYear
+  , daysInMonth
+    -- * Periods of time
+  , Duration (..)
+  , Period (..)
+  , timeAdd
+  , timeDiff
+  , timeDiffP
+  , dateAddPeriod
+    -- * Conversion of points in time
+  , Time (..)
+  , Timeable (..)
+  , timeConvert
+  , timeGetDate
+  , timeGetDateTimeOfDay
+  , timeGetTimeOfDay
+  , localTimeConvert
+  , localTimeToGlobal
+    -- * Conversion of periods of time
+  , TimeInterval (..)
+    -- * Parsing and Printing
+    -- ** Format strings
+  , TimeFormatElem (..)
+  , TimeFormatFct (..)
+  , TimeFormatString (..)
+  , TimeFormat (..)
+    -- ** Common built-in formats
+  , ISO8601_Date (..)
+  , ISO8601_DateAndTime (..)
+    -- ** Format methods
+  , timePrint
+  , timeParse
+  , timeParseE
+  , localTimePrint
+  , localTimeParse
+  , localTimeParseE
+  ) where
+
+import           Time.Calendar
+                   ( daysInMonth, getDayOfTheYear, getWeekDay, isLeapYear )
+import           Time.Format
+import           Time.LocalTime
+import           Time.Time
+import           Time.Timezone
+import           Time.Types
+ src/Data/Hourglass/Compat.hs view
@@ -0,0 +1,38 @@+{- |
+Module      : Data.Hourglass.Compat
+License     : BSD-style
+Copyright   : (c) 2015 Nicolas DI PRIMA <nicolas@di-prima.fr>
+
+Basic Time conversion compatibility.
+
+This module aims to help conversion between the types from the package time to
+the package hourglass.
+
+Example of use (extracted from file Example/Time/Compat.hs):
+
+> import Data.Hourglass as H
+> import Time.Compat as C
+> import Data.Time as T
+>
+> transpose :: T.ZonedTime -> H.LocalTime H.DateTime
+> transpose oldTime = H.localTime
+>   offsetTime
+>   (H.DateTime newDate timeofday)
+>  where
+>   T.ZonedTime (T.LocalTime day tod) (T.TimeZone tzmin _ _) = oldTime
+>
+>   newDate :: H.Date
+>   newDate = C.dateFromMJDEpoch $ T.toModifiedJulianDay day
+>
+>   timeofday :: H.TimeOfDay
+>   timeofday = C.diffTimeToTimeOfDay $ toRational $ T.timeOfDayToTime tod
+>
+>   offsetTime = H.TimezoneOffset $ fromIntegral tzmin
+-}
+
+module Data.Hourglass.Compat
+{-# DEPRECATED "Use Time.Compat instead. Will be removed from future versions of this package." #-}
+  ( module Time.Compat
+  ) where
+
+import Time.Compat
+ src/Data/Hourglass/Epoch.hs view
@@ -0,0 +1,16 @@+{- |
+Module      : Data.Hourglass.Epoch
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Types and functions related to epochs.
+-}
+
+module Data.Hourglass.Epoch
+  {-# DEPRECATED "Use Time.Epoch instead. Will be removed from future versions of this package." #-}
+  ( module Time.Epoch
+  ) where
+
+import Time.Epoch
+ src/Data/Hourglass/Types.hs view
@@ -0,0 +1,21 @@+{- |
+Module      : Data.Hourglass.Types
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+
+Basic times units and types.
+
+While pratically some units could hold infinite values, for practical and
+efficient purpose they are limited to int64 types for seconds and int types for
+years.
+
+Most units use the Unix epoch referential, but by no means reduce portability.
+The Unix referential works under the Windows platform or any other platforms.
+-}
+
+module Data.Hourglass.Types
+  {-# DEPRECATED "Use Time.Types instead. Will be removed from future versions of this package." #-}
+  ( module Time.Types
+  ) where
+
+import Time.Types
+ src/System/Hourglass.hs view
@@ -0,0 +1,16 @@+{- |
+Module      : System.Hourglass
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Get the system timezone and current time value in multiple formats.
+-}
+
+module System.Hourglass
+  {-# DEPRECATED "Use Time.System instead. Will be removed from future versions of this package." #-}
+  ( module Time.System
+  ) where
+
+import Time.System
+ src/Time/Calendar.hs view
@@ -0,0 +1,120 @@+{- |
+Module      : Time.Calendar
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Miscellaneous calendar functions.
+-}
+
+module Time.Calendar
+  ( isLeapYear
+  , getWeekDay
+  , getDayOfTheYear
+  , daysInMonth
+  , dateToUnixEpoch
+  , dateFromUnixEpoch
+  , todToSeconds
+  , dateTimeToUnixEpoch
+  , dateTimeFromUnixEpoch
+  , dateTimeFromUnixEpochP
+  ) where
+
+import           Time.Internal
+                   ( dateTimeFromUnixEpoch, dateTimeFromUnixEpochP )
+import           Time.Types
+                   ( Date (..), DateTime (..), Elapsed (..), Month (..)
+                   , Seconds (..), TimeInterval (..), TimeOfDay (..), WeekDay
+                   )
+
+-- | For the given year in the Gregorian calendar, is it a leap year (366 days
+-- long)?
+isLeapYear ::
+     Int
+     -- ^ Year.
+  -> Bool
+isLeapYear year
+  | year `mod` 4 /= 0   = False
+  | year `mod` 100 /= 0 = True
+  | year `mod` 400 == 0 = True
+  | otherwise           = False
+
+-- | For the given date in the proleptic Gregorian calendar, yield the day of
+-- the week it falls on.
+getWeekDay :: Date -> WeekDay
+getWeekDay date = toEnum (d `mod` 7)
+ where
+  d = daysOfDate date
+
+-- | For the given year and month in the proleptic Gregorian calendar, yield the
+-- number of days from the start of the year to the start of the month.
+daysUntilMonth ::
+     Int
+     -- ^ Year.
+  -> Month
+  -> Int
+daysUntilMonth y m
+  | isLeapYear y = leapYears !! fromEnum m
+  | otherwise    = normalYears !! fromEnum m
+ where
+  normalYears = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ]
+  leapYears   = [ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 ]
+
+-- | For the given year and month in the proleptic Gregorian calendar, yield the
+-- number of days in the month.
+daysInMonth ::
+     Int
+     -- ^ Year.
+  -> Month
+  -> Int
+daysInMonth y m
+  | m == February && isLeapYear y = 29
+  | otherwise                     = days !! fromEnum m
+ where
+  days = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
+
+-- | For the given date in the proleptic Gregorian calendar, yield the number of
+-- days before the date in the same year. For example, there are @0@ days before
+-- 1st January.
+getDayOfTheYear :: Date -> Int
+getDayOfTheYear (Date y m d) = daysUntilMonth y m + d
+
+-- | For the given year in the proleptic Gregorian calendar, yield the number of
+-- days before 1st January of the year and since 1st January 1 CE.
+daysBeforeYear :: Int -> Int
+daysBeforeYear year = y * 365 + (y `div` 4) - (y `div` 100) + (y `div` 400)
+ where
+  y = year - 1
+
+-- | For the given date in the proleptic Gregorian calendar, yield the number of
+-- days since 1st January 1 CE.
+daysOfDate :: Date -> Int
+daysOfDate (Date y m d) = daysBeforeYear y + daysUntilMonth y m + d
+
+-- | For the given date in the proleptic Gregorian calendar, and assuming a time
+-- of 00:00:00 UTC, yield the number of seconds since the start of the Unix
+-- epoch (1970-01-01 00:00:00 UTC). This assumes each day is 24 hours long.
+dateToUnixEpoch :: Date -> Elapsed
+dateToUnixEpoch date =
+  Elapsed $ Seconds (fromIntegral (daysOfDate date - epochDays) * secondsPerDay)
+ where
+  epochDays     = 719163
+  secondsPerDay = 86400 -- Julian day is 24h
+
+-- | For the given period of time since the start of the Unix epoch
+-- (1970-01-01 00:00:00 UTC), yield the corresponding date.
+dateFromUnixEpoch :: Elapsed -> Date
+dateFromUnixEpoch e = dtDate $ dateTimeFromUnixEpoch e
+
+-- | For the given time of day, yield the number of seconds since the start of
+-- the day.
+todToSeconds :: TimeOfDay -> Seconds
+todToSeconds (TimeOfDay h m s _) = toSeconds h + toSeconds m + s
+
+-- | For the given date (in the proleptic Gregorian calendar) and time (in UTC),
+-- yield the number of seconds that have elapsed since the start of the Unix
+-- epoch.
+dateTimeToUnixEpoch :: DateTime -> Elapsed
+dateTimeToUnixEpoch (DateTime d t) =
+  dateToUnixEpoch d + Elapsed (todToSeconds t)
+ src/Time/Compat.hs view
@@ -0,0 +1,139 @@+{- |
+Module      : Time.Compat
+License     : BSD-style
+Copyright   : (c) 2015 Nicolas DI PRIMA <nicolas@di-prima.fr>
+
+Basic time conversion compatibility.
+
+This module aims to help conversion between types from @time@ package and types
+from the @time-hourglass@ package.
+
+An example of use (taken from file examples/Example/Time/Compat.hs):
+
+> import Data.Hourglass as H
+> import Time.Compat as C
+> import Data.Time as T
+>
+> transpose :: T.ZonedTime -> H.LocalTime H.DateTime
+> transpose oldTime = H.localTime
+>   offsetTime
+>   (H.DateTime newDate timeofday)
+>  where
+>   T.ZonedTime (T.LocalTime day tod) (T.TimeZone tzmin _ _) = oldTime
+>
+>   newDate :: H.Date
+>   newDate = C.dateFromMJDEpoch $ T.toModifiedJulianDay day
+>
+>   timeofday :: H.TimeOfDay
+>   timeofday = C.diffTimeToTimeOfDay $ toRational $ T.timeOfDayToTime tod
+>
+>   offsetTime = H.TimezoneOffset $ fromIntegral tzmin
+-}
+
+module Time.Compat
+  ( dateFromUnixEpoch
+  , dateFromMJDEpoch
+  , diffTimeToTimeOfDay
+    -- * Deprecated
+  , dateFromPOSIXEpoch
+  , dateFromTAIEpoch
+  ) where
+
+import           Time.Time ( timeConvert )
+import           Time.Types ( Date, Elapsed (..), TimeOfDay (..) )
+
+-- | Given an integer which represents the number of days since the start of the
+-- Unix epoch (1970-01-01 00:00:00 UTC), yield the corresponding date in the
+-- proleptic Gregorian calendar.
+dateFromUnixEpoch ::
+     Integer
+     -- ^ Number of days since the start of the Unix epoch
+     -- (1970-01-01 00:00:00 UTC).
+  -> Date
+dateFromUnixEpoch day = do
+  let sec = Elapsed $ fromIntegral $ day * 86400
+  timeConvert sec
+
+-- | Same as 'dateFromUnixEpoch'.
+dateFromPOSIXEpoch ::
+     Integer
+     -- ^ Number of days since the start of the Unix epoch
+     -- (1970-01-01 00:00:00 UTC).
+  -> Date
+dateFromPOSIXEpoch = dateFromUnixEpoch
+{-# DEPRECATED dateFromPOSIXEpoch "Will be removed from future versions of this package. Use dateFromUnixEpoch" #-}
+
+-- | The number of days between the start of the Modified Julian Date (MJD)
+-- epoch (1858-11-17 00:00:00 UTC) and the start of the Unix epoch
+-- (1970-01-01 00:00:00 UTC).
+daysMJDtoUnix :: Integer
+daysMJDtoUnix = 40587
+
+-- | Given an integer which represents the number of days since the start of the
+-- Modified Julian Date (MJD) epoch (1858-11-17 00:00:00 UTC), yields the
+-- corresponding date in the proleptic Gregorian calendar.
+--
+-- This function allows a user to convert a t'Data.Time.Calendar.Day'
+-- into t'Date'.
+--
+-- > import qualified Data.Time.Calendar as T
+-- >
+-- > timeDay :: T.Day
+-- >
+-- > dateFromMJDEpoch $ T.toModifiedJulianDay timeDay
+dateFromMJDEpoch ::
+     Integer
+     -- ^ Number of days since 1858-11-17 00:00:00 UTC.
+  -> Date
+dateFromMJDEpoch dtai =
+  dateFromUnixEpoch (dtai - daysMJDtoUnix)
+
+-- | Same as 'dateFromMJDEpoch'.
+--
+-- The name of this function is a misnomer, as the International Atomic Time
+-- (TAI) epoch starts on 1958-01-01 00:00:00 UTC.
+dateFromTAIEpoch ::
+     Integer
+     -- ^ Number of days since 1858-11-17 00:00:00 UTC.
+  -> Date
+dateFromTAIEpoch = dateFromMJDEpoch
+{-# DEPRECATED dateFromTAIEpoch "Will be removed from future versions of this package. Use dateFromMJDEpoch" #-}
+
+-- | Given a real number representing the number of seconds since the start of
+-- the day, yield a t'TimeOfDay' value.
+--
+-- Example with t'Data.Time.Clock.DiffTime' type from package @time@:
+--
+-- > import qualified Data.Time.Clock as T
+-- >
+-- > difftime :: T.DiffTime
+-- >
+-- > diffTimeToTimeOfDay difftime
+--
+-- Example with the 'Data.Time.LocalTime.TimeOfDay' type from package @time@:
+--
+-- > import qualified Data.Time.Clock as T
+-- >
+-- > timeofday :: T.TimeOfDay
+-- >
+-- > diffTimeToTimeOfDay $ T.timeOfDayToTime timeofday
+diffTimeToTimeOfDay ::
+    Real t
+  => t
+     -- ^ Number of seconds of the time of the day.
+  -> TimeOfDay
+diffTimeToTimeOfDay dt = do
+  TimeOfDay
+    { todHour = fromIntegral hours
+    , todMin  = fromIntegral minutes
+    , todSec  = fromIntegral seconds
+    , todNSec = fromIntegral nsecs
+    }
+ where
+  r :: Rational
+  r = toRational dt
+  (secs, nR) = properFraction r :: (Integer, Rational)
+  nsecs :: Integer
+  nsecs = round (nR * 1000000000)
+  (minsofday, seconds) = secs `divMod` 60 :: (Integer, Integer)
+  (hours, minutes) = minsofday `divMod` 60 :: (Integer, Integer)
+ src/Time/Diff.hs view
@@ -0,0 +1,163 @@+{-# LANGUAGE DeriveDataTypeable #-}
+
+{- |
+Module      : Time.Diff
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Time arithmetic methods.
+-}
+
+module Time.Diff
+  ( Duration (..)
+  , Period (..)
+  , durationNormalize
+  , durationFlatten
+  , elapsedTimeAddSeconds
+  , elapsedTimeAddSecondsP
+  , dateAddPeriod
+  ) where
+
+import           Control.DeepSeq ( NFData (..) )
+import           Data.Data ( Data )
+import           Time.Calendar ( daysInMonth )
+import           Time.Types
+                   ( Date (..), Elapsed (..), ElapsedP (..), Hours (..)
+                   , Minutes (..), NanoSeconds (..), Seconds (..)
+                   , TimeInterval (..)
+                   )
+
+-- | Type representing periods of time in years, months and days.
+-- See t'Duration' for periods of time hours, minutes, seconds and nanoseconds.
+data Period = Period
+  { periodYears  :: !Int
+  , periodMonths :: !Int
+  , periodDays   :: !Int
+  }
+  deriving (Data, Eq, Ord, Read, Show)
+
+instance NFData Period where
+  rnf (Period y m d) = y `seq` m `seq` d `seq` ()
+
+instance Semigroup Period where
+  (<>) (Period y1 m1 d1) (Period y2 m2 d2) =
+    Period (y1 + y2) (m1 + m2) (d1 + d2)
+
+instance Monoid Period where
+  mempty = Period 0 0 0
+
+-- | Type represeting periods of time in hours, minutes, seconds and
+-- nanoseconds. See t'Period' for periods of time in years, months and days.
+data Duration = Duration
+  { durationHours   :: !Hours       -- ^ number of hours
+  , durationMinutes :: !Minutes     -- ^ number of minutes
+  , durationSeconds :: !Seconds     -- ^ number of seconds
+  , durationNs      :: !NanoSeconds -- ^ number of nanoseconds
+  }
+  deriving (Data, Eq, Ord, Read, Show)
+
+instance NFData Duration where
+  rnf (Duration h m s ns) = h `seq` m `seq` s `seq` ns `seq` ()
+
+instance Semigroup Duration where
+  (<>) (Duration h1 m1 s1 ns1) (Duration h2 m2 s2 ns2) =
+    Duration (h1 + h2) (m1 + m2) (s1 + s2) (ns1 + ns2)
+
+instance Monoid Duration where
+  mempty = Duration 0 0 0 0
+
+instance TimeInterval Duration where
+  fromSeconds s = (durationNormalize (Duration 0 0 s 0), 0)
+
+  toSeconds d   = fst $ durationFlatten d
+
+-- | Convert a t'Duration' to an equivalent number of seconds and nanoseconds.
+durationFlatten :: Duration -> (Seconds, NanoSeconds)
+durationFlatten (Duration h m s (NanoSeconds ns)) =
+  (toSeconds h + toSeconds m + s + Seconds sacc, NanoSeconds ns')
+ where
+  (sacc, ns') = ns `divMod` 1000000000
+
+-- | Normalize a t'Duration' to represent the same period of time with the
+-- biggest units possible. For example, 62 minutes is normalized as
+-- 1 hour and 2 minutes.
+durationNormalize :: Duration -> Duration
+durationNormalize (Duration (Hours h) (Minutes mi) (Seconds s) (NanoSeconds ns)) =
+  Duration (Hours (h+hacc)) (Minutes mi') (Seconds s') (NanoSeconds ns')
+ where
+  (hacc, mi') = (mi+miacc) `divMod` 60
+  (miacc, s') = (s+sacc) `divMod` 60
+  (sacc, ns') = ns `divMod` 1000000000
+
+-- | Add the given period of time to the given date.
+dateAddPeriod :: Date -> Period -> Date
+dateAddPeriod (Date yOrig mOrig dOrig) (Period yDiff mDiff dDiff) =
+  loop (yOrig + yDiff + yDiffAcc) mStartPos (dOrig + dDiff)
+ where
+  (yDiffAcc,mStartPos) = (fromEnum mOrig + mDiff) `divMod` 12
+  loop y m d
+    | d <= 0 =
+        let (m', y') = if m == 0
+              then (11, y - 1)
+              else (m - 1, y)
+        in  loop y' m' (daysInMonth y' (toEnum m') + d)
+    | d <= dMonth = Date y (toEnum m) d
+    | otherwise =
+        let newDiff = d - dMonth
+        in  if m == 11
+              then loop (y + 1) 0 newDiff
+              else loop y (m + 1) newDiff
+   where
+    dMonth = daysInMonth y (toEnum m)
+
+-- | Add the given number of seconds to the given t'Elapsed' value.
+elapsedTimeAddSeconds :: Elapsed -> Seconds -> Elapsed
+elapsedTimeAddSeconds (Elapsed s1) s2 = Elapsed (s1 + s2)
+
+-- | Add the given number of seconds to the given t'ElapsedP' value.
+elapsedTimeAddSecondsP :: ElapsedP -> Seconds -> ElapsedP
+elapsedTimeAddSecondsP (ElapsedP (Elapsed s1) ns1) s2 =
+  ElapsedP (Elapsed (s1 + s2)) ns1
+
+{- disabled for warning purpose. to be implemented
+
+-- | Duration string to time diff
+--
+-- <http://en.wikipedia.org/wiki/ISO_8601#Durations>
+--
+-- * P is the duration designator (historically called "period") placed at the start of the duration representation.
+--
+-- * Y is the year designator that follows the value for the number of years.
+--
+-- * M is the month designator that follows the value for the number of months.
+--
+-- * W is the week designator that follows the value for the number of weeks.
+--
+-- * D is the day designator that follows the value for the number of days.
+--
+-- * T is the time designator that precedes the time components of the representation.
+--
+-- * H is the hour designator that follows the value for the number of hours.
+--
+-- * M is the minute designator that follows the value for the number of minutes.
+--
+-- * S is the second designator that follows the value for the number of seconds.
+--
+timeDiffFromDuration :: String -> TimeDiff
+timeDiffFromDuration _ = undefined
+
+timeDiffFromString :: String -> (
+
+-- | Human description string to time diff
+--
+-- examples:
+--
+-- * "1 day"
+--
+-- * "2 months, 5 days and 1 second"
+--
+timeDiffFromDescription :: String -> TimeDiff
+timeDiffFromDescription _ = undefined
+-}
+ src/Time/Epoch.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE DeriveDataTypeable         #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+{- |
+Module      : Time.Epoch
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Types and functions related to epochs.
+-}
+
+module Time.Epoch
+  ( -- * Elapsed time from the start of epochs
+    ElapsedSince (..)
+  , ElapsedSinceP (..)
+    -- * Epoch
+  , Epoch (..)
+    -- * Commonly-encountered epochs
+  , UnixEpoch (..)
+  , WindowsEpoch (..)
+  ) where
+
+import           Control.DeepSeq ( NFData (..) )
+import           Data.Data ( Data )
+import           Time.Time ( Time (..), Timeable (..) )
+import           Time.Types
+                   ( Elapsed (..), ElapsedP (..), NanoSeconds (..)
+                   , Seconds (..)
+                   )
+
+-- | A type representing the number of seconds that have elapsed since the start
+-- of a specified epoch.
+newtype ElapsedSince epoch = ElapsedSince Seconds
+  deriving (Data, Eq, NFData, Num, Ord,  Read, Show)
+
+-- | A type representing the number of seconds and nanoseconds that have elapsed
+-- since the start of a specified epoch. The \'P\' is short for \'precise\'.
+data ElapsedSinceP epoch = ElapsedSinceP
+  {-# UNPACK #-} !(ElapsedSince epoch)
+  {-# UNPACK #-} !NanoSeconds
+  deriving (Data, Eq, Ord, Read, Show)
+
+instance NFData (ElapsedSinceP e) where
+  rnf e = e `seq` ()
+
+instance Num (ElapsedSinceP e) where
+  (ElapsedSinceP e1 ns1) + (ElapsedSinceP e2 ns2) =
+    ElapsedSinceP (e1 + e2) (ns1 + ns2)
+
+  (ElapsedSinceP e1 ns1) - (ElapsedSinceP e2 ns2) =
+    ElapsedSinceP (e1 - e2) (ns1 - ns2)
+
+  (ElapsedSinceP e1 ns1) * (ElapsedSinceP e2 ns2) =
+    ElapsedSinceP (e1 * e2) (ns1 * ns2)
+
+  negate (ElapsedSinceP e ns) = ElapsedSinceP (negate e) ns
+
+  abs (ElapsedSinceP e ns) = ElapsedSinceP (abs e) ns
+
+  signum (ElapsedSinceP e ns) = ElapsedSinceP (signum e) ns
+
+  fromInteger i = ElapsedSinceP (ElapsedSince (fromIntegral i)) 0
+
+-- FIXME instance Real (ElapsedSinceP e)
+
+-- | A type class promising epoch-related functionality.
+--
+class Epoch epoch where
+  -- | The name of the epoch.
+  epochName :: epoch -> String
+
+  -- | The start of the epoch relative to the start of the Unix epoch
+  -- (1970-01-01 00:00:00 UTC), in seconds. A negative number means the epoch
+  -- starts before the starts of the Unix epoch.
+  epochDiffToUnix :: epoch -> Seconds
+
+-- | A type representing the Unix epoch, which started on
+-- 1970-01-01 00:00:00 UTC.
+data UnixEpoch = UnixEpoch
+  deriving (Eq, Show)
+
+instance Epoch UnixEpoch where
+  epochName _ = "unix"
+  epochDiffToUnix _ = 0
+
+-- | A type representing the
+-- [Windows epoch](https://learn.microsoft.com/en-us/windows/win32/sysinfo/file-times),
+-- which started on 1601-01-01 00:00:00 UTC.
+data WindowsEpoch = WindowsEpoch
+  deriving (Eq, Show)
+
+instance Epoch WindowsEpoch where
+  epochName _ = "windows"
+  epochDiffToUnix _ = -11644473600
+
+instance Epoch epoch => Timeable (ElapsedSince epoch) where
+  timeGetElapsedP es = ElapsedP (Elapsed e) 0
+   where
+    ElapsedSince e = convertEpoch es :: ElapsedSince UnixEpoch
+
+  timeGetElapsed   es = Elapsed e
+   where
+    ElapsedSince e = convertEpoch es :: ElapsedSince UnixEpoch
+
+  timeGetNanoSeconds _ = 0
+
+instance Epoch epoch => Time (ElapsedSince epoch) where
+  timeFromElapsedP (ElapsedP (Elapsed e) _) =
+    convertEpoch (ElapsedSince e :: ElapsedSince UnixEpoch)
+
+instance Epoch epoch => Timeable (ElapsedSinceP epoch) where
+  timeGetElapsedP es = ElapsedP (Elapsed e) ns
+   where
+    ElapsedSinceP (ElapsedSince e) ns =
+      convertEpochP es :: ElapsedSinceP UnixEpoch
+
+  timeGetNanoSeconds (ElapsedSinceP _ ns) = ns
+
+instance Epoch epoch => Time (ElapsedSinceP epoch) where
+  timeFromElapsedP (ElapsedP (Elapsed e) ns) =
+    convertEpochP (ElapsedSinceP (ElapsedSince e) ns :: ElapsedSinceP UnixEpoch)
+
+-- | For the given pair of epochs, convert a t'ElapsedSince' value for the first
+-- epoch to the corresponding value for the second epoch.
+convertEpochWith ::
+     (Epoch e1, Epoch e2)
+  => (e1, e2)
+  -> ElapsedSince e1
+  -> ElapsedSince e2
+convertEpochWith (e1, e2) (ElapsedSince s1) = ElapsedSince (s1 + diff)
+ where
+  diff = d1 - d2
+  d1 = epochDiffToUnix e1
+  d2 = epochDiffToUnix e2
+
+-- | Convert the given t'ElapsedSince' value to another t'ElapsedSince' value.
+-- This will not compile unless the compiler can infer the types of the epochs.
+convertEpoch :: (Epoch e1, Epoch e2) => ElapsedSince e1 -> ElapsedSince e2
+convertEpoch = convertEpochWith (undefined, undefined)
+
+-- | For the given pair of epochs, convert a t'ElapsedSinceP' value for the
+-- first epoch to the corresponding value for the second epoch.
+convertEpochPWith ::
+     (Epoch e1, Epoch e2)
+  => (e1, e2)
+  -> ElapsedSinceP e1
+  -> ElapsedSinceP e2
+convertEpochPWith es (ElapsedSinceP e1 n1) =
+  ElapsedSinceP (convertEpochWith es e1) n1
+
+-- | Convert the given t'ElapsedSinceP' value to another t'ElapsedSinceP' value.
+-- This will not compile unless the compiler can infer the types of the epochs.
+convertEpochP :: (Epoch e1, Epoch e2) => ElapsedSinceP e1 -> ElapsedSinceP e2
+convertEpochP = convertEpochPWith (undefined, undefined)
+ src/Time/Format.hs view
@@ -0,0 +1,468 @@+{-# LANGUAGE FlexibleInstances #-}
+
+{- |
+Module      : Time.Format
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Time formatting : printing and parsing
+
+Built-in format strings
+-}
+
+module Time.Format
+  ( -- * Parsing and Printing
+    -- ** Format strings
+    TimeFormatElem (..)
+  , TimeFormatFct (..)
+  , TimeFormatString (..)
+  , TimeFormat (..)
+    -- ** Common built-in formats
+  , ISO8601_Date (..)
+  , ISO8601_DateAndTime (..)
+    -- ** Format methods
+  , timePrint
+  , timeParse
+  , timeParseE
+  , localTimePrint
+  , localTimeParse
+  , localTimeParseE
+  ) where
+
+import           Data.Char ( isDigit, ord )
+import           Data.Int ( Int64 )
+import           Time.Internal ( dateTimeFromUnixEpochP )
+import           Time.LocalTime
+                   ( LocalTime (..), localTime, localTimeToGlobal )
+import           Time.Time ( Timeable (..), timeGetDateTimeOfDay )
+import           Time.Utils ( pad2, pad4, padN )
+import           Time.Types
+                   ( Date (..), DateTime (..), Elapsed (..), ElapsedP (..)
+                   , Hours (..), Minutes (..), Month (..), NanoSeconds (..)
+                   , Seconds (..), TimeOfDay (..), TimezoneOffset (..)
+                   , timezone_UTC
+                   )
+
+-- | Type representing formatters that can be part of a time format string.
+data TimeFormatElem =
+    Format_Year2
+    -- ^ 2 digit years (70 is 1970, 69 is 2069).
+  | Format_Year4
+    -- ^ 4 digits years.
+  | Format_Year
+    -- ^ Any digits years.
+  | Format_Month
+    -- ^ Months (1 to 12).
+  | Format_Month2
+    -- ^ Months padded to 2 characters (01 to 12).
+  | Format_MonthName_Short
+    -- ^ Short name of nthe month (\'Jan\', \'Feb\' ..).
+  | Format_DayYear
+    -- ^ Day of the year (1 to 365, 366 for leap years).
+  | Format_Day
+    -- ^ Day of the month (1 to 31).
+  | Format_Day2
+    -- ^ Day of the month padded to 2 characters (01 to 31).
+  | Format_Hour
+    -- ^ Hours (0 to 23).
+  | Format_Minute
+    -- ^ Minutes (0 to 59).
+  | Format_Second
+    -- ^ sSeconds (0 to 59, 60 for leap seconds).
+  | Format_UnixSecond
+    -- ^ Number of seconds since the start of the Unix epoch
+    -- (1970-01-01 00:00:00 UTC).
+  | Format_MilliSecond
+    -- ^ Milliseconds padded to 3 characters (000 to 999).
+  | Format_MicroSecond
+    -- ^ MicroSeconds padded to 6 characters (000000 to 999999).
+  | Format_NanoSecond
+    -- ^ NanoSeconds padded to 9 characters (000000000 to 999999999).
+  | Format_Precision Int
+    -- ^ Sub seconds display with a precision of n digits, with n between @1@
+    -- and @9@.
+  | Format_TimezoneName
+    -- ^ Timezone name (e.g. GMT, PST). Not yet implemented.
+--  Format_TimezoneOffset
+    -- ^ Timezone offset offset (+02:00).
+  | Format_TzHM_Colon_Z
+    -- ^ Zero UTC offset (Z) or timezone offset with colon (+02:00).
+  | Format_TzHM_Colon
+    -- ^ Timezone offset with colon (+02:00).
+  | Format_TzHM
+    -- ^ Timezone offset without colon (+0200).
+  | Format_Tz_Offset
+    -- ^ Timezone offset in minutes.
+  | Format_Spaces
+    -- ^ One or more space-like chars.
+  | Format_Text Char
+    -- ^ A verbatim character.
+  | Format_Fct TimeFormatFct
+    -- ^ Not implemented.
+  deriving (Eq, Show)
+
+-- | Type representing format functions.
+data TimeFormatFct = TimeFormatFct
+  { timeFormatFctName :: String
+    -- ^ The name of the format function.
+  , timeFormatParse   :: DateTime -> String -> Either String (DateTime, String)
+    -- ^ A parser.
+  , timeFormatPrint   :: DateTime -> String
+    -- A printer.
+  }
+
+instance Show TimeFormatFct where
+  show = timeFormatFctName
+
+instance Eq TimeFormatFct where
+  t1 == t2 = timeFormatFctName t1 == timeFormatFctName t2
+
+-- | Type representing time format strings, composed of list
+-- of t'TimeFormatElem'.
+newtype TimeFormatString = TimeFormatString [TimeFormatElem]
+  deriving (Eq, Show)
+
+-- | A type class promising the ability to convert values to
+-- a t'TimeFormatString'.
+class TimeFormat format where
+  toFormat :: format -> TimeFormatString
+
+-- | A type representing a ISO8601 date format string.
+--
+-- e.g. 2014-04-05
+data ISO8601_Date = ISO8601_Date
+  deriving (Eq, Show)
+
+-- | A type representing a ISO8601 date and time format string.
+--
+-- e.g. 2014-04-05T17:25:04+00:00 or 2014-04-05T17:25:04Z.
+data ISO8601_DateAndTime = ISO8601_DateAndTime
+  deriving (Eq, Show)
+
+instance TimeFormat [TimeFormatElem] where
+  toFormat = TimeFormatString
+
+instance TimeFormat TimeFormatString where
+  toFormat = id
+
+instance TimeFormat String where
+  toFormat = TimeFormatString . toFormatElem
+   where
+    toFormatElem []                  = []
+    toFormatElem ('Y':'Y':'Y':'Y':r) = Format_Year4  : toFormatElem r
+    toFormatElem ('Y':'Y':r)         = Format_Year2  : toFormatElem r
+    toFormatElem ('M':'M':r)         = Format_Month2 : toFormatElem r
+    toFormatElem ('M':'o':'n':r)     = Format_MonthName_Short : toFormatElem r
+    toFormatElem ('M':'I':r)         = Format_Minute : toFormatElem r
+    toFormatElem ('M':r)             = Format_Month  : toFormatElem r
+    toFormatElem ('D':'D':r)         = Format_Day2   : toFormatElem r
+    toFormatElem ('H':r)             = Format_Hour   : toFormatElem r
+    toFormatElem ('S':r)             = Format_Second : toFormatElem r
+    toFormatElem ('m':'s':r)         = Format_MilliSecond : toFormatElem r
+    toFormatElem ('u':'s':r)         = Format_MicroSecond : toFormatElem r
+    toFormatElem ('μ':r)             = Format_MicroSecond : toFormatElem r
+    toFormatElem ('n':'s':r)         = Format_NanoSecond : toFormatElem r
+    toFormatElem ('p':'1':r)         = Format_Precision 1 : toFormatElem r
+    toFormatElem ('p':'2':r)         = Format_Precision 2 : toFormatElem r
+    toFormatElem ('p':'3':r)         = Format_Precision 3 : toFormatElem r
+    toFormatElem ('p':'4':r)         = Format_Precision 4 : toFormatElem r
+    toFormatElem ('p':'5':r)         = Format_Precision 5 : toFormatElem r
+    toFormatElem ('p':'6':r)         = Format_Precision 6 : toFormatElem r
+    toFormatElem ('p':'7':r)         = Format_Precision 7 : toFormatElem r
+    toFormatElem ('p':'8':r)         = Format_Precision 8 : toFormatElem r
+    toFormatElem ('p':'9':r)         = Format_Precision 9 : toFormatElem r
+    -----------------------------------------------------------
+    toFormatElem ('E':'P':'O':'C':'H':r) = Format_UnixSecond : toFormatElem r
+    -----------------------------------------------------------
+    toFormatElem ('T':'Z':'H':'M':r)     = Format_TzHM : toFormatElem r
+    toFormatElem ('T':'Z':'H':':':'M':r) = Format_TzHM_Colon : toFormatElem r
+    toFormatElem ('T':'Z':'O':'F':'S':r) = Format_Tz_Offset : toFormatElem r
+    -----------------------------------------------------------
+    toFormatElem ('\\':c:r)          = Format_Text c : toFormatElem r
+    toFormatElem (' ':r)             = Format_Spaces : toFormatElem r
+    toFormatElem (c:r)               = Format_Text c : toFormatElem r
+
+instance TimeFormat ISO8601_Date where
+  toFormat _ =
+    TimeFormatString [Format_Year,dash,Format_Month2,dash,Format_Day2]
+   where
+    dash = Format_Text '-'
+
+instance TimeFormat ISO8601_DateAndTime where
+  toFormat _ = TimeFormatString
+    [ Format_Year,dash,Format_Month2,dash,Format_Day2 -- date
+    , Format_Text 'T'
+    , Format_Hour,colon,Format_Minute,colon,Format_Second -- time
+    , Format_TzHM_Colon_Z -- zero UTC offset (Z) or timezone offset with colon +HH:MM
+    ]
+   where
+    dash = Format_Text '-'
+    colon = Format_Text ':'
+
+monthFromShort :: String -> Either String Month
+monthFromShort str =
+  case str of
+    "Jan" -> Right January
+    "Feb" -> Right February
+    "Mar" -> Right March
+    "Apr" -> Right April
+    "May" -> Right May
+    "Jun" -> Right June
+    "Jul" -> Right July
+    "Aug" -> Right August
+    "Sep" -> Right September
+    "Oct" -> Right October
+    "Nov" -> Right November
+    "Dec" -> Right December
+    _     -> Left $ "unknown month: " ++ str
+
+printWith ::
+     (TimeFormat format, Timeable t)
+  => format
+  -> TimezoneOffset
+  -> t
+  -> String
+printWith fmt tzOfs@(TimezoneOffset tz) t = concatMap fmtToString fmtElems
+ where
+  fmtToString Format_Year     = show (dateYear date)
+  fmtToString Format_Year4    = pad4 (dateYear date)
+  fmtToString Format_Year2    = pad2 (dateYear date - 1900)
+  fmtToString Format_Month2   = pad2 (fromEnum (dateMonth date) + 1)
+  fmtToString Format_Month    = show (fromEnum (dateMonth date) + 1)
+  fmtToString Format_MonthName_Short = take 3 $ show (dateMonth date)
+  fmtToString Format_Day2     = pad2 (dateDay date)
+  fmtToString Format_Day      = show (dateDay date)
+  fmtToString Format_Hour     = pad2 (fromIntegral (todHour tm) :: Int)
+  fmtToString Format_Minute   = pad2 (fromIntegral (todMin tm) :: Int)
+  fmtToString Format_Second   = pad2 (fromIntegral (todSec tm) :: Int)
+  fmtToString Format_MilliSecond = padN 3 (ns `div` 1000000)
+  fmtToString Format_MicroSecond = padN 3 ((ns `div` 1000) `mod` 1000)
+  fmtToString Format_NanoSecond = padN 3 (ns `mod` 1000)
+  fmtToString (Format_Precision n)
+      | n >= 1 && n <= 9 = padN n (ns `div` (10 ^ (9 - n)))
+      | otherwise        = error "invalid precision format"
+  fmtToString Format_UnixSecond = show unixSecs
+  fmtToString Format_TimezoneName   = "" --
+  fmtToString Format_Tz_Offset = show tz
+  fmtToString Format_TzHM = show tzOfs
+  fmtToString Format_TzHM_Colon_Z
+      | tz == 0   = "Z"
+      | otherwise = fmtToString Format_TzHM_Colon
+  fmtToString Format_TzHM_Colon =
+      let (tzH, tzM) = abs tz `divMod` 60
+          sign = if tz < 0 then "-" else "+"
+       in sign ++ pad2 tzH ++ ":" ++ pad2 tzM
+  fmtToString Format_Spaces   = " "
+  fmtToString (Format_Text c) = [c]
+  fmtToString f = error ("unimplemented printing format: " ++ show f)
+
+  (TimeFormatString fmtElems) = toFormat fmt
+
+  (Elapsed (Seconds unixSecs)) = timeGetElapsed t
+  (DateTime date tm) = timeGetDateTimeOfDay t
+  (NanoSeconds ns) = timeGetNanoSeconds t
+
+-- | Given the specified format, pretty print the given local time.
+localTimePrint ::
+     (TimeFormat format, Timeable t)
+  => format      -- ^ The format to use for printing.
+  -> LocalTime t -- ^ The local time to print.
+  -> String
+localTimePrint fmt lt =
+  localTimeUnwrap $ fmap (printWith fmt (localTimeGetTimezone lt)) lt
+
+-- | Given the specified format, pretty print the given time.
+timePrint ::
+     (TimeFormat format, Timeable t)
+  => format -- ^ The format to use for printing.
+  -> t      -- ^ The time to print.
+  -> String
+timePrint fmt = printWith fmt timezone_UTC
+
+-- | Given the specified format, try to parse the given string as time value.
+--
+-- On failure, the parsing function returns the reason of the failure.
+--
+-- If successful, yield the parsed value and the remaining unparsed string.
+localTimeParseE ::
+     TimeFormat format
+  => format -- ^ The format to use for parsing.
+  -> String -- ^ The string to parse.
+  -> Either (TimeFormatElem, String) (LocalTime DateTime, String)
+localTimeParseE fmt = loop ini fmtElems
+ where
+  (TimeFormatString fmtElems) = toFormat fmt
+
+  toLocal (dt, tz) = localTime tz dt
+
+  loop acc []    s  = Right (toLocal acc, s)
+  loop _   (x:_) [] = Left (x, "empty")
+  loop acc (x:xs) s =
+    case processOne acc x s of
+      Left err         -> Left (x, err)
+      Right (nacc, s') -> loop nacc xs s'
+
+  processOne _   _               []     = Left "empty"
+  processOne acc (Format_Text c) (x:xs)
+    | c == x    = Right (acc, xs)
+    | otherwise = Left ("unexpected char, got: " ++ show c)
+
+  processOne acc Format_Year s =
+    onSuccess (\y -> modDate (setYear y) acc) $ isNumber s
+  processOne acc Format_Year4 s =
+    onSuccess (\y -> modDate (setYear y) acc) $ getNDigitNum 4 s
+  processOne acc Format_Year2 s = onSuccess
+    ( \y -> let year = if y < 70 then y + 2000 else y + 1900
+            in  modDate (setYear year) acc
+    )
+    $ getNDigitNum 2 s
+  processOne acc Format_Month2 s = onSuccess
+    ( \m -> modDate (setMonth $ toEnum ((fromIntegral m - 1) `mod` 12)) acc
+    )
+    $ getNDigitNum 2 s
+  processOne acc Format_MonthName_Short s =
+    onSuccess (\m -> modDate (setMonth m) acc) $ getMonth s
+  processOne acc Format_Day2 s =
+    onSuccess (\d -> modDate (setDay d) acc) $ getNDigitNum 2 s
+  processOne acc Format_Hour s =
+    onSuccess (\h -> modTime (setHour h) acc) $ getNDigitNum 2 s
+  processOne acc Format_Minute s =
+    onSuccess (\mi -> modTime (setMin mi) acc) $ getNDigitNum 2 s
+  processOne acc Format_Second s =
+    onSuccess (\sec -> modTime (setSec sec) acc) $ getNDigitNum 2 s
+  processOne acc Format_MilliSecond s =
+    onSuccess (\ms -> modTime (setNsMask (6,3) ms) acc) $ getNDigitNum 3 s
+  processOne acc Format_MicroSecond s =
+    onSuccess (\us -> modTime (setNsMask (3,3) us) acc) $ getNDigitNum 3 s
+  processOne acc Format_NanoSecond s =
+    onSuccess (\ns -> modTime (setNsMask (0,3) ns) acc) $ getNDigitNum 3 s
+  processOne acc (Format_Precision p) s =
+    onSuccess (\num -> modTime (setNS num) acc) $ getNDigitNum p s
+  processOne acc Format_UnixSecond s =
+    onSuccess (\sec ->
+      let newDate =
+            dateTimeFromUnixEpochP $ flip ElapsedP 0 $ Elapsed $ Seconds sec
+      in  modDT (const newDate) acc) $ isNumber s
+  processOne acc Format_TzHM_Colon_Z a@(c:s)
+    | c == 'Z'  = Right (acc, s)
+    | otherwise = processOne acc Format_TzHM_Colon a
+  processOne acc Format_TzHM_Colon (c:s) =
+    parseHMSign True acc c s
+  processOne acc Format_TzHM (c:s) =
+    parseHMSign False acc c s
+
+  processOne acc Format_Spaces (' ':s) = Right (acc, s)
+  -- catch all for unimplemented format.
+  processOne _ f _ = error ("unimplemented parsing format: " ++ show f)
+
+  parseHMSign expectColon acc signChar afterSign =
+    case signChar of
+      '+' -> parseHM False expectColon afterSign acc
+      '-' -> parseHM True expectColon afterSign acc
+      _   -> parseHM False expectColon (signChar:afterSign) acc
+
+  parseHM isNeg True (h1:h2:':':m1:m2:xs) acc
+    | allDigits [h1,h2,m1,m2] = let tz = toTZ isNeg h1 h2 m1 m2
+                                in  Right (modTZ (const tz) acc, xs)
+    | otherwise = Left ("not digits chars: " ++ show [h1,h2,m1,m2])
+  parseHM isNeg False (h1:h2:m1:m2:xs) acc
+    | allDigits [h1,h2,m1,m2] = let tz = toTZ isNeg h1 h2 m1 m2
+                                in  Right (modTZ (const tz) acc, xs)
+    | otherwise = Left ("not digits chars: " ++ show [h1,h2,m1,m2])
+  parseHM _ _    _ _ = Left "invalid timezone format"
+
+  toTZ isNeg h1 h2 m1 m2 = TimezoneOffset ((if isNeg then negate else id) minutes)
+   where
+    minutes = (toInt [h1,h2] * 60) + toInt [m1,m2]
+
+  onSuccess f (Right (v, s')) = Right (f v, s')
+  onSuccess _ (Left s)        = Left s
+
+  isNumber :: Num a => String -> Either String (a, String)
+  isNumber s =
+    case span isDigit s of
+      ("", s2) -> Left ("no digits chars:" ++ s2)
+      (s1, s2) -> Right (toInt s1, s2)
+
+  getNDigitNum :: Int -> String -> Either String (Int64, String)
+  getNDigitNum n s =
+    case getNChar n s of
+      Left err                            -> Left err
+      Right (s1, s2)
+        | not (allDigits s1) -> Left ("not a digit chars in " ++ show s1)
+        | otherwise          -> Right (toInt s1, s2)
+
+  getMonth :: String -> Either String (Month, String)
+  getMonth s =
+    getNChar 3 s >>= \(s1, s2) -> monthFromShort s1 >>= \m -> Right (m, s2)
+
+  getNChar :: Int -> String -> Either String (String, String)
+  getNChar n s
+    | length s1 < n =
+        Left ("not enough chars: expecting " ++ show n ++ " got " ++ show s1)
+    | otherwise = Right (s1, s2)
+   where
+    (s1, s2) = splitAt n s
+
+  toInt :: Num a => String -> a
+  toInt = foldl (\acc w -> acc * 10 + fromIntegral (ord w - ord '0')) 0
+
+  allDigits = all isDigit
+
+  ini = (DateTime (Date 0 (toEnum 0) 0) (TimeOfDay 0 0 0 0), TimezoneOffset 0)
+
+  modDT   f (dt, tz) = (f dt, tz)
+  modDate f (DateTime d tp, tz) = (DateTime (f d) tp, tz)
+  modTime f (DateTime d tp, tz) = (DateTime d (f tp), tz)
+  modTZ   f (dt, tz) = (dt, f tz)
+
+  setYear :: Int64 -> Date -> Date
+  setYear  y (Date _ m d) = Date (fromIntegral y) m d
+  setMonth m (Date y _ d) = Date y m d
+  setDay   d (Date y m _) = Date y m (fromIntegral d)
+  setHour  h (TimeOfDay _ m s ns) = TimeOfDay (Hours h) m s ns
+  setMin   m (TimeOfDay h _ s ns) = TimeOfDay h (Minutes m) s ns
+  setSec   s (TimeOfDay h m _ ns) = TimeOfDay h m (Seconds s) ns
+  setNS    v (TimeOfDay h m s _ ) = TimeOfDay h m s (NanoSeconds v)
+
+  setNsMask :: (Int, Int) -> Int64 -> TimeOfDay -> TimeOfDay
+  setNsMask (shift, mask) val (TimeOfDay h mins seconds (NanoSeconds ns)) =
+    let (nsD,keepL) = ns `divMod` s
+        (keepH,_)   = nsD `divMod` m
+        v           = ((keepH * m + fromIntegral val) * s) + keepL
+    in  TimeOfDay h mins seconds (NanoSeconds v)
+   where
+    s = 10 ^ shift
+    m = 10 ^ mask
+
+-- | Given the specified format, try to parse the given string as time value.
+--
+-- On failure, returns 'Nothing'.
+--
+-- If successful, yields 'Just' the parsed value.
+--
+-- For more elaborate needs use 'localTimeParseE'.
+localTimeParse ::
+     TimeFormat format
+  => format -- ^ The format to use for parsing.
+  -> String -- ^ The string to parse.
+  -> Maybe (LocalTime DateTime)
+localTimeParse fmt s =
+  either (const Nothing) (Just . fst) $ localTimeParseE fmt s
+
+-- | Like 'localTimeParseE' but the time value is automatically converted to
+-- global time.
+timeParseE ::
+     TimeFormat format
+  => format
+  -> String
+  -> Either (TimeFormatElem, String) (DateTime, String)
+timeParseE fmt timeString =
+  (\(d, s) -> Right (localTimeToGlobal d, s)) =<< localTimeParseE fmt timeString
+
+-- | Like 'localTimeParse' but the time value is automatically converted to
+-- global time.
+timeParse :: TimeFormat format => format -> String -> Maybe DateTime
+timeParse fmt s = localTimeToGlobal `fmap` localTimeParse fmt s
+ src/Time/LocalTime.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE FlexibleInstances #-}
+
+{- |
+Module      : Time.LocalTime
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+A local time is a global time together with a timezone.
+-}
+
+module Time.LocalTime
+  ( -- * Local time
+    -- ** Local time type
+    LocalTime
+    -- ** Local time creation and manipulation
+  , localTime
+  , localTimeUnwrap
+  , localTimeToGlobal
+  , localTimeFromGlobal
+  , localTimeGetTimezone
+  , localTimeSetTimezone
+  , localTimeConvert
+  ) where
+
+import           Time.Diff ( elapsedTimeAddSecondsP )
+import           Time.Time ( Time, Timeable (..), timeConvert )
+import           Time.Types ( TimezoneOffset (..), timezoneOffsetToSeconds )
+
+-- | Type representing local times.
+data LocalTime t = LocalTime
+  { localTimeUnwrap      :: t
+    -- ^ The local time.
+  , localTimeGetTimezone :: TimezoneOffset
+    -- ^ The timezone offset.
+  }
+
+-- FIXME add instance Read too.
+
+instance Show t => Show (LocalTime t) where
+  show (LocalTime t tz) = show t ++ show tz
+
+instance Eq t => Eq (LocalTime t) where
+  LocalTime t1 tz1 == LocalTime t2 tz2 = tz1 == tz2 && t1 == t2
+
+instance (Ord t, Time t) => Ord (LocalTime t) where
+  compare l1@(LocalTime g1 tz1) l2@(LocalTime g2 tz2) =
+    case compare tz1 tz2 of
+      EQ -> compare g1 g2
+      _  -> let t1 = localTimeToGlobal l1
+                t2 = localTimeToGlobal l2
+            in  compare t1 t2
+
+instance Functor LocalTime where
+  fmap f (LocalTime t tz) = LocalTime (f t) tz
+
+-- | For the given timezone offset and time value (assumed to be the local
+-- time), yield the corresponding local time.
+localTime ::
+     Time t
+  => TimezoneOffset
+  -> t
+     -- ^ The local time.
+  -> LocalTime t
+localTime tz t = LocalTime t tz
+
+-- | For the given t'LocalTime' value, yield the corresponding global time.
+localTimeToGlobal :: Time t => LocalTime t -> t
+localTimeToGlobal (LocalTime local tz)
+  | tz == TimezoneOffset 0 = local
+  | otherwise =
+      timeConvert $ elapsedTimeAddSecondsP (timeGetElapsedP local) tzSecs
+ where
+  tzSecs = negate $ timezoneOffsetToSeconds tz
+
+-- | For the given time value, yield the corresponding t'LocalTime' value
+-- assuming that there is no timezone offset.
+localTimeFromGlobal :: Time t => t -> LocalTime t
+localTimeFromGlobal = localTime (TimezoneOffset 0)
+
+-- | For the given timezone offset and local time, yield the corresponding local
+-- time.
+localTimeSetTimezone :: Time t => TimezoneOffset -> LocalTime t -> LocalTime t
+localTimeSetTimezone tz currentLocal@(LocalTime t currentTz)
+  | diffTz == 0 = currentLocal
+  | otherwise   = LocalTime (timeConvert t') tz
+ where
+  t'        = elapsedTimeAddSecondsP (timeGetElapsedP t) diffTz
+  diffTz    = timezoneOffsetToSeconds tz - timezoneOffsetToSeconds currentTz
+
+-- | For the given local time of one type, yield the corresponding local time of
+-- a different type. This will not compile unless the compiler can infer the
+-- types of the two local times.
+localTimeConvert :: (Time t1, Time t2) => LocalTime t1 -> LocalTime t2
+localTimeConvert = fmap timeConvert
+ src/Time/System.hs view
@@ -0,0 +1,64 @@+{- |
+Module      : Time.System
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Get the system timezone and current time value in multiple formats.
+-}
+
+module Time.System
+  (
+    -- * Current time, in computer-friendly format
+    timeCurrent
+  , timeCurrentP
+    -- * Current time, in human-friendly format
+  , dateCurrent
+  , localDateCurrent
+  , localDateCurrentAt
+    -- * System timezone
+  , timezoneCurrent
+  ) where
+
+import           Time.Internal
+                   ( systemGetElapsed, systemGetElapsedP, systemGetTimezone )
+import           Time.LocalTime
+                   ( LocalTime, localTimeFromGlobal, localTimeSetTimezone )
+import           Time.Time ( timeGetDateTimeOfDay )
+import           Time.Types ( DateTime, Elapsed, ElapsedP, TimezoneOffset )
+
+-- | Get the current number of seconds elapsed since the start of the Unix
+-- epoch.
+timeCurrent :: IO Elapsed
+timeCurrent = systemGetElapsed
+
+-- | Get the current number of seconds and nanoseconds since the start of the
+-- Unix epoch (1970-01-01 00:00:00 UTC).
+timeCurrentP :: IO ElapsedP
+timeCurrentP = systemGetElapsedP
+
+-- | Get the current global date.
+--
+-- This is equivalent to:
+--
+-- > timeGetDateTimeOfDay `fmap` timeCurrentP
+dateCurrent :: IO DateTime
+dateCurrent = timeGetDateTimeOfDay <$> timeCurrentP
+
+-- | Get the localized date by using 'timezoneCurrent' and 'dateCurrent'.
+localDateCurrent :: IO (LocalTime DateTime)
+localDateCurrent = localTimeSetTimezone
+  <$> timezoneCurrent
+  <*> (localTimeFromGlobal <$> dateCurrent)
+
+-- | Get the localized date at a specific timezone offset.
+localDateCurrentAt :: TimezoneOffset -> IO (LocalTime DateTime)
+localDateCurrentAt tz =
+  localTimeSetTimezone tz . localTimeFromGlobal <$> dateCurrent
+
+-- | Get the current timezone offset.
+--
+-- This includes daylight saving time when it is in operation.
+timezoneCurrent :: IO TimezoneOffset
+timezoneCurrent = systemGetTimezone
+ src/Time/Time.hs view
@@ -0,0 +1,211 @@+{-# LANGUAGE ExistentialQuantification #-}
+
+{- |
+Module      : Time.Time
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Types representing points in time, and associated type classes, and durations
+of time, and associated functions.
+-}
+
+module Time.Time
+  ( -- * Classes for conversion
+    Time (..)
+  , Timeable (..)
+    -- * Conversion
+  , timeConvert
+    -- * Date and time
+  , timeGetDate
+  , timeGetDateTimeOfDay
+  , timeGetTimeOfDay
+    -- * Arithmetic
+  , Duration (..)
+  , Period (..)
+  , timeAdd
+  , timeDiff
+  , timeDiffP
+  , dateAddPeriod
+  ) where
+
+import           Foreign.C.Types ( CTime (..) )
+import           Time.Calendar ( dateTimeToUnixEpoch )
+import           Time.Diff
+                   ( Duration (..), Period (..), dateAddPeriod
+                   , elapsedTimeAddSecondsP
+                   )
+import           Time.Internal ( dateTimeFromUnixEpoch, dateTimeFromUnixEpochP )
+import           Time.Types
+                   ( Date, DateTime (..), Elapsed (..), ElapsedP (..)
+                   , NanoSeconds (..), Seconds (..), TimeInterval (..)
+                   , TimeOfDay (..)
+                   )
+
+-- | A type class promising functionality for:
+--
+-- * converting a value of the type in question to a t'Elapsed' value or
+--   a t'ElapsedP' value; and
+--
+-- * yielding separately a nanoseconds component of the value of the type in
+--   question (should yield @0@ when the type is less precise than seconds).
+--
+class Timeable t where
+  -- | Convert the given value to the number of elapsed seconds and nanoseconds
+  -- since the start of the Unix epoch (1970-01-01 00:00:00 UTC).
+  timeGetElapsedP :: t -> ElapsedP
+
+  -- | Convert the given value to the number of elapsed seconds since the start
+  -- of the Unix epoch (1970-01-01 00:00:00 UTC).
+  --
+  -- Defaults to 'timeGetElapsedP'.
+  timeGetElapsed :: t -> Elapsed
+  timeGetElapsed t = e
+   where
+    ElapsedP e _ = timeGetElapsedP t
+
+  -- | Optionally, for the given value, yield the number of nanoseconds
+  -- component.
+  --
+  -- If the type in question does not provide sub-second precision, should yield
+  -- @0@.
+  --
+  -- Defaults to 'timeGetElapsedP'. For efficiency, if the type in question does
+  -- not provide sub-second precision, it is a good idea to override this
+  -- method.
+  timeGetNanoSeconds :: t -> NanoSeconds
+  timeGetNanoSeconds t = ns where ElapsedP _ ns = timeGetElapsedP t
+
+-- | A type class promising functionality for converting t'ElapsedP' values
+-- and t'Elapsed' values to values of the type in question.
+class Timeable t => Time t where
+  -- | Convert from a number of elapsed seconds and nanoseconds since the start
+  -- of the Unix epoch (1970-01-01 00:00:00 UTC).
+  timeFromElapsedP :: ElapsedP -> t
+
+  -- | Convert from a number of elapsed seconds since the start of the Unix
+  -- epoch (1970-01-01 00:00:00 UTC).
+  --
+  -- Defaults to 'timeFromElapsedP'.
+  timeFromElapsed :: Elapsed -> t
+  timeFromElapsed e = timeFromElapsedP (ElapsedP e 0)
+
+instance Timeable CTime where
+  timeGetElapsedP c         = ElapsedP (timeGetElapsed c) 0
+
+  timeGetElapsed  (CTime c) = Elapsed (Seconds $ fromIntegral c)
+
+  timeGetNanoSeconds _ = 0
+
+instance Time CTime where
+  timeFromElapsedP (ElapsedP e _)       = timeFromElapsed e
+
+  timeFromElapsed (Elapsed (Seconds c)) = CTime (fromIntegral c)
+
+instance Timeable Elapsed where
+  timeGetElapsedP  e = ElapsedP e 0
+
+  timeGetElapsed   e = e
+
+  timeGetNanoSeconds _ = 0
+
+instance Time Elapsed where
+  timeFromElapsedP (ElapsedP e _) = e
+
+  timeFromElapsed  e = e
+
+instance Timeable ElapsedP where
+  timeGetElapsedP    e               = e
+  timeGetNanoSeconds (ElapsedP _ ns) = ns
+
+instance Time ElapsedP where
+  timeFromElapsedP   e               = e
+
+instance Timeable Date where
+  timeGetElapsedP d  = timeGetElapsedP (DateTime d (TimeOfDay 0 0 0 0))
+
+instance Time Date where
+  timeFromElapsedP (ElapsedP elapsed _) = d
+   where
+    (DateTime d _) = dateTimeFromUnixEpoch elapsed
+
+instance Timeable DateTime where
+  timeGetElapsedP d = ElapsedP (dateTimeToUnixEpoch d) (timeGetNanoSeconds d)
+  timeGetElapsed = dateTimeToUnixEpoch
+  timeGetNanoSeconds (DateTime _ (TimeOfDay _ _ _ ns)) = ns
+
+instance Time DateTime where
+  timeFromElapsedP = dateTimeFromUnixEpochP
+
+-- | Convert from one time representation to another. This will not compile
+-- unless the compiler can infer the types.
+--
+-- Specialized functions are available for built-in types:
+--
+-- * 'timeGetDate'
+--
+-- * 'timeGetDateTimeOfDay'
+--
+-- * 'timeGetElapsed'
+--
+-- * 'timeGetElapsedP'
+timeConvert :: (Timeable t1, Time t2) => t1 -> t2
+timeConvert t1 = timeFromElapsedP (timeGetElapsedP t1)
+{-# INLINE[2] timeConvert #-}
+{-# RULES "timeConvert/ID" timeConvert = id #-}
+{-# RULES "timeConvert/ElapsedP" timeConvert = timeGetElapsedP #-}
+{-# RULES "timeConvert/Elapsed" timeConvert = timeGetElapsed #-}
+
+-- | For the given value of a point in time, yield the corresponding date (a
+-- specialization of 'timeConvert').
+timeGetDate :: Timeable t => t -> Date
+timeGetDate t = d where (DateTime d _) = timeGetDateTimeOfDay t
+{-# INLINE[2] timeGetDate #-}
+{-# RULES "timeGetDate/ID" timeGetDate = id #-}
+{-# RULES "timeGetDate/DateTime" timeGetDate = dtDate #-}
+
+-- | For the given value for a point in time, yield the corresponding time
+-- (a specialization of 'timeConvert').
+timeGetTimeOfDay :: Timeable t => t -> TimeOfDay
+timeGetTimeOfDay t = tod where (DateTime _ tod) = timeGetDateTimeOfDay t
+{-# INLINE[2] timeGetTimeOfDay #-}
+{-# RULES "timeGetTimeOfDay/Date" timeGetTimeOfDay = const (TimeOfDay 0 0 0 0) #-}
+{-# RULES "timeGetTimeOfDay/DateTime" timeGetTimeOfDay = dtTime #-}
+
+-- | For the given value for a point in time, yield the corresponding date and
+-- time (a specialization of 'timeConvert').
+timeGetDateTimeOfDay :: Timeable t => t -> DateTime
+timeGetDateTimeOfDay t = dateTimeFromUnixEpochP $ timeGetElapsedP t
+{-# INLINE[2] timeGetDateTimeOfDay #-}
+{-# RULES "timeGetDateTimeOfDay/ID" timeGetDateTimeOfDay = id #-}
+{-# RULES "timeGetDateTimeOfDay/Date" timeGetDateTimeOfDay = flip DateTime (TimeOfDay 0 0 0 0) #-}
+
+-- | Add the given period of time to the given value for a point time.
+--
+-- Example:
+--
+-- > t1 `timeAdd` mempty { durationHours = 12 }
+timeAdd :: (Time t, TimeInterval ti) => t -> ti -> t
+timeAdd t ti =
+  timeFromElapsedP $ elapsedTimeAddSecondsP (timeGetElapsedP t) (toSeconds ti)
+
+-- | For the two given points in time, yields the difference in seconds
+-- between them.
+--
+-- Effectively:
+--
+-- > t2 `timeDiff` t1 = t2 - t1
+timeDiff :: (Timeable t1, Timeable t2) => t1 -> t2 -> Seconds
+timeDiff t1 t2 = sec where (Elapsed sec) = timeGetElapsed t1 - timeGetElapsed t2
+
+-- | For the two given points in time, yields the difference in seconds and
+-- nanoseconds between them.
+--
+-- Effectively:
+--
+-- > @t2 `timeDiffP` t1 = t2 - t1
+timeDiffP :: (Timeable t1, Timeable t2) => t1 -> t2 -> (Seconds, NanoSeconds)
+timeDiffP t1 t2 = (sec, ns)
+ where
+  (ElapsedP (Elapsed sec) ns) = timeGetElapsedP t1 - timeGetElapsedP t2
+ src/Time/Timezone.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE ExistentialQuantification #-}
+
+{- |
+Module      : Time.Timezone
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Timezone utilities.
+-}
+
+module Time.Timezone
+  ( Timezone (..)
+  , UTC (..)
+  , TimezoneMinutes (..)
+  ) where
+
+-- | A type class promising timezone-related functionality.
+class Timezone tz where
+  -- | Offset in minutes from UTC. Valid values should be between @-12 * 60@ and
+  -- @+14 * 60@.
+  timezoneOffset :: tz -> Int
+
+  -- | The name of the timezone.
+  --
+  -- Default implementation is an +-HH:MM encoding of the 'timezoneOffset'.
+  timezoneName :: tz -> String
+  timezoneName = tzMinutesPrint . timezoneOffset
+
+-- | Simple timezone containing the number of minutes difference with UTC.
+--
+-- Valid values should be between @-12 * 60@ and @+14 * 60@.
+newtype TimezoneMinutes = TimezoneMinutes Int
+  deriving (Eq, Ord, Show)
+
+-- | Type representing Universal Time Coordinated (UTC).
+data UTC = UTC
+  deriving (Eq, Ord, Show)
+
+instance Timezone UTC where
+  timezoneOffset _ = 0
+  timezoneName _   = "UTC"
+
+instance Timezone TimezoneMinutes where
+  timezoneOffset (TimezoneMinutes minutes) = minutes
+
+-- | Print a minute offset in format: (+-)HH:MM.
+tzMinutesPrint :: Int -> String
+tzMinutesPrint offset =
+      (if offset > 0 then '+' else '-')
+    : (pad0 h ++ ":" ++ pad0 m)
+ where
+  (h, m) = abs offset `divMod` 60
+  pad0 v
+    | v < 10    = '0':show v
+    | otherwise = show v
+ src/Time/Types.hs view
@@ -0,0 +1,270 @@+{-# LANGUAGE DeriveDataTypeable         #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+{- |
+Module      : Time.Types
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+
+Time-related types.
+
+In principle, some units could hold infinite values. In practice, 'Int64' for
+seconds (about @+/- 9e18@) and 'Int' for years is sufficient.
+-}
+
+module Time.Types
+  ( -- * Time units
+    NanoSeconds (..)
+  , Seconds (..)
+  , Minutes (..)
+  , Hours (..)
+    -- * Calendar enumerations
+  , Month (..)
+  , WeekDay (..)
+    -- * Points in time
+    -- ** Elapsed time since the start of the Unix epoch
+  , Elapsed (..)
+  , ElapsedP (..)
+    -- ** Date, time, and date and time
+  , Date (..)
+  , TimeOfDay (..)
+  , DateTime (..)
+    -- ** Local time and timezone-related
+  , TimezoneOffset (..)
+  , timezoneOffsetToSeconds
+  , timezone_UTC
+    -- * Conversion of periods of time
+  , TimeInterval (..)
+  ) where
+
+import           Control.DeepSeq ( NFData (..) )
+import           Data.Data ( Data )
+import           Data.Int ( Int64 )
+import           Data.Ratio ( (%) )
+import           Time.Utils ( pad2 )
+
+-- | Type class promising functionality for:
+--
+-- * converting a value of the type in question to a number of seconds; and
+--
+-- * converting a t'Seconds' value to a pair of a value of the type in question
+--   and a remaining number of seconds.
+class TimeInterval i where
+  -- | For the given value, yield a corresponding number of seconds.
+  toSeconds   :: i -> Seconds
+
+  -- | For the given number of seconds, yield a pair of the corresponding value
+  -- of the type in queston and a remaining number of seconds.
+  fromSeconds :: Seconds -> (i, Seconds)
+
+-- | Type representing numbers of nanoseconds.
+newtype NanoSeconds = NanoSeconds Int64
+  deriving (Data, Eq, NFData, Num, Ord, Read)
+
+instance Show NanoSeconds where
+  show (NanoSeconds v) = shows v "ns"
+
+instance TimeInterval NanoSeconds where
+  toSeconds (NanoSeconds ns) = Seconds (ns `div` 1000000000)
+  fromSeconds (Seconds s) = (NanoSeconds (s * 1000000000), 0)
+
+-- | Type representing numbers of seconds.
+newtype Seconds = Seconds Int64
+  deriving (Data, Eq, Enum, Integral, NFData, Num, Ord, Read, Real)
+
+instance Show Seconds where
+  show (Seconds s) = shows s "s"
+
+instance TimeInterval Seconds where
+  toSeconds   = id
+  fromSeconds s = (s, 0)
+
+-- | Type representing numbers of minutes.
+newtype Minutes = Minutes Int64
+  deriving (Data, Eq, Enum, Integral, NFData, Num, Ord, Read, Real)
+
+instance Show Minutes where
+  show (Minutes s) = shows s "m"
+
+instance TimeInterval Minutes where
+  toSeconds (Minutes m)   = Seconds (m * 60)
+  fromSeconds (Seconds s) = (Minutes m, Seconds s')
+   where
+    (m, s') = s `divMod` 60
+
+-- | Type representing numbers of hours.
+newtype Hours = Hours Int64
+  deriving (Data, Eq, Enum, Integral, NFData, Num, Ord, Read, Real)
+
+instance Show Hours where
+  show (Hours s) = shows s "h"
+
+instance TimeInterval Hours where
+  toSeconds (Hours h)     = Seconds (h * 3600)
+  fromSeconds (Seconds s) = (Hours h, Seconds s')
+   where
+    (h, s') = s `divMod` 3600
+
+-- | Type representing numbers of seconds elapsed since the start of the Unix
+-- epoch (1970-01-01 00:00:00 UTC).
+newtype Elapsed = Elapsed Seconds
+  deriving (Data, Eq, NFData, Num, Ord, Read)
+
+instance Show Elapsed where
+  show (Elapsed s) = show s
+
+-- | Type representing numbers of seconds and nanoseconds elapsed since the
+-- start of the Unix epoch (1970-01-01 00:00:00 UTC).
+data ElapsedP = ElapsedP {-# UNPACK #-} !Elapsed {-# UNPACK #-} !NanoSeconds
+  deriving (Data, Eq, Ord, Read)
+
+instance Show ElapsedP where
+  show (ElapsedP e ns) = shows e ('.' : show ns)
+
+instance NFData ElapsedP where
+  rnf e = e `seq` ()
+
+instance Num ElapsedP where
+  (+) = addElapsedP
+
+  (-) = subElapsedP
+
+  (ElapsedP e1 ns1) * (ElapsedP e2 ns2) = ElapsedP (e1*e2) (ns1*ns2)
+
+  negate (ElapsedP e ns) = ElapsedP (negate e) ns
+
+  abs (ElapsedP e ns)    = ElapsedP (abs e) ns
+
+  signum (ElapsedP e ns) = ElapsedP (signum e) ns
+
+  fromInteger i          = ElapsedP (Elapsed (fromIntegral i)) 0
+
+addElapsedP :: ElapsedP -> ElapsedP -> ElapsedP
+addElapsedP (ElapsedP e1 (NanoSeconds ns1)) (ElapsedP e2 (NanoSeconds ns2)) =
+  let notNormalizedNS = ns1 + ns2
+      (retainedNS, ns) = notNormalizedNS `divMod` 1000000000
+  in  ElapsedP (e1 + e2 + Elapsed (Seconds retainedNS)) (NanoSeconds ns)
+
+subElapsedP :: ElapsedP -> ElapsedP -> ElapsedP
+subElapsedP (ElapsedP e1 (NanoSeconds ns1)) (ElapsedP e2 (NanoSeconds ns2)) =
+  let notNormalizedNS = ns1 - ns2
+      notNormalizedS  = e1 - e2
+  in  if notNormalizedNS < 0
+        then
+          ElapsedP
+            (notNormalizedS - oneSecond)
+            (NanoSeconds (1000000000 + notNormalizedNS))
+        else
+          ElapsedP notNormalizedS (NanoSeconds notNormalizedNS)
+ where
+  oneSecond :: Elapsed
+  oneSecond = Elapsed $ Seconds 1
+
+instance Real ElapsedP where
+  toRational (ElapsedP (Elapsed (Seconds s)) (NanoSeconds 0)) =
+    fromIntegral s
+
+  toRational (ElapsedP (Elapsed (Seconds s)) (NanoSeconds ns)) =
+    fromIntegral s + (fromIntegral ns % 1000000000)
+
+-- | Type representing months of the Julian or Gregorian year.
+data Month =
+    January
+  | February
+  | March
+  | April
+  | May
+  | June
+  | July
+  | August
+  | September
+  | October
+  | November
+  | December
+  deriving (Bounded, Data, Eq, Enum, Ord, Read, Show)
+
+-- | Type representing days of the week. The enumeration starts on Sunday.
+data WeekDay =
+    Sunday
+  | Monday
+  | Tuesday
+  | Wednesday
+  | Thursday
+  | Friday
+  | Saturday
+  deriving (Bounded, Data, Eq, Enum, Ord, Read, Show)
+
+-- | Type representing offsets in minutes against UTC to obtain local time from
+-- UTC. A positive number represents a location east of where UTC is local time
+-- and a negative number represents a location west of where UTC is local time.
+--
+-- LocalTime t (-300) -- t represents a time at UTC-5
+--
+-- LocalTime t (+480) -- t represents a time at UTC+8
+--
+-- Should be between @(-12 * 60)@ and @(+14 * 60)@.
+--
+-- For example, in timezone AEDT (Australian Eastern Daylight Time) (UTC+11),
+-- local time is 15:47. Consequently, UTC time is 04:47 and the timezone offset
+-- is v'TimezoneOffset' @660@ (in minutes).
+newtype TimezoneOffset = TimezoneOffset
+  { timezoneOffsetToMinutes :: Int -- ^ The number of minutes.
+  }
+  deriving (Data, Eq, NFData, Ord)
+
+-- | For the given timezone offset, yield the corresponding number of seconds.
+timezoneOffsetToSeconds :: TimezoneOffset -> Seconds
+timezoneOffsetToSeconds (TimezoneOffset ofs) = Seconds (fromIntegral ofs * 60)
+
+instance Show TimezoneOffset where
+  show (TimezoneOffset tz) =
+    concat [if tz < 0 then "-" else "+", pad2 tzH, pad2 tzM]
+   where
+    (tzH, tzM) = abs tz `divMod` 60
+
+-- | The UTC timezone.
+--
+-- > timezoneOffsetToMinutes timezone_UTC == 0 -- True
+timezone_UTC :: TimezoneOffset
+timezone_UTC = TimezoneOffset 0
+
+-- | Type representing dates in the proleptic Gregorian calendar (the common
+-- calendar).
+data Date = Date
+  { dateYear  :: {-# UNPACK #-} !Int
+    -- ^ Year of the Common Era.
+  , dateMonth :: !Month
+    -- ^ Month of the year.
+  , dateDay   :: {-# UNPACK #-} !Int
+    -- ^ Day of the month, between 1 to 31.
+  }
+  deriving (Data, Eq, Ord, Read, Show)
+
+instance NFData Date where
+  rnf (Date y m d) = y `seq` m `seq` d `seq` ()
+
+-- | Type representing times as hour, minutes, seconds and nanoseconds.
+data TimeOfDay = TimeOfDay
+  { todHour :: {-# UNPACK #-} !Hours
+    -- ^ Hours, between 0 and 23.
+  , todMin  :: {-# UNPACK #-} !Minutes
+    -- ^ Minutes, between 0 and 59.
+  , todSec  :: {-# UNPACK #-} !Seconds
+    -- ^ Seconds, between 0 and 59. 60 when having leap second.
+  , todNSec :: {-# UNPACK #-} !NanoSeconds
+    -- ^ Nanoseconds, between 0 and 999999999.
+  }
+  deriving (Data, Eq, Ord, Read, Show)
+
+instance NFData TimeOfDay where
+  rnf (TimeOfDay h m s ns) = h `seq` m `seq` s `seq` ns `seq` ()
+
+-- | Type representing date and time.
+data DateTime = DateTime
+  { dtDate :: Date
+  , dtTime :: TimeOfDay
+  }
+  deriving (Data, Eq, Ord, Read, Show)
+
+instance NFData DateTime where
+  rnf (DateTime d t) = rnf d `seq` rnf t
+ src/Time/Utils.hs view
@@ -0,0 +1,37 @@+{- |
+Module      : Time.Utils
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+Some padding / formatting functions.
+-}
+
+module Time.Utils where
+
+-- | Pad a number to 2 digits.
+pad2 :: (Show a, Ord a, Num a, Integral a) => a -> String
+pad2 v
+  | v >= 100   = pad2 (v `mod` 100)
+  | v >= 10    = show v
+  | otherwise  = '0' : show v
+
+-- | Pad a number to 4 digits.
+pad4 :: (Show a, Ord a, Num a, Integral a) => a -> String
+pad4 v
+  | v >= 1000  = show v
+  | v >= 100   = '0' : show v
+  | v >= 10    = '0':'0' : show v
+  | otherwise  = '0':'0':'0': show v
+
+-- | Pad a number to at least N digits.
+--
+-- If the number is greater, no truncation happens.
+padN :: (Show a, Ord a, Num a, Integral a) => Int -> a -> String
+padN n v
+  | vlen >= n = vs
+  | otherwise = replicate (n - vlen) '0' ++ vs
+ where
+  vs = show v
+  vlen = length vs
+ src/unix/Time/Internal.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE ForeignFunctionInterface #-}
+
+{- |
+Module      : Time.Internal
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+This module depends on the operating system. This is the version for Unix-like
+operating systems.
+
+Time lowlevel helpers for the unix operating system.
+
+Depend on localtime_r and gmtime_r. Some obscure Unix system might not support
+them.
+-}
+
+module Time.Internal
+  ( dateTimeFromUnixEpochP
+  , dateTimeFromUnixEpoch
+  , systemGetTimezone
+  , systemGetElapsed
+  , systemGetElapsedP
+  ) where
+
+import           Foreign.C.Types ( CInt, CLong, CTime (..) )
+import           Foreign.Marshal.Alloc ( alloca, allocaBytesAligned )
+import           Foreign.Ptr ( Ptr, castPtr, nullPtr )
+import           Foreign.Storable ( Storable (..) )
+import           System.IO.Unsafe ( unsafePerformIO )
+import           Time.Types
+                   ( Date (..), DateTime (..), Elapsed (..), ElapsedP (..)
+                   , NanoSeconds (..), Seconds (..), TimeOfDay (..)
+                   , TimezoneOffset (..)
+                   )
+
+-- | Convert a Unix epoch precise to t'DateTime'.
+dateTimeFromUnixEpochP :: ElapsedP -> DateTime
+dateTimeFromUnixEpochP (ElapsedP e ns) = fromCP ns $ rawGmTime e
+
+-- | Convert a Unix epoch to t'DateTime'.
+dateTimeFromUnixEpoch :: Elapsed -> DateTime
+dateTimeFromUnixEpoch e = fromC $ rawGmTime e
+
+-- | Return the timezone offset in minutes.
+systemGetTimezone :: IO TimezoneOffset
+systemGetTimezone = TimezoneOffset . fromIntegral . flip div 60 <$> localTime 0
+
+--------------------------------------------------------------------------------
+-- | Return the current elapsedP.
+systemGetElapsedP :: IO ElapsedP
+systemGetElapsedP = allocaBytesAligned sofTimespec 8 $ \ptr -> do
+  c_clock_get ptr
+  toElapsedP <$> peek (castPtr ptr) <*> peekByteOff ptr sofCTime
+ where
+  sofTimespec = sofCTime + sofCLong
+  sofCTime = sizeOf (0 :: CTime)
+  sofCLong = sizeOf (0 :: CLong)
+  toElapsedP :: CTime -> CLong -> ElapsedP
+  toElapsedP (CTime sec) nsec =
+    ElapsedP (Elapsed $ Seconds (fromIntegral sec)) (fromIntegral nsec)
+
+-- | Return the current elapsed.
+systemGetElapsed :: IO Elapsed
+systemGetElapsed = allocaBytesAligned sofTimespec 8 $ \ptr -> do
+  c_clock_get ptr
+  toElapsed <$> peek (castPtr ptr)
+ where
+  sofTimespec = sizeOf (0 :: CTime) + sizeOf (0 :: CLong)
+  toElapsed :: CTime -> Elapsed
+  toElapsed (CTime sec) = Elapsed $ Seconds (fromIntegral sec)
+
+foreign import ccall unsafe "hourglass_clock_calendar"
+  c_clock_get :: Ptr CLong -> IO ()
+
+foreign import ccall unsafe "gmtime_r"
+  c_gmtime_r :: Ptr CTime -> Ptr CTm -> IO (Ptr CTm)
+
+foreign import ccall unsafe "localtime_r"
+  c_localtime_r :: Ptr CTime -> Ptr CTm -> IO (Ptr CTm)
+
+-- | Return a global time's struct tm based on the number of elapsed second
+-- since the start of the Unix epoch (1970-01-01 00:00:00 UTC).
+rawGmTime :: Elapsed -> CTm
+rawGmTime (Elapsed (Seconds s)) = unsafePerformIO callTime
+ where
+  callTime =
+    alloca $ \ctmPtr -> do
+    alloca $ \ctimePtr -> do
+      poke ctimePtr ctime
+      r <- c_gmtime_r ctimePtr ctmPtr
+      if r == nullPtr
+        then error "gmTime failed"
+        else peek ctmPtr
+  ctime = fromIntegral s
+{-# NOINLINE rawGmTime #-}
+
+-- | Return a local time's gmtoff (seconds east of UTC).
+--
+-- Use the ill-defined gmtoff (at offset 40) that might or might not be
+-- available for your platform. Worst case scenario it's not initialized
+-- properly.
+localTime :: Elapsed -> IO CLong
+localTime (Elapsed (Seconds s)) = callTime
+ where
+  callTime =
+    alloca $ \ctmPtr -> do
+    alloca $ \ctimePtr -> do
+      poke ctimePtr ctime
+      r <- c_localtime_r ctimePtr ctmPtr
+      if r == nullPtr
+        then error "localTime failed"
+        else peekByteOff ctmPtr 40
+  ctime = fromIntegral s
+
+-- | Represent the beginning of struct tm.
+data CTm = CTm
+  { ctmSec    :: CInt
+  , ctmMin    :: CInt
+  , ctmHour   :: CInt
+  , ctmMDay   :: CInt
+  , ctmMon    :: CInt
+  , ctmYear   :: CInt
+  }
+  deriving (Eq, Show)
+
+-- | Convert a C structure to a DateTime structure.
+fromC :: CTm -> DateTime
+fromC ctm = DateTime date time
+ where
+  date = Date
+    { dateYear  = fromIntegral $ ctmYear ctm + 1900
+    , dateMonth = toEnum $ fromIntegral $ ctmMon ctm
+    , dateDay   = fromIntegral $ ctmMDay ctm
+    }
+  time = TimeOfDay
+    { todHour = fromIntegral $ ctmHour ctm
+    , todMin  = fromIntegral $ ctmMin ctm
+    , todSec  = fromIntegral $ ctmSec ctm
+    , todNSec = 0
+    }
+
+-- | Similar to 'fromC' except with nanosecond precision.
+fromCP :: NanoSeconds -> CTm -> DateTime
+fromCP ns ctm = DateTime d (t { todNSec = ns })
+ where
+  (DateTime d t) = fromC ctm
+
+instance Storable CTm where
+  alignment _ = 8
+  sizeOf _    = 60 -- account for 9 ints, alignment + 2 unsigned long at end.
+  peek ptr    = do
+    CTm <$> peekByteOff intPtr 0
+        <*> peekByteOff intPtr 4
+        <*> peekByteOff intPtr 8
+        <*> peekByteOff intPtr 12
+        <*> peekByteOff intPtr 16
+        <*> peekByteOff intPtr 20
+   where
+    intPtr = castPtr ptr
+  poke ptr (CTm f0 f1 f2 f3 f4 f5) = do
+    mapM_
+      (uncurry (pokeByteOff intPtr))
+      [(0, f0), (4, f1), (8, f2), (12, f3), (16, f4), (20, f5)]
+    --pokeByteOff (castPtr ptr) 36 f9
+   where
+    intPtr = castPtr ptr
+ src/win/Time/Internal.hs view
@@ -0,0 +1,96 @@+{- |
+Module      : Time.Internal
+License     : BSD-style
+Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
+Stability   : experimental
+Portability : unknown
+
+This module depends on the operating system. This is the version for Windows.
+
+Time lowlevel helpers binding to Windows.
+-}
+
+module Time.Internal
+  ( dateTimeFromUnixEpochP
+  , dateTimeFromUnixEpoch
+  , systemGetTimezone
+  , systemGetElapsed
+  , systemGetElapsedP
+  ) where
+
+import           Data.Int ( Int64 )
+import           System.IO.Unsafe ( unsafePerformIO )
+import           System.Win32.Time
+                   ( FILETIME (..), SYSTEMTIME (..), TimeZoneId (..)
+                   , fileTimeToSystemTime, getSystemTimeAsFileTime
+                   , getTimeZoneInformation, tziBias, tziDaylightBias
+                   , tziStandardBias
+                   )
+import           Time.Types
+                   ( Date (..), DateTime (..), Elapsed (..), ElapsedP (..)
+                   , NanoSeconds (..), Seconds (..), TimeOfDay (..)
+                   , TimezoneOffset (..)
+                   )
+
+unixDiff :: Int64
+unixDiff = 11644473600
+
+toFileTime :: Elapsed -> FILETIME
+toFileTime (Elapsed (Seconds s)) = FILETIME val
+ where
+  val = fromIntegral (s + unixDiff) * 10000000
+
+toElapsedP :: FILETIME -> ElapsedP
+toElapsedP (FILETIME w) = ElapsedP (Elapsed $ Seconds s) (NanoSeconds ns)
+ where
+  (sWin, hundredNs) = w `divMod` 10000000
+  ns = fromIntegral (hundredNs * 100)
+  s = fromIntegral sWin - unixDiff
+
+toElapsed :: FILETIME -> Elapsed
+toElapsed (FILETIME w) = Elapsed (Seconds s)
+ where
+  s = fromIntegral (fst (w `divMod` 10000000)) - unixDiff
+
+callSystemTime :: Elapsed -> SYSTEMTIME
+callSystemTime e = unsafePerformIO (fileTimeToSystemTime (toFileTime e))
+{-# NOINLINE callSystemTime #-}
+
+dateTimeFromUnixEpochP :: ElapsedP -> DateTime
+dateTimeFromUnixEpochP (ElapsedP e ns) = toDateTime $ callSystemTime e
+ where
+  toDateTime (SYSTEMTIME wY wM _ wD wH wMin wS _) =
+    DateTime
+      (Date (fi wY) (toEnum $ fi $ wM - 1) (fi wD))
+      (TimeOfDay (fi wH) (fi wMin) (fi wS) ns)
+  fi :: (Integral a, Num b) => a -> b
+  fi = fromIntegral
+
+dateTimeFromUnixEpoch :: Elapsed -> DateTime
+dateTimeFromUnixEpoch e = toDateTime $ callSystemTime e
+ where
+  toDateTime (SYSTEMTIME wY wM _ wD wH wMin wS _) =
+    DateTime
+      (Date (fi wY) (toEnum $ fi $ wM - 1) (fi wD))
+      (TimeOfDay (fi wH) (fi wMin) (fi wS) 0)
+  fi :: (Integral a, Num b) => a -> b
+  fi = fromIntegral
+
+systemGetTimezone :: IO TimezoneOffset
+systemGetTimezone = do
+  (tzMode,tzInfo) <- getTimeZoneInformation
+  case tzMode of
+    TzIdDaylight -> return $ toTO (tziBias tzInfo + tziDaylightBias tzInfo)
+    TzIdStandard -> return $ toTO (tziBias tzInfo + tziStandardBias tzInfo)
+    TzIdUnknown  -> return $ toTO (tziBias tzInfo)
+ where
+  -- a negative value represent value how to go from local to UTC,
+  -- whereas TimezoneOffset represent the offset to go from UTC to local.
+  -- here we negate the bias to get the proper value represented.
+  toTO = TimezoneOffset . fromIntegral . negate
+
+systemGetElapsedP :: IO ElapsedP
+systemGetElapsedP = toElapsedP `fmap` getSystemTimeAsFileTime
+
+systemGetElapsed :: IO Elapsed
+systemGetElapsed = toElapsed `fmap` getSystemTimeAsFileTime
− tests/Bench.hs
@@ -1,93 +0,0 @@-{-# LANGUAGE ViewPatterns #-}
-{-# LANGUAGE BangPatterns #-}
-module Main (main) where
-
-import Gauge.Main
-import Data.Hourglass
-import System.Hourglass
-import TimeDB
-
-import Data.List (intercalate)
-import qualified Data.Time.Calendar as T
-import qualified Data.Time.Clock as T
-import qualified Data.Time.Clock.POSIX as T
-import qualified System.Locale as T
-
-timeToTuple :: T.UTCTime -> (Int, Int, Int, Int, Int, Int)
-timeToTuple utcTime = (fromIntegral y, m, d, h, mi, sec)
- where (!y,!m,!d)  = T.toGregorian (T.utctDay utcTime)
-       !daytime    = floor $ T.utctDayTime utcTime
-       (!dt, !sec) = daytime `divMod` 60
-       (!h , !mi)  = dt `divMod` 60
-
-timeToTupleDate :: T.UTCTime -> (Int, Int, Int)
-timeToTupleDate utcTime = (fromIntegral y, m, d)
-  where (!y,!m,!d)  = T.toGregorian (T.utctDay utcTime)
-
-elapsedToPosixTime :: Elapsed -> T.POSIXTime
-elapsedToPosixTime (Elapsed (Seconds s)) = fromIntegral s
-
-timePosixDict :: [ (Elapsed, T.POSIXTime) ]
-timePosixDict =
-    [-- (Elapsed 0, 0)
-    --, (Elapsed 1000000, 1000000)
-    --, (Elapsed 9000099, 9000099)
-    {-,-} (Elapsed 1398232846, 1398232846) -- currentish time (at the time of writing)
-    --, (Elapsed 5134000099, 5134000099)
-    --, (Elapsed 10000000000000, 10000000000000) -- year 318857 ..
-    ]
-
-dateDict :: [ (Int, Int, Int, Int, Int, Int) ]
-dateDict =
-    [{- (1970, 1, 1, 1, 1, 1)
-    , -}(2014, 5, 5, 5, 5, 5)
-    --, (2114, 11, 5, 5, 5, 5)
-    ]
-
-main :: IO ()
-main = defaultMain
-    [ bgroup "highlevel"   $ concatMap toHighLevel timePosixDict
-    , bgroup "to-dateTime" $ concatMap toCalendar timePosixDict
-    , bgroup "to-date"     $ concatMap toCalendarDate timePosixDict
-    , bgroup "utc-to-date" $ concatMap toCalendarUTC timePosixDict
-    , bgroup "to-posix"    $ concatMap toPosix dateDict
-    , bgroup "system"      fromSystem
-    ]
-  where toHighLevel (posixHourglass, posixTime) =
-            [ bench (showH posixHourglass) $ nf timeGetDateTimeOfDay posixHourglass
-            , bench (showT posixTime) $ nf T.posixSecondsToUTCTime posixTime
-            ]
-        toCalendar (posixHourglass, posixTime) =
-            [ bench (showH posixHourglass) $ nf timeGetDateTimeOfDay posixHourglass
-            , bench (showT posixTime) $ nf (timeToTuple . T.posixSecondsToUTCTime) posixTime
-            ]
-        toCalendarDate (posixHourglass, posixTime) =
-            [ bench (showH posixHourglass) $ nf timeGetDate posixHourglass
-            , bench (showT posixTime) $ nf (timeToTupleDate . T.posixSecondsToUTCTime) posixTime
-            ]
-        toCalendarUTC (posixHourglass, posixTime) = 
-            [ bench (showH posixHourglass) $ nf timeGetDateTimeOfDay posixHourglass
-            , bench (showT utcTime) $ nf timeToTuple utcTime
-            ]
-          where !utcTime = T.posixSecondsToUTCTime posixTime
-        toPosix v =
-            [ bench ("hourglass/" ++ n v) $ nf hourglass v
-            , bench ("time/" ++ n v) $ nf time v
-            ]
-          where n (y,m,d,h,mi,s) = (intercalate "-" $ map show [y,m,d]) ++ " " ++ (intercalate ":" $ map show [h,mi,s])
-                hourglass (y,m,d,h,mi,s) = timeGetElapsed $ DateTime (Date y (toEnum (m-1)) d) (TimeOfDay (fromIntegral h) (fromIntegral mi) (fromIntegral s) 0)
-                time      (y,m,d,h,mi,s) = let day = T.fromGregorian (fromIntegral y) m d
-                                               diffTime = T.secondsToDiffTime $ fromIntegral (h * 3600 + mi * 60 + s)
-                                            in T.utcTimeToPOSIXSeconds (T.UTCTime day diffTime)
-
-        fromSystem =
-            [ bench ("hourglass/p")    $ nfIO timeCurrent
-            , bench ("hourglass/ns")   $ nfIO timeCurrentP
-            , bench ("time/posixTime") $ nfIO T.getPOSIXTime
-            , bench ("time/utcTime")   $ nfIO T.getCurrentTime
-            ]
-
-        showH :: Show a => a -> String
-        showH a = "hourglass/" ++ show a
-        showT :: Show a => a -> String
-        showT a = "time/" ++ show a
tests/Tests.hs view
@@ -1,265 +1,311 @@-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE CPP #-}
-module Main where
-
-import Control.Applicative
-
-import Test.Tasty
-import Test.Tasty.QuickCheck
-import Test.Tasty.HUnit
+{-# OPTIONS_GHC -Wno-orphans            #-}
 
-import Data.Ratio
-import Data.Word
-import Data.Int
-import Data.Hourglass
-import Data.Hourglass.Epoch
+{-# LANGUAGE FlexibleInstances   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 
-import Foreign.Storable
-import Foreign.C.Types (CTime)
+module Main
+  ( main
+  ) where
 
+import qualified Control.Exception as E
+import           Control.Monad ( when )
+import           Data.Hourglass
+                   ( Date (..), DateTime (..), Duration (..), Elapsed (..)
+                   , ElapsedP (..), Hours (..), ISO8601_Date (..)
+                   , ISO8601_DateAndTime (..), LocalTime, Minutes (..)
+                   , Month (..), NanoSeconds (..), Period (..), Seconds (..)
+                   , Time, TimeFormat (..), TimeOfDay (..), TimezoneOffset (..)
+                   , WeekDay, dateAddPeriod, daysInMonth, getWeekDay, localTime
+                   , localTimeFromGlobal, localTimeGetTimezone, localTimeParseE
+                   , localTimeSetTimezone, localTimeToGlobal, timeConvert
+                   , timeGetDateTimeOfDay, timeGetElapsed, timeParseE, timePrint
+                   )
+import           Data.Hourglass.Epoch ( ElapsedSince, WindowsEpoch )
+import           Data.Int ( Int64 )
+import           Data.Ratio ( (%) )
 import qualified Data.Time.Calendar as T
 import qualified Data.Time.Clock as T
 import qualified Data.Time.Clock.POSIX as T
 import qualified Data.Time.Format as T
-#if MIN_VERSION_time(1,5,0)
-import qualified System.Locale as T hiding (defaultTimeLocale)
-#else
-import qualified System.Locale as T
-#endif
-
-import qualified Control.Exception as E
-
-import TimeDB
-
-tmPosix0 :: Elapsed
-tmPosix0 = fromIntegral (0 :: Word64)
-
-timePosix0 :: T.POSIXTime
-timePosix0 = fromIntegral (0 :: Word64)
+import           Test.Tasty ( TestTree, defaultMain, testGroup )
+import           Test.Tasty.HUnit
+                   ( Assertion, (@=?), assertEqual, assertFailure, testCase )
+import           Test.Tasty.QuickCheck
+                   ( Arbitrary (..), choose, elements, testProperty )
+import           TimeDB ( parseTimeConv )
+import           TimeRange ( dateRange, hiElapsed, loElapsed )
 
 elapsedToPosixTime :: Elapsed -> T.POSIXTime
 elapsedToPosixTime (Elapsed (Seconds s)) = fromIntegral s
 
 dateEqual :: LocalTime DateTime -> T.UTCTime -> Bool
-dateEqual localtime utcTime =
-    and [ fromIntegral y == y', m' == (fromEnum m + 1), d' == d
-        , fromIntegral h' == h, fromIntegral mi' == mi, sec' == sec ]
- where (y',m',d') = T.toGregorian (T.utctDay utcTime)
-       daytime    = floor $ T.utctDayTime utcTime
-       (dt', sec')= daytime `divMod` 60
-       (h' , mi') = dt' `divMod` 60
-       (DateTime (Date y m d) (TimeOfDay h mi sec _)) = localTimeToGlobal localtime
+dateEqual localtime utcTime = and
+  [ fromIntegral y == y', m' == (fromEnum m + 1), d' == d
+  , fromIntegral h' == h, fromIntegral mi' == mi, sec' == sec
+  ]
+ where
+  (y',m',d') = T.toGregorian (T.utctDay utcTime)
+  daytime    = floor $ T.utctDayTime utcTime
+  (dt', sec')= daytime `divMod` 60
+  (h' , mi') = dt' `divMod` 60
+  (DateTime (Date y m d) (TimeOfDay h mi sec _)) = localTimeToGlobal localtime
 
 -- | The @Date@ type is able to represent some values that aren't actually legal,
 -- specifically dates with a day field outside of the range of dates in the
 -- month. This function validates a @Date@.
 isValidDate :: Date -> Bool
-isValidDate (Date y m d) = d > 0 && d <= (daysInMonth y m)
+isValidDate (Date y m d) = d > 0 && d <= daysInMonth y m
 
--- windows native functions to convert time cannot handle time before year 1601
-#ifdef WINDOWS
-loElapsed = -11644473600 -- ~ year 1601
-hiElapsed =  32503680000
-dateRange = (1800, 2202)
-#else
-isCTime64 = sizeOf (undefined :: CTime) == 8
-loElapsed =
-  if isCTime64
-     then -62135596800 -- ~ year 0
-     else -(2^(28 :: Int))
-hiElapsed =
-  if isCTime64
-     then 2^(55 :: Int) -- in a future far far away
-     else 2^(29 :: Int) -- before the 2038 bug.
-dateRange =
-  if isCTime64
-     then (1800, 2202)
-     else (1960, 2036)
-#endif
 instance Arbitrary Seconds where
-    arbitrary = Seconds . toHiLo <$> arbitrary
-      where toHiLo v | v > loElapsed && v < hiElapsed = v
-                     | v > hiElapsed = v `mod` hiElapsed
-                     | v < loElapsed = v `mod` loElapsed
-                     | otherwise = error "internal error"
+  arbitrary = Seconds . toHiLo <$> arbitrary
+   where
+    toHiLo v
+      | v > loElapsed && v < hiElapsed = v
+      | v > hiElapsed = v `mod` hiElapsed
+      | v < loElapsed = v `mod` loElapsed
+      | otherwise = error "internal error"
+
 instance Arbitrary Minutes where
-    arbitrary = Minutes <$> choose (-1125899906842624, 1125899906842624)
+  arbitrary = Minutes <$> choose (-1125899906842624, 1125899906842624)
+
 instance Arbitrary Hours where
-    arbitrary = Hours <$> choose (-1125899906842, 1125899906842)
+  arbitrary = Hours <$> choose (-1125899906842, 1125899906842)
+
 instance Arbitrary NanoSeconds where
-    arbitrary = NanoSeconds <$> choose (0, 100000000)
+  arbitrary = NanoSeconds <$> choose (0, 100000000)
+
 instance Arbitrary Elapsed where
-    arbitrary = Elapsed <$> arbitrary
+  arbitrary = Elapsed <$> arbitrary
+
 instance Arbitrary TimezoneOffset where
-    arbitrary = TimezoneOffset <$> choose (-11*60,11*60)
+  arbitrary = TimezoneOffset <$> choose (-(11*60), 11*60)
+
 instance Arbitrary Duration where
-    arbitrary = Duration <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+  arbitrary = Duration <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
 instance Arbitrary Period where
-    arbitrary = Period <$> choose (-29,29) <*> choose (-27,27) <*> choose (-400,400)
+  arbitrary = Period
+    <$> choose (-29,29)
+    <*> choose (-27,27)
+    <*> choose (-400,400)
+
 instance Arbitrary Month where
-    arbitrary = elements [January ..]
+  arbitrary = elements [January ..]
+
 instance Arbitrary DateTime where
-    arbitrary = DateTime <$> arbitrary <*> arbitrary
+  arbitrary = DateTime <$> arbitrary <*> arbitrary
+
 instance Arbitrary Date where
-    arbitrary = do
-        year <- choose dateRange
-        month <- arbitrary
-        Date year month <$> choose (1, daysInMonth year month)
+  arbitrary = do
+    year <- choose dateRange
+    month <- arbitrary
+    Date year month <$> choose (1, daysInMonth year month)
+
 instance Arbitrary TimeOfDay where
-    arbitrary = TimeOfDay <$> (Hours <$> choose (0,23))
-                          <*> (Minutes <$> choose (0,59))
-                          <*> (Seconds <$> choose (0,59))
-                          <*> arbitrary
+  arbitrary = (TimeOfDay . Hours <$> choose (0, 23))
+    <*> (Minutes <$> choose (0,59))
+    <*> (Seconds <$> choose (0,59))
+    <*> arbitrary
+
 instance (Time t, Arbitrary t) => Arbitrary (LocalTime t) where
-    arbitrary = localTime <$> arbitrary <*> arbitrary
+  arbitrary = localTime <$> arbitrary <*> arbitrary
 
+eq :: (Eq a, Show a) => a -> a -> Bool
 eq expected got
-    | expected == got = True
-    | otherwise       = error ("expected: " ++ show expected ++ " got: " ++ show got)
+  | expected == got = True
+  | otherwise =
+      error ("expected: " ++ show expected ++ " got: " ++ show got)
 
-testCaseWith :: (Num a, Eq a, Show a) => String -> (a -> a -> a) -> (a, a, a) -> TestTree
-testCaseWith what fun (x, y, ref) =
-    testCase ((show x) ++ " " ++ what ++ " " ++ (show y) ++ " ?= " ++ (show ref)) checkAdd
-  where
-    checkAdd :: Assertion
-    checkAdd =
-        if fun x y /= ref
-            then assertFailure $ (show $ fun x y) ++ " /= " ++ (show ref)
-            else return ()
+testCaseWith ::
+     (Eq a, Num a, Show a)
+  => String
+  -> (a -> a -> a)
+  -> (a, a, a)
+  -> TestTree
+testCaseWith what fun (x, y, ref) = testCase
+  (show x ++ " " ++ what ++ " " ++ show y ++ " ?= " ++ show ref)
+  checkAdd
+ where
+  checkAdd :: Assertion
+  checkAdd =
+    when (fun x y /= ref) $
+      assertFailure $ show (fun x y) ++ " /= " ++ show ref
 
 arithmeticTestAddRef :: [(ElapsedP, ElapsedP, ElapsedP)]
 arithmeticTestAddRef = map testRefToElapsedP
-    [ ((1, 090000000), (2, 090000000), (3, 180000000))
-    , ((1, 900000000), (1, 200000000), (3, 100000000))
-    , ((1, 000000001), (0, 999999999), (2, 000000000))
-    ]
+  [ ((1, 090000000), (2, 090000000), (3, 180000000))
+  , ((1, 900000000), (1, 200000000), (3, 100000000))
+  , ((1, 000000001), (0, 999999999), (2, 000000000))
+  ]
 
 arithmeticTestSubRef :: [(ElapsedP, ElapsedP, ElapsedP)]
 arithmeticTestSubRef = map testRefToElapsedP
-    [ ((1, ms 100), (1, ms 100), (0, ms 000))
-    , ((1, ms 900), (1, ms 100), (0, ms 800))
-    , ((1, ms 100), (0, ms 200), (0, ms 900))
-    , ((1, ms 100), (2, ms 400), (-2, ms 700))
-    ]
-  where ms v = v * 1000000
+  [ ((1, ms 100), (1, ms 100), (0, ms 000))
+  , ((1, ms 900), (1, ms 100), (0, ms 800))
+  , ((1, ms 100), (0, ms 200), (0, ms 900))
+  , ((1, ms 100), (2, ms 400), (-2, ms 700))
+  ]
+ where
+  ms v = v * 1000000
 
-testRefToElapsedP :: ((Int64, Int64), (Int64, Int64), (Int64, Int64)) -> (ElapsedP, ElapsedP, ElapsedP)
-testRefToElapsedP (a, b, c) = (tupleToElapsedP a, tupleToElapsedP b, tupleToElapsedP c)
-  where
-    tupleToElapsedP :: (Int64, Int64) -> ElapsedP
-    tupleToElapsedP (s, n) = ElapsedP (Elapsed $ Seconds s) (NanoSeconds n)
+testRefToElapsedP ::
+     ((Int64, Int64), (Int64, Int64), (Int64, Int64))
+  -> (ElapsedP, ElapsedP, ElapsedP)
+testRefToElapsedP (a, b, c) =
+  (tupleToElapsedP a, tupleToElapsedP b, tupleToElapsedP c)
+ where
+  tupleToElapsedP :: (Int64, Int64) -> ElapsedP
+  tupleToElapsedP (s, n) = ElapsedP (Elapsed $ Seconds s) (NanoSeconds n)
 
+tests :: [(Elapsed, DateTime, WeekDay, d)] -> TestTree
 tests knowns = testGroup "hourglass"
-    [ testGroup "known"
-        [ testGroup "calendar conv" (map toCalendarTest $ zip eint (map tuple12 knowns))
-        , testGroup "seconds conv" (map toSecondTest $ zip eint (map tuple12 knowns))
-        , testGroup "weekday" (map toWeekDayTest $ zip eint (map tuple13 knowns))
-        ]
-    , testGroup "conversion"
-        [ testProperty "calendar" $ \(e :: Elapsed) ->
-             e `eq` timeGetElapsed (timeGetDateTimeOfDay e)
-        , testProperty "win epoch" $ \(e :: Elapsed) ->
-            let e2 = timeConvert e :: ElapsedSince WindowsEpoch
-             in timePrint ISO8601_DateAndTime e `eq` timePrint ISO8601_DateAndTime e2
-        ]
-    , testGroup "localtime"
-        [ testProperty "eq" $ \(l :: LocalTime Elapsed) ->
-            let g = localTimeToGlobal l
-             in l `eq` localTimeSetTimezone (localTimeGetTimezone l) (localTimeFromGlobal g)
-        , testProperty "set" $ \(l :: LocalTime Elapsed, newTz) ->
-            let l2 = localTimeSetTimezone newTz l
-             in localTimeToGlobal l `eq` localTimeToGlobal l2
-        ]
-    , testGroup "arithmetic"
-        [ testGroup "ElapseP add" $ map (testCaseWith "+" (+)) arithmeticTestAddRef
-        , testGroup "ElapseP sub" $ map (testCaseWith "-" (-)) arithmeticTestSubRef
-          {-testProperty "add-diff" $ \(e :: Elapsed, tdiff) ->
-            let d@(TimeDiff _ _ day h mi s _) = tdiff { timeDiffYears  = 0
-                                                      , timeDiffMonths = 0
-                                                      , timeDiffNs     = 0
-                                                      }
-                i64     = fromIntegral
-                accSecs = (((i64 day * 24) + i64 h) * 60 + i64 mi) * 60 + i64 s :: Int64
-                e'      = timeAdd e d
-             in Seconds accSecs `eq` timeDiff e' e
-        , testProperty "calendar-add-month" $ \date@(DateTime (Date y m d) _) ->
-            let date'@(DateTime (Date y' m' d') _) = timeAdd date (mempty { timeDiffMonths = 1 })
-             in timeGetTimeOfDay date `eq` timeGetTimeOfDay date' &&
-                (d `eq` d')                                       &&
-                (toEnum ((fromEnum m+1) `mod` 12) `eq` m')        &&
-                (if m == December then (y+1) `eq` y' else y `eq` y')
-                -}
+  [ testGroup "known"
+      [ testGroup "calendar conv"
+          (zipWith (curry toCalendarTest) eint (map tuple12 knowns))
+      , testGroup "seconds conv"
+          (zipWith (curry toSecondTest) eint (map tuple12 knowns))
+      , testGroup "weekday"
+          (zipWith (curry toWeekDayTest) eint (map tuple13 knowns))
+      ]
+  , testGroup "conversion"
+      [ testProperty "calendar" $ \(e :: Elapsed) ->
+           e `eq` timeGetElapsed (timeGetDateTimeOfDay e)
+      , testProperty "win epoch" $ \(e :: Elapsed) ->
+          let e2 = timeConvert e :: ElapsedSince WindowsEpoch
+          in       timePrint ISO8601_DateAndTime e
+              `eq` timePrint ISO8601_DateAndTime e2
+      ]
+  , testGroup "localtime"
+      [ testProperty "eq" $ \(l :: LocalTime Elapsed) ->
+          let g = localTimeToGlobal l
+          in      l
+             `eq` localTimeSetTimezone
+                    (localTimeGetTimezone l)
+                    (localTimeFromGlobal g)
+      , testProperty "set" $ \(l :: LocalTime Elapsed, newTz) ->
+          let l2 = localTimeSetTimezone newTz l
+          in  localTimeToGlobal l `eq` localTimeToGlobal l2
+      ]
+  , testGroup "arithmetic"
+      [ testGroup "ElapseP add" $
+          map (testCaseWith "+" (+)) arithmeticTestAddRef
+      , testGroup "ElapseP sub" $
+          map (testCaseWith "-" (-)) arithmeticTestSubRef
+        {-testProperty "add-diff" $ \(e :: Elapsed, tdiff) ->
+          let d@(TimeDiff _ _ day h mi s _) = tdiff { timeDiffYears  = 0
+                                                    , timeDiffMonths = 0
+                                                    , timeDiffNs     = 0
+                                                    }
+              i64     = fromIntegral
+              accSecs = (((i64 day * 24) + i64 h) * 60 + i64 mi) * 60 + i64 s :: Int64
+              e'      = timeAdd e d
+           in Seconds accSecs `eq` timeDiff e' e
+      , testProperty "calendar-add-month" $ \date@(DateTime (Date y m d) _) ->
+          let date'@(DateTime (Date y' m' d') _) = timeAdd date (mempty { timeDiffMonths = 1 })
+           in timeGetTimeOfDay date `eq` timeGetTimeOfDay date' &&
+              (d `eq` d')                                       &&
+              (toEnum ((fromEnum m+1) `mod` 12) `eq` m')        &&
+              (if m == December then (y+1) `eq` y' else y `eq` y')
+              -}
 
-        -- Make sure our Arbitrary instance only generates valid dates:
-        , testProperty "Arbitrary-isValidDate" isValidDate
+      -- Make sure our Arbitrary instance only generates valid dates:
+      , testProperty "Arbitrary-isValidDate" isValidDate
 
-        , testProperty "dateAddPeriod" $ (\date period ->
-            isValidDate (date `dateAddPeriod` period))
-        ]
-    , testGroup "formating"
-        [ testProperty "iso8601 date" $ \(e :: Elapsed) ->
-            (calTimeFormatTimeISO8601 (elapsedToPosixTime e) `eq` timePrint ISO8601_Date e)
-        , testProperty "unix seconds" $ \(e :: Elapsed) ->
-            let sTime = T.formatTime T.defaultTimeLocale "%s" (T.posixSecondsToUTCTime $ elapsedToPosixTime e)
-                sHg = timePrint "EPOCH" e
-             in sTime `eq` sHg
-        ]
-    , testGroup "parsing"
-        [ testProperty "iso8601 date" $ \(e :: Elapsed) ->
-            let fmt = calTimeFormatTimeISO8601 (elapsedToPosixTime e)
-                ed1  = localTimeParseE ISO8601_Date fmt
-                md2  = T.parseTimeM True T.defaultTimeLocale fmt "%F"
-             in case (ed1,md2) of
-                    (Left err, Nothing)       -> error ("both cannot parse: " ++ show fmt ++ " hourglass-err=" ++ show err)
-                    (Left err, Just _)        -> error ("error parsing string: " ++ show err)
-                    (Right (d1, ""), Just d2) -> dateEqual d1 d2
-                    (Right (_,_), Nothing)    -> True -- let (LocalTime tparsed _) = r in error ("time cannot parse: " ++ show tparsed ++ " " ++ fmt)
-                    (Right (_, rm), _)        -> error ("remaining string after parse: " ++ rm)
-        , testProperty "timezone" $ \tz ->
-            let r = localTimeParseE "TZHM" (show tz) in
-            case r of
+      , testProperty "dateAddPeriod" (\date period ->
+          isValidDate (date `dateAddPeriod` period))
+      ]
+  , testGroup "formating"
+      [ testProperty "iso8601 date" $ \(e :: Elapsed) ->
+               calTimeFormatTimeISO8601 (elapsedToPosixTime e)
+          `eq` timePrint ISO8601_Date e
+      , testProperty "unix seconds" $ \(e :: Elapsed) ->
+          let sTime = T.formatTime
+                T.defaultTimeLocale
+                "%s"
+                (T.posixSecondsToUTCTime $ elapsedToPosixTime e)
+              sHg = timePrint "EPOCH" e
+          in  sTime `eq` sHg
+      ]
+  , testGroup "parsing"
+      [ testProperty "iso8601 date" $ \(e :: Elapsed) ->
+          let fmt = calTimeFormatTimeISO8601 (elapsedToPosixTime e)
+              ed1  = localTimeParseE ISO8601_Date fmt
+              md2  = T.parseTimeM True T.defaultTimeLocale fmt "%F"
+          in  case (ed1,md2) of
+                (Left err, Nothing) -> error
+                  (  "both cannot parse: "
+                  ++ show fmt
+                  ++ " hourglass-err="
+                  ++ show err
+                  )
+                (Left err, Just _) ->
+                  error ("error parsing string: " ++ show err)
+                (Right (d1, ""), Just d2) -> dateEqual d1 d2
+                (Right (_,_), Nothing) -> True -- let (LocalTime tparsed _) = r in error ("time cannot parse: " ++ show tparsed ++ " " ++ fmt)
+                (Right (_, rm), _) ->
+                  error ("remaining string after parse: " ++ rm)
+      , testProperty "timezone" $ \tz ->
+          let r = localTimeParseE "TZHM" (show tz)
+          in  case r of
                 Right (localtime, "") -> tz `eq` localTimeGetTimezone localtime
                 _                     -> error "Cannot parse timezone"
-        , testProperty "custom-1" $ test_property_format ("YYYY-MM-DDTH:MI:S.msusns" :: String)
-        , testProperty "custom-2" $ test_property_format ("Mon DD\\t\\h YYYY at HH\\hMI\\mS\\s.p9\\n\\s" :: String)
-        ]
-    , testGroup "Regression Tests"
-        [ testCase  "Real instance of ElapsedP (#33)" $
-            let res = toRational (ElapsedP (Elapsed $ Seconds 0) (NanoSeconds 0))
-                ref = toRational 0 :: Rational
-             in assertEqual "failed equality" ref res
-        , testCase  "Real instance of ElapsedP (#33) (2)" $
-            let res = toRational (ElapsedP (Elapsed $ Seconds 100) (NanoSeconds 1000000))
-                ref = toRational 100 + (1 % 1000) :: Rational
-             in assertEqual "failed equality" ref res
-        ]
-    ]
-  where toCalendarTest (i, (us, dt)) =
-            testCase (show i) (dt @=? timeGetDateTimeOfDay us)
-        toSecondTest (i, (us@(Elapsed (Seconds s)), dt)) =
-            testCase (show i ++ "-" ++ show s ++ "s") (us @=? timeGetElapsed dt)
-        toWeekDayTest (i, (us, wd)) =
-            testCase (show i ++ "-" ++ show wd) (wd @=? getWeekDay (dtDate $ timeGetDateTimeOfDay us))
+      , testProperty "custom-1" $
+          test_property_format ("YYYY-MM-DDTH:MI:S.msusns" :: String)
+      , testProperty "custom-2" $
+          test_property_format ("Mon DD\\t\\h YYYY at HH\\hMI\\mS\\s.p9\\n\\s" :: String)
+      ]
+  , testGroup "Regression Tests"
+      [ testCase  "Real instance of ElapsedP (#33)" $
+          let res = toRational (ElapsedP (Elapsed $ Seconds 0) (NanoSeconds 0))
+              ref = toRational (0 :: Int) :: Rational
+          in  assertEqual "failed equality" ref res
+      , testCase  "Real instance of ElapsedP (#33) (2)" $
+          let res = toRational
+                (ElapsedP (Elapsed $ Seconds 100) (NanoSeconds 1000000))
+              ref = toRational (100 :: Int) + (1 % 1000) :: Rational
+          in  assertEqual "failed equality" ref res
+      ]
+  ]
+ where
+  toCalendarTest (i, (us, dt)) =
+    testCase (show i) (dt @=? timeGetDateTimeOfDay us)
+  toSecondTest (i, (us@(Elapsed (Seconds s)), dt)) =
+    testCase (show i ++ "-" ++ show s ++ "s") (us @=? timeGetElapsed dt)
+  toWeekDayTest (i, (us, wd)) = testCase
+    (show i ++ "-" ++ show wd)
+    (wd @=? getWeekDay (dtDate $ timeGetDateTimeOfDay us))
 
-        eint :: [Int]
-        eint = [1..]
+  eint :: [Int]
+  eint = [1..]
 
-        tuple12 (a,b,_,_) = (a,b)
-        tuple13 (a,_,b,_) = (a,b)
+  tuple12 (a,b,_,_) = (a,b)
+  tuple13 (a,_,b,_) = (a,b)
 
-        calTimeFormatTimeISO8601 timePosix =
-            T.formatTime T.defaultTimeLocale "%F" (T.posixSecondsToUTCTime timePosix)
+  calTimeFormatTimeISO8601 timePosix =
+    T.formatTime T.defaultTimeLocale "%F" (T.posixSecondsToUTCTime timePosix)
 
-        test_property_format :: (TimeFormat format, Show format) => format -> DateTime -> Bool
-        test_property_format fmt dt =
-            let p1  = timePrint fmt dt in
-            case timeParseE fmt p1 of
-                Left (fmtEl, err) -> error ("cannot decode printed DateTime: " ++ show p1 ++ " with format " ++ show fmt ++ " error with(" ++ show fmtEl ++ "): " ++ err)
-                Right (dt2, _) -> dt `eq` dt2
+  test_property_format ::
+       (TimeFormat format, Show format)
+    => format
+    -> DateTime
+    -> Bool
+  test_property_format fmt dt =
+    let p1 = timePrint fmt dt
+    in  case timeParseE fmt p1 of
+          Left (fmtEl, err) -> error
+            (  "cannot decode printed DateTime: "
+            ++ show p1
+            ++ " with format "
+            ++ show fmt
+            ++ " error with("
+            ++ show fmtEl
+            ++ "): " ++ err
+            )
+          Right (dt2, _) -> dt `eq` dt2
 
+main :: IO ()
 main = do
-    knowns <- E.catch (map parseTimeConv . lines <$> readFile "test-time-db")
-                      (\(_ :: E.SomeException) -> return [])
-    defaultMain (tests knowns)
+  knowns <- E.catch
+    (map parseTimeConv . lines <$> readFile "test-time-db")
+    (\(_ :: E.SomeException) -> return [])
+  defaultMain (tests knowns)
tests/TimeDB.hs view
@@ -1,36 +1,45 @@ {-# LANGUAGE BangPatterns #-}
-module TimeDB (parseTimeConv) where
 
-import Data.Char (isDigit)
-import Data.Int
-import Data.Hourglass
+module TimeDB
+  ( parseTimeConv
+  ) where
 
+import           Data.Char ( isDigit )
+import           Data.Hourglass
+                   ( Date (..), DateTime (..), Elapsed, TimeOfDay (..)
+                   , WeekDay
+                   )
+import           Data.Int ( Int64 )
+
 r :: (Read a, Num a) => String -> a
 r !s
-    | isNumber s = case reads s of
-                    [(n,"")] -> fromIntegral (n :: Int64)
-                    []       -> error ("cannot parse anything: " ++ show s)
-                    _        -> error ("cannot parse anything: " ++ show s)
-    | otherwise  = error ("not a num: " ++ s)
-  where
-        isNumber []       = False
-        isNumber ('-':xs) = allNum xs
-        isNumber n@(_:_)  = allNum n
+  | isNumber s = case reads s of
+      [(n,"")] -> fromIntegral (n :: Int64)
+      []       -> error ("cannot parse anything: " ++ show s)
+      _        -> error ("cannot parse anything: " ++ show s)
+  | otherwise  = error ("not a num: " ++ s)
+ where
+  isNumber []       = False
+  isNumber ('-':xs) = allNum xs
+  isNumber n@(_:_)  = allNum n
 
-        allNum = and . map isDigit
+  allNum = all isDigit
 
 wordsWhen :: (Char -> Bool) -> String -> [String]
 wordsWhen p s = case dropWhile p s of
-    "" -> []
-    s' -> w : wordsWhen p s''
-          where (w, s'') = break p s'
+  "" -> []
+  s' -> w : wordsWhen p s''
+   where
+    (w, s'') = break p s'
 
 parseTimeConv :: String -> (Elapsed, DateTime, WeekDay, Int)
 parseTimeConv v =
-    case wordsWhen (== ':') v of
-        ts:y:m:d:h:n:s:wd:doy:[] ->
-            ( r ts
-            , DateTime (Date (r y) (toEnum $ r m - 1) (r d)) (TimeOfDay (r h) (r n) (r s) 0)
-            , read wd
-            , r doy)
-        l -> error ("invalid line: " ++ show l)
+  case wordsWhen (== ':') v of
+    [ts, y, m, d, h, n, s, wd, doy] ->
+      ( r ts
+      , DateTime
+          (Date (r y) (toEnum $ r m - 1) (r d))
+          (TimeOfDay (r h) (r n) (r s) 0)
+      , read wd
+      , r doy)
+    l -> error ("invalid line: " ++ show l)
+ tests/unix/TimeRange.hs view
@@ -0,0 +1,35 @@+{-
+This module depends on the operating system. This is the version for
+Unix-like operating systems.
+-}
+
+module TimeRange
+  ( loElapsed
+  , hiElapsed
+  , dateRange
+  ) where
+
+import           Data.Int ( Int64 )
+import           Foreign.C.Types ( CTime )
+import           Foreign.Storable ( sizeOf )
+
+isCTime64 :: Bool
+isCTime64 = sizeOf (undefined :: CTime) == 8
+
+loElapsed :: Int64
+loElapsed =
+  if isCTime64
+    then -62135596800 -- ~ year 0
+    else -(2^(28 :: Int))
+
+hiElapsed :: Int64
+hiElapsed =
+  if isCTime64
+    then 2^(55 :: Int) -- in a future far far away
+    else 2^(29 :: Int) -- before the 2038 bug.
+
+dateRange :: (Int, Int)
+dateRange =
+  if isCTime64
+    then (1800, 2202)
+    else (1960, 2036)
+ tests/win/TimeRange.hs view
@@ -0,0 +1,22 @@+{-
+This module depends on the operating system. This is the version for Windows.
+-}
+
+module TimeRange
+  ( loElapsed
+  , hiElapsed
+  , dateRange
+  ) where
+
+import           Data.Int ( Int64 )
+
+-- | Windows native functions to convert time cannot handle time before year
+-- 1601.
+loElapsed :: Int64
+loElapsed = -11644473600 -- ~ year 1601
+
+hiElapsed :: Int64
+hiElapsed =  32503680000
+
+dateRange :: (Int, Int)
+dateRange = (1800, 2202)
time-hourglass.cabal view
@@ -1,110 +1,117 @@ cabal-version: 1.12
---- This file has been generated from package.yaml by hpack version 0.38.1.------ see: https://github.com/sol/hpack--name:           time-hourglass-version:        0.2.12-synopsis:       simple performant time related library-description:    Simple time library focusing on simple but powerful and performant API-                .-                The backbone of the library are the Timeable and Time type classes.-                .-                Each Timeable instances can be converted to type that has a Time instances,-                and thus are different representations of current time.-category:       Time-stability:      experimental-homepage:       https://github.com/mpilgrem/time-hourglass-bug-reports:    https://github.com/mpilgrem/time-hourglass/issues-author:         Vincent Hanquez <vincent@snarc.org>-maintainer:     Mike Pilgrem <public@pilgrem.com>-copyright:      Vincent Hanquez <vincent@snarc.org>-license:        BSD3-license-file:   LICENSE-build-type:     Simple-extra-source-files:-    CHANGELOG.md-    README.md-    tests/TimeDB.hs--source-repository head-  type: git-  location: https://github.com/mpilgrem/time-hourglass--library-  exposed-modules:-      Time.Types-      Time.System-      Time.Compat-      Data.Hourglass-      Data.Hourglass.Types-      Data.Hourglass.Epoch-      Data.Hourglass.Compat-      System.Hourglass-  other-modules:-      Data.Hourglass.Time-      Data.Hourglass.Format-      Data.Hourglass.Diff-      Data.Hourglass.Local-      Data.Hourglass.Calendar-      Data.Hourglass.Zone-      Data.Hourglass.Internal-      Data.Hourglass.Utils-  ghc-options: -Wall -fwarn-tabs-  build-depends:-      base ==4.*-    , deepseq-  default-language: Haskell2010-  if os(windows)-    other-modules:-        Data.Hourglass.Internal.Win-    cpp-options: -DWINDOWS-    build-depends:-        Win32-  else-    other-modules:-        Data.Hourglass.Internal.Unix-    c-sources:-        cbits/unix.c--test-suite test-hourglass-  type: exitcode-stdio-1.0-  main-is: Tests.hs-  other-modules:-      TimeDB-  hs-source-dirs:-      tests-  ghc-options: -Wall -fno-warn-orphans -fno-warn-missing-signatures-  build-depends:-      base >=3 && <5-    , deepseq-    , hourglass-    , mtl-    , old-locale-    , tasty-    , tasty-hunit-    , tasty-quickcheck-    , time-  default-language: Haskell2010-  if os(windows)-    cpp-options: -DWINDOWS--benchmark bench-hourglass-  type: exitcode-stdio-1.0-  main-is: Bench.hs-  other-modules:-      Tests-      TimeDB-  hs-source-dirs:-      tests-  build-depends:-      base ==4.*-    , bytestring-    , deepseq-    , gauge-    , hourglass-    , mtl-    , old-locale-    , time-  default-language: Haskell2010+
+-- This file has been generated from package.yaml by hpack version 0.38.1.
+--
+-- see: https://github.com/sol/hpack
+
+name:           time-hourglass
+version:        0.2.13
+synopsis:       A simple and efficient time library
+description:    A simple and efficient time library.
+                .
+                A key part of the library is the `Timeable` and `Time` type classes.
+                .
+                Types representing time values that are instances of the classes allow easy
+                conversion between values of one time type and another.
+category:       Time
+stability:      experimental
+homepage:       https://github.com/mpilgrem/time-hourglass
+bug-reports:    https://github.com/mpilgrem/time-hourglass/issues
+author:         Vincent Hanquez <vincent@snarc.org>
+maintainer:     Mike Pilgrem <public@pilgrem.com>
+copyright:      Vincent Hanquez <vincent@snarc.org>
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    CHANGELOG.md
+    README.md
+    examples/Example/Time/Compat.hs
+    tests/TimeDB.hs
+
+source-repository head
+  type: git
+  location: https://github.com/mpilgrem/time-hourglass
+
+library
+  exposed-modules:
+      Data.Hourglass
+      Data.Hourglass.Compat
+      Data.Hourglass.Epoch
+      Data.Hourglass.Types
+      System.Hourglass
+      Time.Compat
+      Time.Epoch
+      Time.System
+      Time.Types
+  other-modules:
+      Time.Calendar
+      Time.Diff
+      Time.Format
+      Time.LocalTime
+      Time.Time
+      Time.Timezone
+      Time.Utils
+  hs-source-dirs:
+      src
+  ghc-options: -Wall
+  build-depends:
+      base >=4.11 && <5
+    , deepseq
+  default-language: Haskell2010
+  if os(windows)
+    other-modules:
+        Time.Internal
+    hs-source-dirs:
+        src/win
+    build-depends:
+        Win32
+  else
+    other-modules:
+        Time.Internal
+    hs-source-dirs:
+        src/unix
+    c-sources:
+        cbits/unix.c
+
+test-suite test-hourglass
+  type: exitcode-stdio-1.0
+  main-is: Tests.hs
+  other-modules:
+      TimeDB
+  hs-source-dirs:
+      tests
+  ghc-options: -Wall
+  build-depends:
+      base >=4.11 && <5
+    , deepseq
+    , hourglass
+    , tasty
+    , tasty-hunit
+    , tasty-quickcheck
+    , time
+  default-language: Haskell2010
+  if os(windows)
+    other-modules:
+        TimeRange
+    hs-source-dirs:
+        tests/win
+  else
+    other-modules:
+        TimeRange
+    hs-source-dirs:
+        tests/unix
+
+benchmark bench-hourglass
+  type: exitcode-stdio-1.0
+  main-is: Bench.hs
+  hs-source-dirs:
+      benchmarks
+  ghc-options: -Wall
+  build-depends:
+      base >=4.11 && <5
+    , deepseq
+    , hourglass
+    , tasty-bench
+    , time
+  default-language: Haskell2010