Skip to content

Commit ef76422

Browse files
authored
timezones are now removed if present (#62)
* timezones are now removed if present * realized I had the test using the wrong dataset * now linted * now apply the timezone instead of ignoring it
1 parent 7cd6d39 commit ef76422

File tree

3 files changed

+77
-5
lines changed

3 files changed

+77
-5
lines changed

docs/whats_new.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# What's New
22

3-
## v0.13.0 (November 19, 2025)
3+
## v0.13.2 (November 20, 2025)
4+
5+
* If timezone-aware `start_time`, `start_time_end`, or `end_time` is input, the timezone is now removed and it is assumed they are in the same timezone as the model times.
6+
7+
## v0.13.1 (November 19, 2025)
48

59
* Fixed kerchunk JSON file filtering.
610
* Added CIOFS3 as ocean model option.

particle_tracking_manager/config_the_manager.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,12 @@ class TheManagerConfig(BaseModel):
110110
)
111111
start_time: datetime | None = Field(
112112
datetime(2022, 1, 1),
113-
description="Start time for drifter simulation. start_time or end_time must be input.",
113+
description="Start time for drifter simulation. start_time or end_time must be input. If a timezone is included, it will be used and the time will be converted to UTC which is the same timezone as the models.",
114114
json_schema_extra=dict(ptm_level=1),
115115
)
116116
start_time_end: datetime | None = Field(
117117
None,
118-
description="If used, this creates a range of start times for drifters, starting with `start_time` and ending with `start_time_end`. Drifters will be initialized linearly between the two start times.",
118+
description="If used, this creates a range of start times for drifters, starting with `start_time` and ending with `start_time_end`. Drifters will be initialized linearly between the two start times. If a timezone is included, it will be used and the time will be converted to UTC which is the same timezone as the models.",
119119
json_schema_extra=dict(ptm_level=2),
120120
)
121121
run_forward: bool = Field(
@@ -149,7 +149,7 @@ class TheManagerConfig(BaseModel):
149149
)
150150
end_time: datetime | None = Field(
151151
None,
152-
description="The end of the simulation. steps, end_time, or duration must be input by user. start_time or end_time must be input.",
152+
description="The end of the simulation. steps, end_time, or duration must be input by user. start_time or end_time must be input. If a timezone is included, it will be used and the time will be converted to UTC which is the same timezone as the models.",
153153
json_schema_extra=dict(ptm_level=1),
154154
)
155155
# OceanModelEnum was created dynamically and will trigger an issue with mypy, so we suppress it
@@ -379,6 +379,41 @@ def calculate_config_times(self) -> Self:
379379

380380
return self
381381

382+
@model_validator(mode="after")
383+
def remove_timezone_info(self) -> Self:
384+
"""Remove timezone information from datetime fields."""
385+
if self.start_time is not None and self.start_time.tzinfo is not None:
386+
self.start_time = (
387+
pd.Timestamp(self.start_time)
388+
.tz_convert("UTC")
389+
.tz_localize(None)
390+
.to_pydatetime()
391+
)
392+
logger.debug(
393+
f"Removed timezone info from start_time, new value is {self.start_time}."
394+
)
395+
if self.start_time_end is not None and self.start_time_end.tzinfo is not None:
396+
self.start_time_end = (
397+
pd.Timestamp(self.start_time_end)
398+
.tz_convert("UTC")
399+
.tz_localize(None)
400+
.to_pydatetime()
401+
)
402+
logger.debug(
403+
f"Removed timezone info from start_time_end, new value is {self.start_time_end}."
404+
)
405+
if self.end_time is not None and self.end_time.tzinfo is not None:
406+
self.end_time = (
407+
pd.Timestamp(self.end_time)
408+
.tz_convert("UTC")
409+
.tz_localize(None)
410+
.to_pydatetime()
411+
)
412+
logger.debug(
413+
f"Removed timezone info from end_time, new value is {self.end_time}."
414+
)
415+
return self
416+
382417
@computed_field
383418
def ocean_model_config(self) -> OceanModelConfig:
384419
"""Select ocean model config based on ocean_model."""

tests/test_opendrift.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,40 @@
4242

4343
ptm.config_ocean_model.register_on_the_fly(ds_info)
4444

45-
seed_kws = dict(lon=2, lat=1.5, start_time=0, time_step=0.01)
45+
# ocean_model_local is False because otherwise it requires a `kerchunk_func_str`
46+
seed_kws = dict(lon=2, lat=1.5, start_time=0, time_step=0.01, ocean_model_local=False)
47+
48+
49+
def test_start_time_tz():
50+
"""Check start time timezone is removed."""
51+
52+
m = OpenDriftModel(
53+
duration="1s",
54+
start_time="1970-01-01T00:00Z",
55+
lon=2,
56+
lat=1.5,
57+
time_step=0.01,
58+
ocean_model="ONTHEFLY",
59+
ocean_model_local=False,
60+
)
61+
m.add_reader(ds=ds)
62+
assert m.config.start_time == pd.Timestamp("1970-01-01 00:00:00")
63+
assert m.config.end_time == pd.Timestamp("1970-01-01 00:00:01")
64+
65+
m = OpenDriftModel(
66+
duration="1s",
67+
start_time=(pd.Timestamp("1970-01-01T00:00") - pd.Timedelta("5h")).tz_localize(
68+
"US/Eastern"
69+
),
70+
lon=2,
71+
lat=1.5,
72+
time_step=0.01,
73+
ocean_model="ONTHEFLY",
74+
ocean_model_local=False,
75+
)
76+
m.add_reader(ds=ds)
77+
assert m.config.start_time == pd.Timestamp("1970-01-01 00:00:00")
78+
assert m.config.end_time == pd.Timestamp("1970-01-01 00:00:01")
4679

4780

4881
def test_drop_vars_do3D_true():

0 commit comments

Comments
 (0)