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 +21/−0
- Data/Hourglass.hs +0/−41
- Data/Hourglass/Calendar.hs +0/−86
- Data/Hourglass/Compat.hs +0/−38
- Data/Hourglass/Diff.hs +0/−162
- Data/Hourglass/Epoch.hs +0/−129
- Data/Hourglass/Format.hs +0/−403
- Data/Hourglass/Internal.hs +0/−23
- Data/Hourglass/Internal/Unix.hs +0/−168
- Data/Hourglass/Internal/Win.hs +0/−79
- Data/Hourglass/Local.hs +0/−86
- Data/Hourglass/Time.hs +0/−197
- Data/Hourglass/Types.hs +0/−23
- Data/Hourglass/Utils.hs +0/−33
- Data/Hourglass/Zone.hs +0/−52
- README.md +69/−47
- System/Hourglass.hs +0/−16
- Time/Compat.hs +0/−104
- Time/System.hs +0/−58
- Time/Types.hs +0/−245
- benchmarks/Bench.hs +107/−0
- examples/Example/Time/Compat.hs +35/−0
- src/Data/Hourglass.hs +107/−0
- src/Data/Hourglass/Compat.hs +38/−0
- src/Data/Hourglass/Epoch.hs +16/−0
- src/Data/Hourglass/Types.hs +21/−0
- src/System/Hourglass.hs +16/−0
- src/Time/Calendar.hs +120/−0
- src/Time/Compat.hs +139/−0
- src/Time/Diff.hs +163/−0
- src/Time/Epoch.hs +156/−0
- src/Time/Format.hs +468/−0
- src/Time/LocalTime.hs +96/−0
- src/Time/System.hs +64/−0
- src/Time/Time.hs +211/−0
- src/Time/Timezone.hs +57/−0
- src/Time/Types.hs +270/−0
- src/Time/Utils.hs +37/−0
- src/unix/Time/Internal.hs +168/−0
- src/win/Time/Internal.hs +96/−0
- tests/Bench.hs +0/−93
- tests/Tests.hs +258/−212
- tests/TimeDB.hs +33/−24
- tests/unix/TimeRange.hs +35/−0
- tests/win/TimeRange.hs +22/−0
- time-hourglass.cabal +116/−109
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