Puzzled trying to add a Duration to an Instant

I feel quite dumb, but I can not find a way to add a Duration (Data.Interval.Duration) to an Instant (Data.DateTime.Instant).

What I am looking for is a function with a signature looking something like this:
add :: Duration -> Instant -> Instant

The closer match I could find is ‘adjust’ (Data.DateTime), but its signature is:
adjust :: forall d. Duration d => d -> DateTime -> Maybe DateTime

This looked great until I realised that the Duration type (Data.Interval.Duration) has nothing to do with the Duration class (Data.DateTime).

What am I doing wrong here?

I looked closer into the datetime library code, and I understood why it is not possible to add a Duration (Data.Interval.Duration) to a DateTime or Instant (Data.DateTime.Instant). The reason is state in a comment inside the library:
DateTime is “A date/time value in the Gregorian calendar/UTC time zone.”

I should have guessed this limit by what you are allowed to do using the API, as there is no way to specify a time zone when creating a DateTime.

I have tried to search some alternative options, but I haven’t found anything for PureScript. If I didn’t miss something obvious, I am seeing different ways forward:

  • search for a complete Haskell date/time library for copying the API and possibly some of the implementation (if available);
  • extend the API of the current DateTime library, possibly leveraging the Moment.js library for some of the implementation.

Do these options make any sense?

Cheers.

2 Likes

I don’t think there’s anything conceptually wrong with adding a duration to a DateTime; if you have a date/time value in UTC, you don’t need to specify a time zone to be able to unambiguously ask what the date/time is five hours after that time. I would guess the reason this function is not there is that this is that implementing it is complicated because of things like leap seconds.

3 Likes

I’ve just looked up what Haskell does, and for durations you generally just use NominalDiffTime, which ignores leap seconds, so maybe that would be acceptable for us too.

I experimented a bit with date-time libraries a while ago. Adding a duration to a time instant of any kind (Date only, Time only, DateTime with or without TimeZone) is a natural thing to do and should be allowed. But these things are subtile…

Durations

Note that there are different kinds of “time intervals”!

  • A duration in time, measured in (milli)seconds, minutes, hours, days, or weeks. You can add this to a Time instant, Date instant, or DateTime instant. I.e. add two hours to 12:00 PM, maybe on a specific day, maybe in a specific time zone, or not. This is what Data.Time.Duration is in PureScript’s time lib.
  • A period in time, measured in months, or years. Can only be added to a Date or DateTime instant. This is a more informal way of specifying a duration. Adding one year to the 1st of July in 2019 does not have the same length (in days/hours/etc.) as the year added to the 1st of July in 2020 because of a leap day. But we mean “just a year”. I think this is wat Data.Interval.Duration is in PureScript’s time lib.
  • An interval between two time instances. Note that an interval has a begin and/or end point in time (like a mathematical interval). You can check if a Time instant, Date instant or DateTime instant is included in an interval. Intervals can have one of three forms:
    • start point to end point
    • start point with length (which together specify the end point)
    • length with end point (which together specific the start point)

This is the point where I don’t understand PureScript’s time lib, because Data.Interval adds a fourth interval form: “duration only”. But this doesn’t have a start and/or end point. Also, it uses a different kind of duration, defined in Data.Interval.Duration. This is the one you (@gcsolaroli) like to add to a DateTime isn’t it? This “interval duration” mingles the “time duration” and “time period” types I described above. Maybe @garyb can elaborate on this?

Leap seconds

About leap seconds, @hdgarrood. Libraries in multiple languages use time representations without keeping track of leap seconds. This makes things a lot simpler. See for example:

  • Go: “The calendrical calculations always assume a Gregorian calendar, with no leap seconds.”
  • Nim: “Although this type can represent leap seconds, they are generally not supported in this module. They are not ignored, but the DateTime’s returned by procedures in this module will never have a leap second.”
  • Crystal: “The calendaric calculations are based on the rules of the proleptic Gregorian calendar as specified in ISO 8601. Leap seconds are ignored.”
  • Python: “The specific date of the epoch and the handling of leap seconds is platform dependent. On Windows and most Unix systems, the epoch is January 1, 1970, 00:00:00 (UTC) and leap seconds are not counted towards the time in seconds since the epoch.”

Haskell’s time library makes things overly complicated in my opinion by using similar names like DiffTime and NominalDiffTime with subtile differences.

1 Like

Thanks @hdgarrood and @timjs for your takes.

I probably need to make my needs more explicit in order to focus the discussion.

UTC and Gregorian Calendar are two great defaults, specially when storing data.
But I need to interact with “business” date/time; my current need is to store values (on the UTC timeline) at the start of each day in the Europe/Rome timezone.

My plan was to have a starting point (Instant) set to 2018-01-01-00:00:00 (Europe/Rome), and than compute the next ‘instant’ “adding” the 1 day ‘duration’. But the current library does not support such use.

Ad @timjs clearly put it, I am interested in “Period in time” duration (although I am not sure I agree that “duration in time” can include also ‘days’ or ‘weeks’).

At the moment I am not concerned about the leap second issue, as I hope it will emerge a consensus about dealing it using Google take (speeding up/down seconds).

Cheers