DateTime: Time division

I was wondering how division works on time intervals.

My naïve thought doesn’t work

using Dates
hr = Dates.Hour(1)
t1 = DateTime(2023, 1, 3)
r = 7.4
t2 = t1 + hr/r # ->InexactError: Int64(0.13513513513513511)

The following works only for some values of r:

tdummy = t1 + hr
t2 = t1 + (tdummy - t1).value / r # -> works only for some values of r

tdummy - t1 is of type Millisecond and if the division results in an integer, the above code works.

The following works in general:

tdummy = t1 + hr
t2 = t1 + Dates.Millisecond(round((tdummy - t1).value/r))

But, is this really what’s intended? I thought there should be a more elegant solution.

What is the standard way to express “t1 + onehour/r” for a general real number r ? It seems to me that we need a proper “time interval type” which works seamlessly with DateTime.

Yeah, float-based time intervals are often more convenient than integers. Julia doesn’t have them in stdlib, unfortunately.
See (my) DateFormats.jl package that helps dealing with different date/time formats, including decimal periods:

julia> using Dates, DateFormats

julia> DateTime(2023, 1, 3) + 1/7.4 *ₜ Hour
2023-01-03T00:08:06.486
1 Like

Note that you can convert from lower to higher precision time periods where the answer is always the same (so you can do Millisecond(Hour(1)) but not Day(Month(1))). This just skips the need for your dummy variable:

using Dates
hr = Dates.Hour(1)
t1 = DateTime(2023, 1, 3)
r = 7.4
t1 + Millisecond(round(Millisecond(hr).value/r))

This does seem annoying. But the reason all sorts of fractional arithmetic doesn’t just work is because

Period types represent discrete, human representations of time.

as opposed to physical durations of time. The lubridate cheatsheet for R is good for seeing how complex things need to be to support date arithmetic fully.

1 Like

Check also this related thread.

From which your example could be written, with the help of CompoundPeriods, as:

using Dates, CompoundPeriods
import Base: / , *
/(t::Dates.CompoundPeriod, x::Real) = canonicalize(Dates.CompoundPeriod(Millisecond(round(Int64,Dates.toms(t)/x))))
*(t::Dates.CompoundPeriod, x::Real) = canonicalize(Dates.CompoundPeriod(Millisecond(round(Int64,Dates.toms(t)*x))))

hr = Hour(1) |> CompoundPeriod
t1 = DateTime(2023, 1, 3)
r = 7.4
t2 = t1 + hr/r      # 2023-01-03T00:08:06.486

3 Likes

Thanks!

Your code is corrupted on my screen (webbrowser) but I get what it is:

DateTime(2023, 1, 3) + period_decimal(Hour, 1/7.4)

from the automatic email message sent me by discoursemail.com .

Thanks. I understand that. I understand that the Dates package isn’t designed with this usage in mind.

The original motivation of my post is to ask whether there is already a standard method to handle physical duration of time.

In the future, it would be nice if “we” have

using FloatingDates
del = Hour(1.25) # a CompoundPeriod
t1 = DateTime(2023, 1, 3) # a floating-point version
r  = 7.4
t2 = t1 + del/r

to use CompoundPeriod implicitly.

I keep needing DateTime arithmetics in such a construct as

t1 = DateTime( . . . )
t2 = DateTime( . . . )
del = Hour(1) + Minute(30)
time_axis = (t1 + del/2):del:(t2 - del/2)
@assert length(time_axis) == length(somedata)
plot(time_axis, somedata)

because I sometimes have to deal with datasets without a proper time axis.

In that case, do consider Unitful.jl:

Incidentally, your desired code does work if you change the compound period del = Hour(1) + Minute(30) to a simple period del = Minute(90).

t1 = DateTime("2023-01-01")
t2 = DateTime("2023-01-02")
del = Minute(90)
time_axis = (t1 + del/2):del:(t2 - del/2)

I wonder why using a compound period as a range step isn’t implemented? Perhaps we can get it added!

That also works, and was indeed my message before I edited it. I recently noticed that multiplication/division is more natural for this operation than the period_decimal function, and made a new DateFormats version with *ₜ and /ₜ operators. This message prompted me to actually release the new version, so I updated the message on discourse.
Meaning of 1/7.4 *ₜ Hour is more obvious than period_decimal(Hour, 1/7.4), so I suggest using this operator.

You seem to ignore the context of this discussion: It seems to me that you lift my words out of the context and propose a solution that doesn’t work in the present context. The following code doesn’t work:

using Dates
using Unitful
del = 1u"hr" + 5u"minute" + 2u"s"
t1 = DateTime(2023, 1, 29)
t2 = t1 + del/5 # MethodError: no method matching +(::DateTime, . . .

From the context of this discussion, it’s clear that I’m wanting a method to handle physical duration of time together with DateTime. (But, it would be nice if Dates worked with the time in the Unitful package.)

I know that! Yours isn’t my “desired code”. Again you lift my example out of the context. The code you wrote stops working once we introduce division:

t1 = DateTime(2023, 1, 29)
t2 = DateTime(2023, 5, 29)
del = Minute(90) / 3.14
time_axis = (t1 + del/2):del:(t2 - del/2)

Wanting a time interval type that allows for usual arithmetics as if it were a floating-point number together with DateTime, is the starting point of this discussion!