Copyright | (C) 2013 Mihaly Barasz |
---|---|
License | BSD3, see LICENSE |
Maintainer | Mihaly Barasz <mihaly@barasz.com> |
Stability | experimental |
Portability | non-portable |
Safe Haskell | None |
Language | Haskell98 |
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)
- class Dateable a where
- years :: Dateable d => Lens' d Integer
- months :: Dateable d => Lens' d Int
- days :: Dateable d => Lens' d Int
- class Timeable a where
- hours :: Timeable t => Lens' t Int
- minutes :: Timeable t => Lens' t Int
- seconds :: Timeable t => Lens' t Pico
- data FlexDateTime = FlexDateTime {}
- data FlexDate = FlexDate {}
- newtype FlexTime = FlexTime TimeOfDay
- class FlexibleDateTime a where
- class FlexibleDate a where
- class FlexibleTime a where
- utcInTZ :: TimeZone -> Iso' UTCTime LocalTime
- utcAsLocal :: Iso' UTCTime LocalTime
- zonedAsLocal :: Lens' ZonedTime LocalTime
- julianDay :: Iso' Day Integer
- julianDT :: Iso' LocalTime Rational
- gregorianDate :: Iso' Day (Integer, Int, Int)
Lenses for the date parts
class Dateable a where Source #
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 Integer Source #
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 Int Source #
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).
Lenses for the time parts
class Timeable a where Source #
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.
Support for the correct roll-over of fields
data FlexDateTime Source #
class FlexibleDateTime a where Source #
Type class to provide correct roll-over behavior for date-time lenses.
See examples in the general overview part.
flexDT :: Lens' a FlexDateTime Source #
class FlexibleDate a where Source #
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.
class FlexibleTime a where Source #
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
julianDT :: Iso' LocalTime Rational Source #
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
.