Constructing dates from a vector of numbers?

I have a solution of a differential equations problem, with a given time vector, say 50 values in the range [0,10]. I want to use date along the abscissa axis in the plot. So essentially, I want to associate time 0 from the differential equation to, say, October 1, 2020, and so on.

Question: Is there a way to take a vector, say time = range(0,10,length=50) and add an initial date so that the result becomes a vector of dates?

Question: Would I have to interpolate the solution of the differential equation at integer times to achieve this?

Not clear enough, if you have 0 as Oct 1st, what would 0.20408 (second element in your time array) be, Oct 2nd? if so, then you only really care how long the time vector is:

julia> time_d = range(Date(2020, 10, 1); step=Day(1), length=length(time))
Date("2020-10-01"):Day(1):Date("2020-11-19")
1 Like

This is not quite what I need. This would be pretending that the time step is 1 day, but it is not. Differential equation solvers typically use variable step length.

Still, your answer helps me if I interpolate the solution once a day.

It is possible that I need to use DateTime (??) to handle it properly. And – you have a point: I need to specify which time of day the clock starts.

yes you need DateTime for finer grain

I did fixed step size because your time has fixed step.

Here is approximately what I was looking for:

function add_float2datetime(x,datetime)
    d = Int(floor(x))
    e = x-d
    h = Int(floor(e*24))
    e = e-h/24
    m = Int(floor(e*24*60))
    e = e-m/24/60
    s = Int(floor(e*24*60*60))
    e = e-s/24/60/60
    ms = Int(floor(e*24*60*60*1e3))
    return datetime+Day(d)+Hour(h)+Minute(m)+Second(s)+Millisecond(ms)
end
#
dt = DateTime(2020,10,1)
x = range(0,10,length=50)
#
add_float2datetime.(x,dt)

leading to:

50-element Array{DateTime,1}:
 2020-10-01T00:00:00
 2020-10-01T04:53:52.653
 2020-10-01T09:47:45.306
 2020-10-01T14:41:37.959
 â‹®
 2020-10-10T09:18:22.04
 2020-10-10T14:12:14.693
 2020-10-10T19:06:07.346
 2020-10-11T00:00:00

My function is not elegant… it could have been improved with test on argument types, and generalized in case x is something else than day.

Two other improvements…

  • I could have skipped the “add to” part, i.e., argument datetime if it had been possible to create a DateTime type with zero value for month… Maybe there is another way to do that.
  • Perhaps this function already exists? My second HP calculator, the HP-29c (produced in 1975; I bought it in 1979 or so), had functions -> H.MS and ->H .
1 Like

It can be written shorter in this way

using Dates
import Dates: DateTime

function Dates.DateTime(x::Float64, origin = DateTime(0))
    time = Millisecond(Int(floor(Millisecond(Day(1)).value * x)))
    return origin + time
end

with the result

julia> dt = DateTime(2020,10,1)
2020-10-01T00:00:00

julia> x = range(0,10,length=50)
0.0:0.20408163265306123:10.0

julia> DateTime.(x, dt)
50-element Array{DateTime,1}:
 2020-10-01T00:00:00
 2020-10-01T04:53:52.653
 2020-10-01T09:47:45.306
 2020-10-01T14:41:37.959

On the other hand, you can remove origin and convert time periods to time periods and adding origin later

function timeperiod(x::Float64)
    Millisecond(Int(floor(Millisecond(Day(1)).value * x)))
end

and

julia> timeperiod.(x)
50-element Array{Dates.CompoundPeriod,1}:
 empty period
 17632653 milliseconds
 35265306 milliseconds
 52897959 milliseconds
 70530612 milliseconds
 88163265 milliseconds
 105795918 milliseconds

julia> dt + timeperiod.(x)
50-element Array{DateTime,1}:
 2020-10-01T00:00:00
 2020-10-01T04:53:52.653
 2020-10-01T09:47:45.306
 2020-10-01T14:41:37.959
 2020-10-01T19:35:30.612
 2020-10-02T00:29:23.265
 2020-10-02T05:23:15.918
4 Likes

Very nice!

What is not clear to me…

  • do you add a method to function DateTime here?
  • in other words: does your function definition constitute a modification/new method to the Dates package? [Probably not? By adding this function with name Dates.DateTime, it is only defined in the current session…??]

Yes. Well, not me, but @Skoffer does that, :sweat_smile:.

Yes. All now calls to Dates.DateTime can end up ambiguous because of that. This is the reason Type Piracy is not encouraged. If he added a new method using a a new type introduced by them, then there would be no risk of some code (loaded before this change and unaware of it) end up calling the new method when it was not wanted.

Addendum: @Skoffer did specifically import that function because if you get a function name by means of using you cannot add a new method to it, so as a safety net to avoid doing something disastrous without noticing.

3 Likes

Sorry for this confusion, I should have warn you that this is type piracy and it is not needed at all. You may use any other function name (like add_datetime) and it’ll work just fine.

On the other hand, it is not a first time I see request for constructing date time from float value, so I suppose it worth a PR to Dates or at least small separate package.

2 Likes

A design choice of timeperiod(x;...) is: should one allow the user to specify (i) the time unit of x (Day? Hour? etc.) and the accuracy (millisecond? second? etc.).

You should do what best suits your needs :slight_smile:
But the time unit is nice and rather cheap to implement, so why not?

using Dates

function timeperiod(x::Float64, timeunit=Day(1))
    Millisecond(Int(floor(Millisecond(timeunit).value * x)))
end

with an outcome

julia> origin = DateTime("2020-10-01")
2020-10-01T00:00:00

julia> x = range(0,10,length=50)
0.0:0.20408163265306123:10.0

julia> origin + timeperiod.(x)
50-element Array{DateTime,1}:
 2020-10-01T00:00:00
 2020-10-01T04:53:52.653
 2020-10-01T09:47:45.306
 2020-10-01T14:41:37.959

julia> origin + timeperiod.(x, Hour(1))
50-element Array{DateTime,1}:
 2020-10-01T00:00:00
 2020-10-01T00:12:14.693
 2020-10-01T00:24:29.387
 2020-10-01T00:36:44.081

julia> origin + timeperiod.(x, Day(7))
50-element Array{DateTime,1}:
 2020-10-01T00:00:00
 2020-10-02T10:17:08.571
 2020-10-03T20:34:17.142
 2020-10-05T06:51:25.714
2 Likes