Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Changelog
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ version 1.2.1 (not yet released)
* num2date uses 'proleptic_gregorian' scheme when basedate is post-Gregorian but date is pre-Gregorian
(issue #182).
* fix 1.2.0 regression (date2num no longer works with numpy scalar array inputs, issue #185).
* Fix for issue #187 (have date2num round to the nearest second when within 1
microsecond).
* Fix for issue #189 (leap years calculated incorrectly for negative years in
proleptic_gregorian calendar).

version 1.2.0 (release tag v1.2.0rel)
=====================================
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Time-handling functionality from netcdf4-python
## News
For details on the latest updates, see the [Changelog](https://github.com/Unidata/cftime/blob/master/Changelog).

??/??/2020: Version 1.2.1 released. Fixes a couple of regressions introduced in 1.2.0. See Changelog for details.

7/06/2020: version 1.2.0 released. New microsecond accurate algorithm for date2num/num2date contributed by [spencerkclark](https://github.com/spencerkclark). Bugs fixed in masked array handling.

5/12/2020: version 1.1.3 released. Add isoformat method for compatibility with python datetime (issue #152).
Expand Down
22 changes: 17 additions & 5 deletions cftime/_cftime.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ cdef int32_t* days_per_month_array = [
_rop_lookup = {Py_LT: '__gt__', Py_LE: '__ge__', Py_EQ: '__eq__',
Py_GT: '__lt__', Py_GE: '__le__', Py_NE: '__ne__'}

__version__ = '1.2.0'
__version__ = '1.2.1'

# Adapted from http://delete.me.uk/2005/03/iso8601.html
# Note: This regex ensures that all ISO8601 timezone formats are accepted - but, due to legacy support for other timestrings, not all incorrect formats can be rejected.
Expand Down Expand Up @@ -353,16 +353,28 @@ _MAX_INT64 = np.iinfo("int64").max
_MIN_INT64 = np.iinfo("int64").min


def cast_to_int(num):
def cast_to_int(num, units=None):
if num.dtype.kind in "iu":
return num
else:
if np.any(num < _MIN_INT64) or np.any(num > _MAX_INT64):
raise OverflowError('time values outside range of 64 bit signed integers')
if isinstance(num, np.ma.core.MaskedArray):
int_num = np.ma.masked_array(np.rint(num), dtype=np.int64)
# use ceil instead of rint if 1 microsec less than a second
# or floor if 1 microsec greater than a second (issue #187)
if units not in microsec_units and units not in millisec_units:
int_num = np.ma.where(int_num%1000000 == 1, \
np.ma.masked_array(np.floor(num),dtype=np.int64), int_num)
int_num = np.ma.where(int_num%1000000 == 999999, \
np.ma.masked_array(np.ceil(num),dtype=np.int64), int_num)
else:
int_num = np.array(np.rint(num), dtype=np.int64)
if units not in microsec_units and units not in millisec_units:
int_num = np.where(int_num%1000000 == 1, \
np.array(np.floor(num),dtype=np.int64), int_num)
int_num = np.where(int_num%1000000 == 999999, \
np.array(np.ceil(num),dtype=np.int64), int_num)
return int_num


Expand Down Expand Up @@ -483,7 +495,7 @@ def num2date(
times = np.asanyarray(times) # Allow list as input
times = upcast_times(times)
scaled_times = scale_times(times, factor)
scaled_times = cast_to_int(scaled_times)
scaled_times = cast_to_int(scaled_times,units=unit)

# Through np.timedelta64, convert integers scaled to have units of
# microseconds to datetime.timedelta objects, the timedelta type compatible
Expand Down Expand Up @@ -1494,9 +1506,9 @@ cdef _is_leap(int year, calendar):
if calendar == 'proleptic_gregorian' or (calendar == 'standard' and year > 1581):
if tyear % 4: # not divisible by 4
leap = False
elif year % 100: # not divisible by 100
elif tyear % 100: # not divisible by 100
leap = True
elif year % 400: # not divisible by 400
elif tyear % 400: # not divisible by 400
leap = False
else:
leap = True
Expand Down
12 changes: 12 additions & 0 deletions test/test_cftime.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,18 @@ def test_tz_naive(self):
# issue #185: date2num should work the numpy scalar array of dates (1.2.0 regression)
dates = np.array(datetime(2010, 2, 2, 0, 0))
assert (date2num(dates, units="hours since 2010-02-01 00:00:00") == 24.)
# issue #187 - roundtrip near second boundary
dt1 = datetime(1810, 4, 24, 16, 15, 10)
units = 'days since -4713-01-01 12:00'
dt2 = num2date(date2num(dt1, units), units)
assert(dt1 == dt2)
# issue #189 - leap years calculated incorrectly for negative years in proleptic_gregorian calendar
dt1 = datetime(2020, 4, 24, 16, 15, 10)
units = 'days since -4713-01-01 12:00'
cal = 'proleptic_gregorian'
dt2 = num2date(date2num(dt1, units, cal), units, cal)
assert(dt1 == dt2)


class TestDate2index(unittest.TestCase):

Expand Down