lens-datetime-0.2: Lenses for Data.Time.* types

Portabilitynon-portable
Stabilityexperimental
MaintainerMihaly Barasz <mihaly@barasz.com>
Safe HaskellNone

Data.Time.Lens

Contents

Description

Usage:

Basic interface consists of the following six lenses: years, months, days, hours, minutes and seconds with which you can access the corresponding "fields" of LocalTime and UTCTime in a unified way. Also, use date and time if you want to access the Day and TimeOfDay parts as a whole.

Let's assume the following definitions:

import Control.Lens
import Data.Time
import Data.Time.Lens

aDay :: Day
aDay = fromGregorian 2013 08 22

aLocal :: LocalTime
aLocal = LocalTime aDay (TimeOfDay 13 45 28)

aUTC :: UTCTime
aUTC = UTCTime aDay 7458.9

Then you can use the above lenses as follows:

>>> aLocal ^. years
2013
>>> aUTC ^. months
8
>>> aDay ^. days
22
>>> aLocal & time .~ midnight
2013-08-22 00:00:00
>>> aUTC & days .~ 1 & months .~ 1
2013-01-01 02:04:18.9 UTC
>>> aLocal & hours +~ 1            -- But see the note below!
2013-08-22 14:45:28

A note about invalid values and lens laws.

For LocalTime and UTCTime these lenses provide the most straightforward implementation: via 'toGregorian'/'fromGregorian' in the case of years, months and days; and directly to the fields of TimeOfDay in the case of hours, minutes and seconds.

Which means, on one hand, that the date "parts" will be clipped to valid values:

>>> aLocal & months +~ 12
2013-12-22 13:45:28        -- instead of: 2014-08-22 13:45:28
>>> aUTC & days +~ 100
2013-08-31 02:04:18.9 UTC  -- instead of: 2013-11-30 02:04:18.9 UTC

And on the other hand, that the time "parts" will not roll over and produce invalid values:

>>> aLocal & minutes +~ 120
2013-08-22 13:165:28       -- instead of: 2013-08-22 15:45:28

Also, this means that the date lenses are not proper lenses: they only satisfy the lens laws when used with valid values for the given fields.

Basically, avoid setting/modifying the date-time values directly via these lenses if you cannot be sure that the result is a valid value. Instead use the FlexibleDateTime mechanism and the flexDT isomorphism, which correctly rolls over:

>>> aLocal & flexDT.months +~ 12
2014-08-22 13:45:28
>>> aUTC & flexDT.days +~ 100
2013-11-30 02:04:18.9 UTC
>>> aLocal & flexDT.minutes +~ 120
2013-08-22 15:45:28

If you need to set multiple fields try to make only one round-trip via flexDT:

>>> aLocal & flexDT %~ ((days +~ 7) . (hours +~ 2))
2013-08-22 13:45:28

Note that even with flexDT we completely ignore all the issues around daylight saving time and leap seconds. If your code has to be correct wrt. DST, do all the computations in UTCTime and convert to local time only for output. If you need to be correct wrt. leap seconds, then... Well, then I don't know. :)

And while this doesn't strictly belong to this package, here's a complete example of working with daylight saving time:

dstExample :: IO ()
dstExample = do
  let baseT = UTCTime (fromGregorian 2013 10 26) 0

      printInLocal :: UTCTime -> IO ()
      printInLocal t = do
        tz <- getTimeZone t
        print (tz, t ^. utcInTZ tz)

  printInLocal baseT
  printInLocal $ baseT & flexDT %~ ((days +~ 1) . (hours +~ 0) . (minutes +~ 5))
  printInLocal $ baseT & flexDT %~ ((days +~ 1) . (hours +~ 1) . (minutes +~ 5))
  printInLocal $ baseT & flexDT %~ ((days +~ 1) . (hours +~ 2) . (minutes +~ 5))
>>> dstExample
(CEST,2013-10-26 02:00:00)
(CEST,2013-10-27 02:05:00)
(CET,2013-10-27 02:05:00)
(CET,2013-10-27 03:05:00)

Synopsis

Lenses for the date parts

class Dateable a whereSource

Type class that defines access to the "date" part of a type.

You can implement either of the two methods.

years :: Dateable d => Lens' d IntegerSource

Lens into the year value of a Dateable.

Warning: this is not a proper lens for LocalTime and UTCTime: it only obeys the lens laws if used with valid values. When the year value in a date is modified the month and day values might also change. This happens when the original date was a February 29th and we change to a non-leap year.

months :: Dateable d => Lens' d IntSource

Lens into the month value of a Dateable.

Warning: this is not a proper lens for LocalTime and UTCTime: it only obeys the lens laws if used with valid values. The updated month value will be clipped to a valid month value. Also note that the day value might also be modified (clipped to a valid day in that month).

days :: Dateable d => Lens' d IntSource

Lens into the day value of a Dateable.

Warning: this is not a proper lens for LocalTime and UTCTime: it only obeys the lens laws if used with valid values. The updated day value will be clipped to a valid day value in the given year-month.

Lenses for the time parts

class Timeable a whereSource

Type class that defines access to the "time" part of a type.

You only need to define one of the two methods, whichever is more natural.

hours :: Timeable t => Lens' t IntSource

Lens into the hour value of a Timeable.

Warning: this is not a proper lens for UTCTime: it only obeys the lens laws if used with valid values.

minutes :: Timeable t => Lens' t IntSource

Lens into the minute value of a Timeable.

Warning: this is not a proper lens for UTCTime: it only obeys the lens laws if used with valid values.

seconds :: Timeable t => Lens' t PicoSource

Lens into the second value of a Timeable.

Warning: this is not a proper lens for UTCTime: it only obeys the lens laws if used with valid values.

Support for the correct roll-over of fields

class FlexibleDateTime a whereSource

Type class to provide correct roll-over behavior for date-time lenses.

See examples in the general overview part.

class FlexibleDate a whereSource

Type class to provide correct roll-over behavior for date lenses.

Used exactly as flexDT, but for values that have only "date" and no "time" part.

Instances

class FlexibleTime a whereSource

Type class to provide correct roll-over behavior for time lenses.

Used exactly as flexDT, but for values that have only "time" and no "date" part.

If the time rolls-over more than 24 hours the day carry is discarded. Ex.:

>>> let t = TimeOfDay 1 12 3
>>> t
01:12:03
>>> t & flexT.seconds +~ (-7200)
23:12:03

Miscellaneous

utcInTZ :: TimeZone -> Iso' UTCTime LocalTimeSource

Isomorphism between UTCTime and LocalTime for the given TimeZone.

utcAsLocal :: Iso' UTCTime LocalTimeSource

Trivial isomorphism between UTCTime and LocalTime.

We view LocalTime values as being in the UTC time zone. This is utcInTZ applied to utc.

julianDay :: Iso' Day IntegerSource

View Day as an Integer day number in the Julian calendar.

See the description at the definition of Day.

julianDT :: Iso' LocalTime RationalSource

View LocalTime as a fractional day in the modified Julian calendar.

See the description of ModifiedJulianDay and timeOfDayToDayFraction.

gregorianDate :: Iso' Day (Integer, Int, Int)Source

View Day as a triple of (year,month,day) in Gregorian calendar.

See the description at the definition of fromGregorian / toGregorian.