-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TimeManager module implemented, tested
- Loading branch information
1 parent
3afad31
commit 8574bbb
Showing
9 changed files
with
223 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# TimeManager | ||
|
||
This module contains functions that handle dates and times | ||
in simulations. The functions in this module often call | ||
functions from Julia's [Dates](https://docs.julialang.org/en/v1/stdlib/Dates/) module. | ||
|
||
## TimeManager API | ||
|
||
```@docs | ||
ClimaCoupler.TimeManager.to_datetime | ||
ClimaCoupler.TimeManager.strdate_to_datetime | ||
ClimaCoupler.TimeManager.datetime_to_strdate | ||
ClimaCoupler.TimeManager.trigger_callback | ||
ClimaCoupler.TimeManager.Monthly | ||
ClimaCoupler.TimeManager.EveryTimestep | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
module ClimaUtilities | ||
|
||
greet() = print("Hello World!") | ||
include("TimeManager.jl") | ||
|
||
end # module ClimaUtilities |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
""" | ||
TimeManager | ||
This module facilitates calendar functions and temporal interpolations | ||
of data. | ||
""" | ||
module TimeManager | ||
|
||
import Dates | ||
import CFTime | ||
|
||
export to_datetime, | ||
strdate_to_datetime, | ||
datetime_to_strdate, | ||
trigger_callback, | ||
Monthly, | ||
EveryTimestep | ||
|
||
""" | ||
to_datetime(date) | ||
Convert a `DateTime`-like object (e.g. `DateTimeNoLeap`) to a `DateTime`, | ||
using CFTime.jl. We need this since some data files we use contain | ||
`DateTimeNoLeap` objects for dates, which can't be used for math with `DateTime`s. | ||
The `DateTimeNoLeap` type uses the Gregorian calendar without leap years, while | ||
the `DateTime` type uses Gregorian calendar with leap years. | ||
For consistency, all input data files should have dates converted to `DateTime` | ||
before being used in a simulation. | ||
# Arguments | ||
- `date`: `DateTime`-like object to be converted to `DateTime` | ||
""" | ||
to_datetime(date) = CFTime.reinterpret(Dates.DateTime, date) | ||
|
||
""" | ||
strdate_to_datetime(strdate::String) | ||
Convert from String ("YYYYMMDD") to Date format, | ||
required by the official AMIP input files. | ||
""" | ||
strdate_to_datetime(strdate::String) = Dates.DateTime( | ||
parse(Int, strdate[1:4]), | ||
parse(Int, strdate[5:6]), | ||
parse(Int, strdate[7:8]), | ||
) | ||
|
||
""" | ||
datetime_to_strdate(datetime::Dates.DateTime) | ||
Convert from DateTime to String ("YYYYMMDD") format. | ||
""" | ||
datetime_to_strdate(datetime::Dates.DateTime) = | ||
string(lpad(Dates.year(datetime), 4, "0")) * | ||
string(string(lpad(Dates.month(datetime), 2, "0"))) * | ||
string(lpad(Dates.day(datetime), 2, "0")) | ||
|
||
abstract type AbstractFrequency end | ||
struct Monthly <: AbstractFrequency end | ||
struct EveryTimestep <: AbstractFrequency end | ||
|
||
""" | ||
trigger_callback(date_nextcall::Dates.DateTime, | ||
date_current::Dates.DateTime, | ||
::Monthly, | ||
func::Function,) | ||
If the current date is equal to or later than the "next call" date at time | ||
00:00:00, call the callback function and increment the next call date by one | ||
month. Otherwise, do nothing and leave the next call date unchanged. | ||
The tuple of arguments `func_args` must match the types, number, and order | ||
of arguments expected by `func`. | ||
# Arguments | ||
- `date_nextcall::DateTime` the next date to call the callback function at or after | ||
- `date_current::DateTime` the current date of the simulation | ||
- `save_freq::AbstractFrequency` frequency with which to trigger callback | ||
- `func::Function` function to be triggered if date is at or past the next call date | ||
- `func_args::Tuple` a tuple of arguments to be passed into the callback function | ||
""" | ||
function trigger_callback( | ||
date_nextcall::Dates.DateTime, | ||
date_current::Dates.DateTime, | ||
::Monthly, | ||
func::Function, | ||
func_args::Tuple, | ||
) | ||
if date_current >= date_nextcall | ||
func(func_args...) | ||
return date_nextcall + Dates.Month(1) | ||
else | ||
return date_nextcall | ||
end | ||
end | ||
|
||
end # module TimeManager |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import ClimaUtilities: TimeManager | ||
import Dates | ||
import CFTime | ||
using Test | ||
|
||
for FT in (Float32, Float64) | ||
@testset "test to_datetime for FT=$FT" begin | ||
# Test non-leap year behavior | ||
year = 2001 | ||
dt_noleap = CFTime.DateTimeNoLeap(year) | ||
dt = Dates.DateTime(year) | ||
@test TimeManager.to_datetime(dt_noleap) == dt | ||
# In non-leap year, DateTime and DateTimeNoLeap are the same | ||
@test TimeManager.to_datetime(dt_noleap + Dates.Day(365)) == | ||
dt + Dates.Day(365) | ||
|
||
# Test leap year behavior | ||
leap_year = 2000 | ||
dt_noleap_ly = CFTime.DateTimeNoLeap(leap_year) | ||
dt_ly = Dates.DateTime(leap_year) | ||
# DateTime includes leap days, DateTimeNoLeap does not, so DateTime has one extra day in leap year | ||
@test TimeManager.to_datetime(dt_noleap_ly + Dates.Day(365)) == | ||
dt_ly + Dates.Day(366) | ||
|
||
end | ||
|
||
@testset "test strdate_to_datetime for FT=$FT" begin | ||
@test TimeManager.strdate_to_datetime("19000101") == | ||
Dates.DateTime(1900, 1, 1) | ||
@test TimeManager.strdate_to_datetime("00000101") == | ||
Dates.DateTime(0, 1, 1) | ||
end | ||
|
||
@testset "test datetime_to_strdate for FT=$FT" begin | ||
@test TimeManager.datetime_to_strdate(Dates.DateTime(1900, 1, 1)) == | ||
"19000101" | ||
@test TimeManager.datetime_to_strdate(Dates.DateTime(0, 1, 1)) == | ||
"00000101" | ||
end | ||
|
||
@testset "test trigger_callback for FT=$FT" begin | ||
# Define callback function | ||
func! = (val) -> val[1] += 1 | ||
# Case 1: date_current == date_nextcall | ||
# Define list for arg so we can mutate it in `func!` | ||
arg = [FT(0)] | ||
arg_copy = copy(arg) | ||
date_current = | ||
date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) | ||
date_nextcall = TimeManager.trigger_callback( | ||
date_nextcall, | ||
date_current, | ||
TimeManager.Monthly(), | ||
func!, | ||
(arg,), | ||
) | ||
# Test that cutoff date was updated and `func!` got called | ||
@test date_nextcall == date_nextcall_copy + Dates.Month(1) | ||
@test arg[1] == func!(arg_copy) | ||
|
||
# Case 2: date_current > date_nextcall | ||
date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) | ||
date_current = date_nextcall + Dates.Day(1) | ||
date_nextcall = TimeManager.trigger_callback( | ||
date_nextcall, | ||
date_current, | ||
TimeManager.Monthly(), | ||
func!, | ||
(arg,), | ||
) | ||
# Test that cutoff date was updated and `func!` got called | ||
@test date_nextcall == date_nextcall_copy + Dates.Month(1) | ||
@test arg[1] == func!(arg_copy) | ||
|
||
# Case 3: date_current < date_nextcall | ||
date_nextcall = date_nextcall_copy = Dates.DateTime(1979, 3, 21) | ||
date_current = date_nextcall - Dates.Day(1) | ||
date_nextcall = TimeManager.trigger_callback( | ||
date_nextcall, | ||
date_current, | ||
TimeManager.Monthly(), | ||
func!, | ||
(arg,), | ||
) | ||
# Test that cutoff date is unchanged and `func!` did not get called | ||
@test date_nextcall == date_nextcall_copy | ||
@test arg[1] == arg_copy[1] | ||
end | ||
end |