From ebe9f11f6c07bbaf1c0680e67180678579213312 Mon Sep 17 00:00:00 2001 From: Kevin Phan <98072684+ph-kev@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:51:13 -0700 Subject: [PATCH] Support passing dates for Var.resampled_as --- NEWS.md | 2 ++ docs/src/howdoi.md | 20 ++++++++++++++ src/Var.jl | 24 ++++++++++++++++- test/test_Var.jl | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 860eaddd..7b21116d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -45,6 +45,8 @@ Makie.save("surface_plot.png", fig) - The number of dimensions of a `OutputVar` can be accessed with `ndims`. - You can drop dimensions of size 1 with `dropdims`. - `==` and `isequal` are now defined for `OutputVar`, `FlatVar`, and `Metadata`. +- When resampling with keyword arguments, `resampled_as` now accepts dates for + the time dimension, e.g. `resampled_as(var, time = [Dates.DateTime(2010)])`. ## Bug fixes diff --git a/docs/src/howdoi.md b/docs/src/howdoi.md index 51f267c6..382ad273 100644 --- a/docs/src/howdoi.md +++ b/docs/src/howdoi.md @@ -244,6 +244,7 @@ the dimensions in `dest_var`. ```@setup resampled_as import ClimaAnalysis import OrderedCollections: OrderedDict +import Dates src_lon = [0.0, 1.0, 2.0] src_lat = [0.0, 1.0, 2.0, 3.0] @@ -260,6 +261,11 @@ dest_dims = OrderedDict("long" => dest_long, "lat" => dest_lat) dest_dim_attribs = OrderedDict("long" => Dict("units" => "degrees"), "lat" => Dict("units" => "degrees")) dest_attribs = Dict("long_name" => "hi") dest_var = ClimaAnalysis.OutputVar(dest_attribs, dest_dims, dest_dim_attribs, dest_var_data) + +time_dims = OrderedDict("time" => [0.0, 86400.0, 172800.0]) +time_dim_attribs = OrderedDict("time" => Dict("units" => "s")) +time_attribs = Dict("long_name" => "hi", "start_date" => "2010-01-01") +time_var = ClimaAnalysis.OutputVar(time_attribs, time_dims, time_dim_attribs, [1.0, 2.0, 3.0]) ``` ```@repl resampled_as @@ -312,6 +318,20 @@ resampled_var.data resampled_var.dims ``` +If `start_date` is among the attributes of the `OutputVar`, then dates can be +passed to the time dimension. The dates are automatically converted to the +corresponding times using `start_date`. + +```@repl resampled_as +ClimaAnalysis.dates(time_var) +resampled_var = ClimaAnalysis.resampled_as( + time_var, + time = [Dates.DateTime(2010, 1, 1), Dates.DateTime(2010, 1, 2)], +); +resampled_var.data +ClimaAnalysis.dates(resampled_var) +``` + ## How do I load multiple NetCDF files along the time dimension? To load multiple NetCDF files along the time dimension, you can pass in diff --git a/src/Var.jl b/src/Var.jl index 8688a43e..24dcda33 100644 --- a/src/Var.jl +++ b/src/Var.jl @@ -1788,11 +1788,33 @@ If the dimensions in `dims` and `src_var.dims` match (ignoring order), the resul dimensions of the resulting `OutputVar` will remain unchanged. If the dimensions of `dims` is a strict subset of the dimensions in `src_var`, then partial resampling is done instead. +When resampling on the time dimension and `start_date` is among the attributes of +`src_var`, it is possible to directly pass a vector of `Dates.DateTime` and +`resampled_as` will automatically convert the dates into the corresponding times. + Note that there is no checking for units of the dimensions. + +!!! compat "Support for dates" + Passing dates for the time dimension is introduced after ClimaAnalysis + v0.5.22. """ function resampled_as(src_var::OutputVar; kwargs...) + # If the values are dates, then compute the associated times before resampling + function _kwarg_to_dim_pair(dim_name, dim_array) + dim_name = String(dim_name) + vals_are_dates = eltype(dim_array) <: Dates.AbstractDateTime + dim_is_time = conventional_dim_name(dim_name) == "time" + (vals_are_dates && !dim_is_time) && + error("Dates are only supported with time dimension") + vals_are_dates && (dim_array = date_to_time.(Ref(src_var), dim_array)) + return dim_name => dim_array + end + # Construct tuple - dims = (String(dim_name) => dim_array for (dim_name, dim_array) in kwargs) + dims = ( + _kwarg_to_dim_pair(dim_name, dim_array) for + (dim_name, dim_array) in kwargs + ) # Find corresponding dimension names in src_var src_var_dim_names = collect(keys(src_var.dims)) diff --git a/test/test_Var.jl b/test/test_Var.jl index 7a3fca76..85728406 100644 --- a/test/test_Var.jl +++ b/test/test_Var.jl @@ -2030,6 +2030,71 @@ end Dates.DateTime("2010-01-01") end +@testset "Resampling with dates" begin + src_time = [0.0, 86400.0, 172800.0, 259200.0] + src_var = + TemplateVar() |> + add_dim("time", src_time, units = "s") |> + add_attribs(long_name = "hi", start_date = "2010-01-01") |> + one_to_n_data() |> + initialize + + dest_dates = + [Dates.DateTime(2010, 1, 1, 12), Dates.DateTime(2010, 1, 2, 12)] + resampled_var = ClimaAnalysis.resampled_as(src_var, time = dest_dates) + @test ClimaAnalysis.times(resampled_var) == [43200.0, 129600.0] + @test ClimaAnalysis.dates(resampled_var) == dest_dates + @test resampled_var.data == [1.5, 2.5] + + # Specify the time dimension by one of its conventional names + resampled_var = ClimaAnalysis.resampled_as(src_var, t = dest_dates) + @test ClimaAnalysis.times(resampled_var) == [43200.0, 129600.0] + @test resampled_var.data == [1.5, 2.5] + + # Partial resampling on the time dimension with dates + src_long = 0.0:2.0 |> collect + src_var = + TemplateVar() |> + add_dim("time", src_time, units = "s") |> + add_dim("long", src_long, units = "degrees") |> + add_attribs(long_name = "hi", start_date = "2010-01-01") |> + one_to_n_data() |> + initialize + resampled_var = ClimaAnalysis.resampled_as(src_var, time = dest_dates) + @test ClimaAnalysis.times(resampled_var) == [43200.0, 129600.0] + @test resampled_var.dims["long"] == src_long + @test resampled_var.data == [[1.5, 2.5] [5.5, 6.5] [9.5, 10.5]] + + # Resampling with a mix of dates and floats + resampled_var = ClimaAnalysis.resampled_as( + src_var, + time = dest_dates, + long = [0.0, 1.0], + ) + @test ClimaAnalysis.times(resampled_var) == [43200.0, 129600.0] + @test resampled_var.dims["long"] == [0.0, 1.0] + @test resampled_var.data == [[1.5, 2.5] [5.5, 6.5]] + + # Error handling + # Dates for a dimension that is not time + @test_throws ErrorException ClimaAnalysis.resampled_as( + src_var, + long = dest_dates, + ) + + # Dates when start_date is not among the attributes + no_start_date_var = + TemplateVar() |> + add_dim("time", src_time, units = "s") |> + add_attribs(long_name = "hi") |> + one_to_n_data() |> + initialize + @test_throws ErrorException ClimaAnalysis.resampled_as( + no_start_date_var, + time = dest_dates, + ) +end + @testset "Units" begin long = -180.0:180.0 |> collect data = copy(long)