Skip to content

Commit

Permalink
Merge pull request #604 from jbusecke/jbusecke_calendar_parser
Browse files Browse the repository at this point in the history
Calendar parsing
  • Loading branch information
delandmeterp authored Jun 25, 2019
2 parents 623c199 + 38cd7a5 commit 762f021
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 17 deletions.
9 changes: 8 additions & 1 deletion parcels/particlefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ def _is_particle_started_yet(particle, time):
return (particle.dt*particle.time <= particle.dt*time or np.isclose(particle.time, time))


def _set_calendar(origin_calendar):
if origin_calendar == 'np_datetime64':
return 'standard'
else:
return origin_calendar


class ParticleFile(object):
"""Initialise netCDF4.Dataset for trajectory output.
Expand Down Expand Up @@ -84,7 +91,7 @@ def open_dataset(self):
self.time.units = "seconds"
else:
self.time.units = "seconds since " + str(self.particleset.time_origin)
self.time.calendar = 'standard' if self.particleset.time_origin.calendar == 'np_datetime64' else self.particleset.time_origin.calendar
self.time.calendar = _set_calendar(self.particleset.time_origin.calendar)
self.time.axis = "T"

if self.particleset.lonlatdepth_dtype is np.float64:
Expand Down
22 changes: 16 additions & 6 deletions parcels/tools/converters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from math import cos, pi
import numpy as np
import cftime._cftime as cftime_mods
import cftime
import inspect
from datetime import timedelta as delta

Expand All @@ -9,6 +9,17 @@
'TimeConverter']


def _get_cftime_datetimes():
# Is there a more elegant way to parse these from cftime?
cftime_calendars = tuple(x[1].__name__ for x in inspect.getmembers(cftime._cftime, inspect.isclass))
cftime_datetime_names = [ca for ca in cftime_calendars if 'Datetime' in ca]
return cftime_datetime_names


def _get_cftime_calendars():
return [getattr(cftime, cf_datetime)(1990, 1, 1).calendar for cf_datetime in _get_cftime_datetimes()]


class TimeConverter(object):
""" Converter class for dates with different calendars in FieldSets
Expand All @@ -18,11 +29,10 @@ class TimeConverter(object):

def __init__(self, time_origin=0):
self.time_origin = 0 if time_origin is None else time_origin
cftime_calendars = tuple(x[1].__name__ for x in inspect.getmembers(cftime_mods, inspect.isclass))
if isinstance(time_origin, np.datetime64):
self.calendar = "np_datetime64"
elif type(time_origin).__name__ in cftime_calendars:
self.calendar = "cftime"
elif isinstance(time_origin, cftime._cftime.datetime):
self.calendar = time_origin.calendar
else:
self.calendar = None

Expand All @@ -36,7 +46,7 @@ def reltime(self, time):
time = time.time_origin if isinstance(time, TimeConverter) else time
if self.calendar == 'np_datetime64':
return (time - self.time_origin) / np.timedelta64(1, 's')
elif self.calendar == 'cftime':
elif self.calendar in _get_cftime_calendars():
if isinstance(time, (list, np.ndarray)):
return np.array([(t - self.time_origin).total_seconds() for t in time])
else:
Expand All @@ -58,7 +68,7 @@ def fulltime(self, time):
return [self.time_origin + np.timedelta64(int(t), 's') for t in time]
else:
return self.time_origin + np.timedelta64(int(time), 's')
elif self.calendar == 'cftime':
elif self.calendar in _get_cftime_calendars():
return self.time_origin + delta(seconds=time)
elif self.calendar is None:
return self.time_origin + time
Expand Down
13 changes: 13 additions & 0 deletions tests/test_converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from parcels.tools.converters import TimeConverter, _get_cftime_datetimes
import cftime
import numpy as np


def test_TimeConverter():
cf_datetime_names = _get_cftime_datetimes()
for cf_datetime in cf_datetime_names:
date = getattr(cftime, cf_datetime)(1990, 1, 1)
assert TimeConverter(date).calendar == date.calendar
assert TimeConverter(None).calendar is None
date_datetime64 = np.datetime64('2001-01-01T12:00')
assert TimeConverter(date_datetime64).calendar == "np_datetime64"
18 changes: 8 additions & 10 deletions tests/test_fieldset.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from parcels import FieldSet, ParticleSet, ScipyParticle, JITParticle, Variable, AdvectionRK4, AdvectionRK4_3D, RectilinearZGrid, ErrorCode
from parcels.field import Field, VectorField
from parcels.tools.converters import TimeConverter
from parcels.tools.converters import TimeConverter, _get_cftime_calendars, _get_cftime_datetimes
from datetime import timedelta as delta
import datetime
import numpy as np
import xarray as xr
import math
import pytest
from os import path
import cftime


ptype = {'scipy': ScipyParticle, 'jit': JITParticle}
Expand Down Expand Up @@ -98,23 +99,20 @@ def test_fieldset_from_parcels(xdim, ydim, tmpdir, filename='test_parcels'):
assert np.allclose(fieldset.V.data[0, :], data['V'], rtol=1e-12)


@pytest.mark.parametrize('calendar', ['noleap', '360day'])
def test_fieldset_nonstandardtime(calendar, tmpdir, filename='test_nonstandardtime.nc', xdim=4, ydim=6):
from cftime import DatetimeNoLeap, Datetime360Day
@pytest.mark.parametrize('calendar, cftime_datetime',
zip(_get_cftime_calendars(),
_get_cftime_datetimes()))
def test_fieldset_nonstandardtime(calendar, cftime_datetime, tmpdir, filename='test_nonstandardtime.nc', xdim=4, ydim=6):
filepath = tmpdir.join(filename)

if calendar == 'noleap':
dates = [DatetimeNoLeap(0, m, 1) for m in range(1, 13)]
else:
dates = [Datetime360Day(0, m, 1) for m in range(1, 13)]
dates = [getattr(cftime, cftime_datetime)(1, m, 1) for m in range(1, 13)]
da = xr.DataArray(np.random.rand(12, xdim, ydim),
coords=[dates, range(xdim), range(ydim)],
dims=['time', 'lon', 'lat'], name='U')
da.to_netcdf(str(filepath))

dims = {'lon': 'lon', 'lat': 'lat', 'time': 'time'}
field = Field.from_netcdf(filepath, 'U', dims)
assert field.grid.time_origin.calendar == 'cftime'
assert field.grid.time_origin.calendar == calendar


def test_field_from_netcdf():
Expand Down
10 changes: 10 additions & 0 deletions tests/test_particle_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from parcels.particlefile import _set_calendar
from parcels.tools.converters import _get_cftime_calendars, _get_cftime_datetimes
import cftime


def test_set_calendar():
for calendar_name, cf_datetime in zip(_get_cftime_calendars(), _get_cftime_datetimes()):
date = getattr(cftime, cf_datetime)(1990, 1, 1)
assert _set_calendar(date.calendar) == date.calendar
assert _set_calendar('np_datetime64') == 'standard'

0 comments on commit 762f021

Please sign in to comment.