Skip to content

Commit

Permalink
Merge pull request #246 from andersy005/support-year-units
Browse files Browse the repository at this point in the history
Add support for 'common_year' and 'common_years' units
  • Loading branch information
jswhit authored Jun 1, 2021
2 parents 5280089 + f831a9c commit 414c946
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 2 deletions.
5 changes: 5 additions & 0 deletions Changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
version x.x.x (release tag vx.x.x.rel)
======================================
* added support for "common_year" and "common_years" units for "noleap"
and "365_day" calendars (issue #5, PR #246)

version 1.5.0 (release tag v1.5.0.rel)
======================================
* clean-up deprecated calendar specific subclasses (PR #231).
Expand Down
10 changes: 8 additions & 2 deletions src/cftime/_cftime.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ min_units = ['minute', 'minutes', 'min', 'mins']
hr_units = ['hour', 'hours', 'hr', 'hrs', 'h']
day_units = ['day', 'days', 'd']
month_units = ['month', 'months'] # only allowed for 360_day calendar
year_units = ['common_year', 'common_years'] # only allowed for 365_day and noleap calendars
_units = microsec_units+millisec_units+sec_units+min_units+hr_units+day_units
# supported calendars. Includes synonyms ('standard'=='gregorian',
# '366_day'=='all_leap','365_day'=='noleap')
Expand Down Expand Up @@ -93,9 +94,11 @@ def _dateparse(timestr,calendar,has_year_zero=None):
if has_year_zero is None:
has_year_zero = _year_zero_defaults(calendar)
(units, isostring) = _datesplit(timestr)
if not ((units in month_units and calendar=='360_day') or units in _units):
if not ((units in month_units and calendar=='360_day') or (units in year_units and calendar in {'365_day', 'noleap'}) or units in _units):
if units in month_units and calendar != '360_day':
raise ValueError("'months since' units only allowed for '360_day' calendar")
if units in year_units and calendar not in {'365_day', 'noleap'}:
raise ValueError("'%s' units only allowed for '365_day' and 'noleap' calendars" % units)
else:
raise ValueError(
"units must be one of 'seconds', 'minutes', 'hours' or 'days' (or singular version of these), got '%s'" % units)
Expand Down Expand Up @@ -320,7 +323,10 @@ UNIT_CONVERSION_FACTORS = {
"days": 86400 * 1000000,
"d": 86400 * 1000000,
"month": 30 * 86400 * 1000000, # Only allowed for 360_day calendar
"months": 30 * 86400 * 1000000
"months": 30 * 86400 * 1000000,
"common_year": 365 * 86400 * 1000000, # Only allowed for 365_day and no_leap calendars
"common_years": 365 * 86400 * 1000000 # Only allowed for 365_day and no_leap calendars

}

DATE_TYPES = {
Expand Down
18 changes: 18 additions & 0 deletions test/test_cftime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,7 @@ def test_exact_datetime_difference(date_type):
_HOUR_UNITS = ["hours", "hour", "hr", "hrs", "h"]
_DAY_UNITS = ["day", "days", "d"]
_MONTH_UNITS = ["month", "months"]
_YEAR_UNITS = ["common_years", "common_year"]
_DTYPES = [np.dtype("int64"), np.dtype("float64")]
_STANDARD_CALENDARS = [
"standard",
Expand Down Expand Up @@ -1880,6 +1881,23 @@ def test_num2date_month_units(calendar, unit, shape, dtype):
result = num2date(numeric_times, units=units, calendar=calendar)
np.testing.assert_equal(result, expected)

@pytest.mark.parametrize("unit", _YEAR_UNITS)
def test_num2date_year_units(calendar, unit, shape, dtype):
date_type = _EXPECTED_DATE_TYPES[calendar]
expected = np.array([date_type(2001, 1, 1, 0, 0, 0, 0),
date_type(2002, 1, 1, 0, 0, 0, 0),
date_type(2003, 1, 1, 0, 0, 0, 0),
date_type(2004, 1, 1, 0, 0, 0, 0)]).reshape(shape)
numeric_times = np.array([1, 2, 3, 4]).reshape(shape).astype(dtype)
units = "{} since 2000-01-01".format(unit)

if calendar not in {"365_day", "noleap"}:
with pytest.raises(ValueError):
num2date(numeric_times, units=units, calendar=calendar)
else:
result = num2date(numeric_times, units=units, calendar=calendar)
np.testing.assert_equal(result, expected)


def test_num2date_only_use_python_datetimes(calendar, shape, dtype):
date_type = real_datetime
Expand Down

0 comments on commit 414c946

Please sign in to comment.