Skip to content

Commit

Permalink
Add Utils.time_to_date and Utils.date_to_time
Browse files Browse the repository at this point in the history
Also, add Utils.period_to_seconds_float. These functions are
functionally identical to the equivalent functions in ClimaUtilities. We
do not export from ClimaUtilities because we do not want to add
ClimaUtilities as a dependency.
  • Loading branch information
ph-kev committed Sep 9, 2024
1 parent a102f1a commit 3245c93
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 0 deletions.
105 changes: 105 additions & 0 deletions src/Utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,111 @@ function _isequispaced(arr::Vector)
return all(diff(arr) .≈ arr[begin + 1] - arr[begin])
end

"""
time_to_date(start_date::Dates.DateTime, time::AbstractFloat)
Convert the given time to a calendar date starting from `start_date`.
Examples
=========
```jldoctest
julia> import Dates
julia> Utils.time_to_date(Dates.DateTime(2013, 7, 1, 12), 86400.0)
2013-07-02T12:00:00
julia> Utils.time_to_date(Dates.DateTime(2013, 7, 1, 12), 3600.0)
2013-07-01T13:00:00
julia> Utils.time_to_date(Dates.DateTime(2013, 7, 1, 12), 60.0)
2013-07-01T12:01:00
julia> Utils.time_to_date(Dates.DateTime(2013, 7, 1, 12), 1.0)
2013-07-01T12:00:01
```
"""
function time_to_date(start_date::Dates.DateTime, time::AbstractFloat)
# We go through milliseconds to allow fractions of a second (otherwise, Second(0.8)
# would fail). Milliseconds is the level of resolution that one gets when taking the
# difference between two DateTimes. In addition to this, we add a round to account for
# floating point errors. If the floating point error is small enough, round will correct
# it.
milliseconds = Dates.Millisecond.(round.(1_000 * time))
return start_date + milliseconds
end

"""
date_to_time(reference_date::Dates.DateTime, date::Dates.DateTime)
Convert the given calendar date to a time (in seconds) where t=0 is `reference_date`.
Examples
=========
```jldoctest
julia> import Dates
julia> Utils.date_to_time(Dates.DateTime(2013, 7, 1, 12), Dates.DateTime(2013, 7, 2, 12))
86400.0
julia> Utils.date_to_time(Dates.DateTime(2013, 7, 1, 12), Dates.DateTime(2013, 7, 1, 13))
3600.0
julia> Utils.date_to_time(Dates.DateTime(2013, 7, 1, 12), Dates.DateTime(2013, 7, 1, 12, 1))
60.0
julia> Utils.date_to_time(Dates.DateTime(2013, 7, 1, 12), Dates.DateTime(2013, 7, 1, 12, 0, 1))
1.0
```
"""
function date_to_time(reference_date::Dates.DateTime, date::Dates.DateTime)
return period_to_seconds_float(date - reference_date)
end

"""
period_to_seconds_float(period::Dates.Period)
Convert the given `period` to seconds in Float64.
Examples
=========
```jldoctest
julia> import Dates
julia> Utils.period_to_seconds_float(Dates.Millisecond(1))
0.001
julia> Utils.period_to_seconds_float(Dates.Second(1))
1.0
julia> Utils.period_to_seconds_float(Dates.Minute(1))
60.0
julia> Utils.period_to_seconds_float(Dates.Hour(1))
3600.0
julia> Utils.period_to_seconds_float(Dates.Day(1))
86400.0
julia> Utils.period_to_seconds_float(Dates.Week(1))
604800.0
julia> Utils.period_to_seconds_float(Dates.Month(1))
2.629746e6
julia> Utils.period_to_seconds_float(Dates.Year(1))
3.1556952e7
```
"""
function period_to_seconds_float(period::Dates.Period)
# See https://github.com/JuliaLang/julia/issues/55406
period isa Dates.OtherPeriod &&
(period = Dates.Second(Dates.Day(1)) * Dates.days(period))
return period / Dates.Second(1)
end

"""
_data_at_dim_vals(data, dim_arr, dim_idx, vals)
Expand Down
30 changes: 30 additions & 0 deletions test/test_Utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,36 @@ end
@test equispaced == false
end

@testset "Date and time conversion" begin
reference_date = Dates.DateTime(2013, 7, 1, 12)
date_one_day = Dates.DateTime(2013, 7, 2, 12)
date_one_hour = Dates.DateTime(2013, 7, 1, 13)
date_one_min = Dates.DateTime(2013, 7, 1, 12, 1)
date_one_sec = Dates.DateTime(2013, 7, 1, 12, 0, 1)

# Test time_to_date
@test Utils.time_to_date(reference_date, 86400.0) == date_one_day
@test Utils.time_to_date(reference_date, 3600.0) == date_one_hour
@test Utils.time_to_date(reference_date, 60.0) == date_one_min
@test Utils.time_to_date(reference_date, 1.0) == date_one_sec

# Test date_to_time
@test Utils.date_to_time(reference_date, date_one_day) == 86400.0
@test Utils.date_to_time(reference_date, date_one_hour) == 3600.0
@test Utils.date_to_time(reference_date, date_one_min) == 60.0
@test Utils.date_to_time(reference_date, date_one_sec) == 1.0

# Test period_to_seconds_float
@test Utils.period_to_seconds_float(Dates.Millisecond(1)) == 0.001
@test Utils.period_to_seconds_float(Dates.Second(1)) == 1.0
@test Utils.period_to_seconds_float(Dates.Minute(1)) == 60.0
@test Utils.period_to_seconds_float(Dates.Hour(1)) == 3600.0
@test Utils.period_to_seconds_float(Dates.Day(1)) == 86400.0
@test Utils.period_to_seconds_float(Dates.Week(1)) == 604800.0
@test Utils.period_to_seconds_float(Dates.Month(1)) == 2.629746e6
@test Utils.period_to_seconds_float(Dates.Year(1)) == 3.1556952e7
end

@testset "data_at_dim_vals" begin
data = [[1, 2, 3] [4, 5, 6] [7, 8, 9]]
dim_arr = [2.0, 3.0, 4.0]
Expand Down

0 comments on commit 3245c93

Please sign in to comment.