{-# LANGUAGE MagicHash #-} {-# OPTIONS_GHC -fno-warn-unused-imports #-} module Data.Thyme.Docs ( -- * #api#API -- $api -- ** #compat#Compatibility with @time@ -- $compat -- ** #spaces#VectorSpace and AffineSpace -- $spaces -- ** #lenses#Isomorphisms and Lenses -- $lenses -- * #tutorial#Tutorial -- $tutorial -- * #impl#Implementation -- $impl -- ** #range#Range -- $range -- ** #prec#Precision -- $prec -- ** #perf#Performance -- $perf ) where import Control.Lens import Data.AffineSpace import Data.Int import Data.IntMap (Key, IntMap) import Data.Map.Strict (Map) import Data.Thyme import Data.Thyme.Calendar.WeekDate import Data.Thyme.Clock.TAI import Data.Thyme.Format.Human import Data.Thyme.Time.Core import qualified Data.Time as T import Data.VectorSpace -- Note: {{{ and }}} below are fold markers for Vim. Please ignore. {- {{{ api -} {- $api In general stick to 'UTCTime' and 'NominalDiffTime' for time calculations, converting from/to 'LocalTime', or 'ZonedTime' et cetera only for input/output. Synonyms for 'Int'—such as 'Year', 'Month', and 'DayOfMonth'—are provided for more descriptive type signatures. There are records (with strict and unpacked fields) for some common tuples, e.g. 'YearMonthDay', instead of @('Integer', 'Int', 'Int')@. On platforms where the native 'Int' is 64-bits wide, types with an 'Enum' instance such as 'Day' can be used as 'Key's for 'IntMap', preferably via an @@ wrapper. In any case the 'Ord' instances are much faster, if you must use 'Map'. -}{- }}} -} {- {{{ compat -} {- $compat @thyme@ tries to maintain API compatibility with @time@ and each module exports the same functions as corresponding one in @time@. There are however some differences to keep in mind: * 'UTCTime' is an opaque type and there is no @UTCTime@ constructor. Since GHC 7.8 you can use the @@ pattern synonym. In any case, 'utcTime' can convert it to a 'UTCView'. * 'UniversalTime' similarly has the @@ pattern synonym, along with 'modJulianDate'. * 'Year's are 'Int's, not 'Integer's: you may need 'fromIntegral'. * If you'd rather not use @lens@ or @vector-space@ directly, most modules list functions under a ‘Compatibility’ section that mimics the @time@ API. * Where a third party library uses @time@, you can use 'toThyme' and 'fromThyme' from "Data.Thyme.Time.Core" to convert between the corresponding types. * "Data.Thyme.Time" provides 'Num', 'Real', 'Fractional', and 'RealFrac' instances for 'DiffTime' and 'NominalDiffTime'. Avoid using this in new code, <#spaces because of reasons>. Any other differences not covered here may be unintentional. Please check on GitHub and file a new one if necessary. -}{- }}} -} {- {{{ spaces -} {- $spaces One of the issues arising from 'Num', 'Real', 'Fractional', and 'RealFrac' instances for e.g. 'NominalDiffTime' is that we must also supply nonsensical operations such as @('*')@: multiplying two quantities of gives something of dimension /T²/, yet according to the type of @('*')@ the result is another 'NominalDiffTime' of dimension /T/. A more principled approach is to invoke our geometric intuition and think of instants in time as points in a one-dimensional affine space—that is, a line without an origin per se; a time-line, if you will. Taking the distance (displacement) between two points in this space amounts to finding the (signed) duration between two instants in time. These durations are akin to vectors in the geometric sense, and as such we can add, subtract, or scaled them by some factor to obtain another duration. Conversely a point plus (or minus) a vector results in another point, and likewise an instant in time plus (or minus) a duration takes us to another instant in time. The 'AffineSpace' and 'VectorSpace' classes from allows us to express exactly this idea: 'UTCTime' correspond to points on the time-line, and is therefore an instance of 'AffineSpace' with the associated @ type 'Diff' 'UTCTime' = 'NominalDiffTime' @ as the type of durations between instants. It may help to think of the provided operations as having these restricted types: @ ('.-.') :: 'UTCTime' -> 'UTCTime' -> 'NominalDiffTime' ('.+^') :: 'UTCTime' -> 'NominalDiffTime' -> 'UTCTime' ('.-^') :: 'UTCTime' -> 'NominalDiffTime' -> 'UTCTime' @ In turn 'NominalDiffTime' is an instance of 'VectorSpace' (and also 'AdditiveGroup') which comes with the following operations: @ ('^+^') :: 'NominalDiffTime' -> 'NominalDiffTime' -> 'NominalDiffTime' ('^-^') :: 'NominalDiffTime' -> 'NominalDiffTime' -> 'NominalDiffTime' @ Said instance also defines @type 'Scalar' 'NominalDiffTime' = 'Rational'@, so we can scale 'NominalDiffTime's thusly: @ ('*^') :: 'Rational' -> 'NominalDiffTime' -> 'NominalDiffTime' ('^*') :: 'NominalDiffTime' -> 'Rational' -> 'NominalDiffTime' ('^/') :: 'NominalDiffTime' -> 'Rational' -> 'NominalDiffTime' @ On operator naming: remember that @.@ are points, and @^@ are vectors. Thus @('.+^')@ corresponds to the operation /point plus vector/, and @('^-^')@ means /vector minus vector/, and so on. Similarly, 'AbsoluteTime' is an 'AffineSpace', and 'DiffTime' is a 'VectorSpace', with @ type 'Diff' 'AbsoluteTime' = 'DiffTime' type 'Scalar' 'DiffTime' = 'Rational' @ If you must have 'Num', 'Real', 'Fractional', and 'RealFrac' for 'DiffTime' and 'NominalDiffTime', they are provided as orhpan instances in the "Data.Thyme.Time" module. -}{- }}} -} {- {{{ lenses -} {- $lenses Since a large part of @thyme@'s functionality is involved in converting back and forth between various representations of time, it's convenient to expose these as "Control.Lens.Iso"s from the @@ package, and field accessors as "Control.Lens.Lens"es. A full @lens@ is however beyond the scope of this documentation. As far as /using/ @thyme@ is concerned, it's sufficient to restrict ourselves to a subset of the @lens@ API: @ 'view' :: 'Lens'' s a -> s -> a 'review' :: 'Iso'' s a -> a -> s 'over' :: 'Lens'' s a -> (a -> a) -> s -> s @ There are infix versions of the above: @ ('^.') = 'flip' 'view' ('Control.Lens.#') = 'review' ('%~') = 'over' @ An 'Iso'' can be used wherever a 'Lens'' is required, so it is helpful to think of 'view' and 'over' as also having the following types: @ 'view' :: 'Iso'' s a -> s -> a 'over' :: 'Iso'' s a -> (a -> a) -> s -> s @ You can use a @'Lens'' s a@ to extract an @a@ out of an @s@ with 'view'. An 'Iso' s a' means you can also construct an @s@ from an @a@ with 'review'. Furthermore 'Lens''s and 'Iso''s can be chained with the standard @('.')@ function composition. @ ('.') :: 'Lens'' s u -> 'Lens'' u a -> 'Lens'' s a ('.') :: 'Iso'' s u -> 'Iso'' u a -> 'Iso'' s a @ Composing an 'Iso'' with a 'Lens'' however ‘downgrades’ the result to a 'Lens''. The <#tutorial tutorial below> has some examples. -}{- }}} -} {- {{{ tutorial -} {- $tutorial Start @ghci@ with the @lens@ and @vector-space@ packages, along with some preliminary imports: @ $ ghci -package thyme -package lens -package vector-space > import "Control.Lens" > import "Data.AffineSpace" > import "Data.Thyme" > import "Data.VectorSpace" @ Let's begin by getting the current UTC date and time from the local system clock: @ > now <- 'getCurrentTime' > now 2016-04-06 03:50:11.159991 UTC @ What date and time is it in my local time zone, formatted using the default locale? @ > zone <- 'getCurrentTimeZone' > 'formatTime' 'defaultTimeLocale' \"%c\" $ (zone, now) '^.' 'zonedTime' "Wed Apr 6 12:50:11 JST 2016" @ See also: 'getZonedTime'. What will be the local time-of-day be /1000/ seconds from now? @ > (now '.+^' 'fromSeconds'' 1000) '^.' 'utcLocalTime' zone '.' '_localTimeOfDay' 12:50:11.159991 @ Approximately how long has it been since the Unix epoch at midnight /1970-01-01/? @ > import "Data.Thyme.Format.Human" > let posixEpoch = 'UTCTime' ('gregorian' 'Control.Lens.#' 'YearMonthDay' 1970 1 1) 'zeroV' > 'humanTimeDiff' (now '.-.' posixEpoch) "5 decades" @ Two months from today, what day of the week will it be? @ > import "Data.Thyme.Calendar.WeekDate" > let later = now '&' '_utctDay' '.' 'gregorian' '%~' 'gregorianMonthsClip' 2 > later 2016-06-06 03:50:11.159991 UTC > later '^.' '_utctDay' '.' 'weekDate' 'WeekDate' {'wdYear' = 2016, 'wdWeek' = 23, 'wdDay' = 1} > 'formatTime' 'defaultTimeLocale' \"%A\" later "Monday" @ -}{- }}} -} {- {{{ impl -} {- $impl The performance improvement of "Data.Thyme" comes chiefly from representing times internally as a single @newtype@d 'Int64's instead of multiple 'GHC.Integer.GMP.Internals.Integer's, as is the case with "Data.Time.Clock"@.@'T.UTCTime'. The trade-offs are summarised below. -}{- }}} -} {- {{{ range -} {- $range 'T.UTCTime' from "Data.Time.Clock" has unbounded range, whereas 'UTCTime' from "Data.Thyme.Clock" has a usable range just under 600,000 years, between the following times inclusive: @ -290419-11-07 19:59:05.224192 UTC 294135-11-26 04:00:54.775807 UTC @ @thyme@ is therefore not Y294K-compliant. -}{- }}} -} {- {{{ prec -} {- $prec 'UTCTime', 'NominalDiffTime', 'DiffTime', and so on from "Data.Thyme.Clock" have resolutions of /1 μs/ = /10^-6 s/, while those from "Data.Time.Clock" have resolutions of /1 ps/ = /10^-12 s/. However keep in mind that a call to @gettimeofday(3)@ or @clock_gettime(3)@ takes on the order of ~0.4μs on a relatively recent (circa 2016) desktop, so the coarser resolution is not really an issue in almost all applications. -}{- }}} -} {- {{{ perf -} {- $perf "Data.Thyme.Clock"@.@'UTCTime' is implemented as @newtype@ wrappers around a single 'Int64', so that 'addUTCTime' and 'diffUTCTime' both compile down to a single arithmetic operation. Likewise "Data.Thyme.Clock"@.@'DiffTime' (and 'NominalDiffTime') are also @newtype@s over 'Int64's. "Data.Time.Clock"@.@'T.UTCTime' on the other hand comprises of two 'Integer's internally: one for the 'T.Day' and another for the 'T.DiffTime' time-of-day. Here 'T.diffUTCTime' has to account for the difference in 'T.Day's, while 'T.addUTCTime' must normalise its result to ensure 'T.utctDayTime' stays within 'T.posixDayLength'. Furthermore, any "Data.Time.Clock"@.@'T.DiffTime' (and 'T.NominalDiffTime') longer than /~2.15 ms/ exceeds /2^31 ps/, and would overflow a native 'GHC.Prim.Int#' on 32-bit architectures, incurring the cost of using 'GHC.Integer.GMP.Internals.BigNat's inside 'GHC.Integer.GMP.Internals.Integer'. Even for 64-bit 'Int#'s, /2^63 ps/ corresponds to only /~106 days/, and it's not inconceivable to deal with durations longer than this. In both cases, there is the additional overhead of working with lifted 'GHC.Integer.GMP.Internals.Integer's. -}{- }}} -}