From 3245c9310b0c2092ec79931ba80eae1ad2f8ae4d Mon Sep 17 00:00:00 2001 From: Kevin Phan <98072684+ph-kev@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:05:43 -0700 Subject: [PATCH] Add Utils.time_to_date and Utils.date_to_time 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. --- src/Utils.jl | 105 +++++++++++++++++++++++++++++++++++++++++++++ test/test_Utils.jl | 30 +++++++++++++ 2 files changed, 135 insertions(+) diff --git a/src/Utils.jl b/src/Utils.jl index b927e4c..9f380c1 100644 --- a/src/Utils.jl +++ b/src/Utils.jl @@ -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) diff --git a/test/test_Utils.jl b/test/test_Utils.jl index a2783a1..e59a1b8 100644 --- a/test/test_Utils.jl +++ b/test/test_Utils.jl @@ -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]