From 451d27d75797d052e1186a8201dc589da122cca2 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Wed, 26 Aug 2020 19:57:40 -0600 Subject: [PATCH 01/16] fix for issue #198 --- cftime/__init__.py | 1 + cftime/_cftime.pyx | 100 +++++++++++++++++++++++++------------------- test/test_cftime.py | 4 +- 3 files changed, 59 insertions(+), 46 deletions(-) diff --git a/cftime/__init__.py b/cftime/__init__.py index 7138316d..415214a0 100644 --- a/cftime/__init__.py +++ b/cftime/__init__.py @@ -6,4 +6,5 @@ from ._cftime import microsec_units, millisec_units, \ sec_units, hr_units, day_units, min_units from ._cftime import num2date, date2num, date2index, num2pydate +from ._cftime import to_calendar_specific_datetime from ._cftime import __version__ diff --git a/cftime/_cftime.pyx b/cftime/_cftime.pyx index 0caa6641..f2f96220 100644 --- a/cftime/_cftime.pyx +++ b/cftime/_cftime.pyx @@ -148,8 +148,8 @@ def _dateparse(timestr,calendar): pass if not basedate: if not utc_offset: - basedate = datetime(year, month, day, hour, minute, second, - microsecond) + basedate = _datetime(year, month, day, hour, minute, second, + microsecond) else: raise ValueError('cannot use utc_offset for this reference date/calendar') if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']: @@ -246,7 +246,7 @@ def date2num(dates,units,calendar='standard'): # remove time zone offset if getattr(date, 'tzinfo',None) is not None: date = date.replace(tzinfo=None) - date.utcoffset() - else: # convert date to same calendar specific cftime.datetime instance + else: # convert date to same calendar specific cftime._datetime instance if not isinstance(date, DATE_TYPES[calendar]): date = to_calendar_specific_datetime(date, calendar, False) if ismasked and mask.flat[n]: @@ -332,20 +332,20 @@ DATE_TYPES = { } -def to_calendar_specific_datetime(datetime, calendar, use_python_datetime): +def to_calendar_specific_datetime(dt, calendar, use_python_datetime): if use_python_datetime: date_type = real_datetime else: date_type = DATE_TYPES[calendar] return date_type( - datetime.year, - datetime.month, - datetime.day, - datetime.hour, - datetime.minute, - datetime.second, - datetime.microsecond + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond ) @@ -435,7 +435,7 @@ def num2date( **only_use_cftime_datetimes**: if False, python datetime.datetime objects are returned from num2date where possible; if True dates which - subclass cftime.datetime are returned for all calendars. Default **True**. + subclass cftime._datetime are returned for all calendars. Default **True**. **only_use_python_datetimes**: always return python datetime.datetime objects and raise an error if this is not possible. Ignored unless @@ -498,7 +498,7 @@ def num2date( # Through np.timedelta64, convert integers scaled to have units of # microseconds to datetime.timedelta objects, the timedelta type compatible - # with all cftime.datetime objects. + # with all cftime._datetime objects. deltas = scaled_times.astype("timedelta64[us]").astype(timedelta) try: return basedate + deltas @@ -841,7 +841,19 @@ cdef to_tuple(dt): dt.second, dt.microsecond) @cython.embedsignature(True) -cdef class datetime(object): +def datetime(year, month, day, hour=0, minute=0, + second=0, microsecond=0, dayofwk=-1, + dayofyr = -1, calendar='standard'): + """ +Create a calendar specific datetime instance. + """ + if calendar == '': + return _datetime(year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr,calendar=calendar) + else: + date_type = DATE_TYPES[calendar] + return date_type(year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr) + +cdef class _datetime(object): """ The base class implementing most methods of datetime classes that mimic datetime.datetime but support calendars other than the proleptic @@ -854,7 +866,7 @@ Gregorial calendar. # Python's datetime.datetime uses the proleptic Gregorian # calendar. This boolean is used to decide whether a - # cftime.datetime instance can be converted to + # cftime._datetime instance can be converted to # datetime.datetime. cdef readonly bint datetime_compatible @@ -997,9 +1009,9 @@ Gregorial calendar. self.second, self.microsecond) def __richcmp__(self, other, int op): - cdef datetime dt, dt_other + cdef _datetime dt, dt_other dt = self - if isinstance(other, datetime): + if isinstance(other, _datetime): dt_other = other # comparing two datetime instances if dt.calendar == dt_other.calendar: @@ -1054,11 +1066,11 @@ Gregorial calendar. return NotImplemented def __add__(self, other): - cdef datetime dt - if isinstance(self, datetime) and isinstance(other, timedelta): + cdef _datetime dt + if isinstance(self, _datetime) and isinstance(other, timedelta): dt = self delta = other - elif isinstance(self, timedelta) and isinstance(other, datetime): + elif isinstance(self, timedelta) and isinstance(other, _datetime): dt = other delta = self else: @@ -1066,10 +1078,10 @@ Gregorial calendar. return dt._add_timedelta(delta) def __sub__(self, other): - cdef datetime dt - if isinstance(self, datetime): # left arg is a datetime instance + cdef _datetime dt + if isinstance(self, _datetime): # left arg is a datetime instance dt = self - if isinstance(other, datetime): + if isinstance(other, _datetime): # datetime - datetime if dt.calendar != other.calendar: raise ValueError("cannot compute the time difference between dates with different calendars") @@ -1113,13 +1125,13 @@ datetime object.""" return NotImplemented @cython.embedsignature(True) -cdef class DatetimeNoLeap(datetime): +cdef class DatetimeNoLeap(_datetime): """ Phony datetime object which mimics the python datetime object, but uses the "noleap" ("365_day") calendar. """ def __init__(self, *args, **kwargs): - datetime.__init__(self, *args, **kwargs) + _datetime.__init__(self, *args, **kwargs) self.calendar = "noleap" self.datetime_compatible = False assert_valid_date(self, no_leap, False, has_year_zero=True) @@ -1132,13 +1144,13 @@ but uses the "noleap" ("365_day") calendar. return _dpm[self.month-1] @cython.embedsignature(True) -cdef class DatetimeAllLeap(datetime): +cdef class DatetimeAllLeap(_datetime): """ Phony datetime object which mimics the python datetime object, but uses the "all_leap" ("366_day") calendar. """ def __init__(self, *args, **kwargs): - datetime.__init__(self, *args, **kwargs) + _datetime.__init__(self, *args, **kwargs) self.calendar = "all_leap" self.datetime_compatible = False assert_valid_date(self, all_leap, False, has_year_zero=True) @@ -1151,13 +1163,13 @@ but uses the "all_leap" ("366_day") calendar. return _dpm_leap[self.month-1] @cython.embedsignature(True) -cdef class Datetime360Day(datetime): +cdef class Datetime360Day(_datetime): """ Phony datetime object which mimics the python datetime object, but uses the "360_day" calendar. """ def __init__(self, *args, **kwargs): - datetime.__init__(self, *args, **kwargs) + _datetime.__init__(self, *args, **kwargs) self.calendar = "360_day" self.datetime_compatible = False assert_valid_date(self, no_leap, False, has_year_zero=True, is_360_day=True) @@ -1170,13 +1182,13 @@ but uses the "360_day" calendar. return _dpm_360[self.month-1] @cython.embedsignature(True) -cdef class DatetimeJulian(datetime): +cdef class DatetimeJulian(_datetime): """ Phony datetime object which mimics the python datetime object, but uses the "julian" calendar. """ def __init__(self, *args, **kwargs): - datetime.__init__(self, *args, **kwargs) + _datetime.__init__(self, *args, **kwargs) self.calendar = "julian" self.datetime_compatible = False assert_valid_date(self, is_leap_julian, False) @@ -1185,7 +1197,7 @@ but uses the "julian" calendar. return DatetimeJulian(*add_timedelta(self, delta, is_leap_julian, False, False)) @cython.embedsignature(True) -cdef class DatetimeGregorian(datetime): +cdef class DatetimeGregorian(_datetime): """ Phony datetime object which mimics the python datetime object, but uses the mixed Julian-Gregorian ("standard", "gregorian") calendar. @@ -1199,7 +1211,7 @@ datetime.datetime instances and used to compute time differences a datetime.datetime instance or vice versa. """ def __init__(self, *args, **kwargs): - datetime.__init__(self, *args, **kwargs) + _datetime.__init__(self, *args, **kwargs) self.calendar = "gregorian" # dates after 1582-10-15 can be converted to and compared to @@ -1214,7 +1226,7 @@ a datetime.datetime instance or vice versa. return DatetimeGregorian(*add_timedelta(self, delta, is_leap_gregorian, True, False)) @cython.embedsignature(True) -cdef class DatetimeProlepticGregorian(datetime): +cdef class DatetimeProlepticGregorian(_datetime): """ Phony datetime object which mimics the python datetime object, but allows for dates that don't exist in the proleptic gregorian calendar. @@ -1225,14 +1237,14 @@ Has strftime, timetuple, replace, __repr__, and __str__ methods. The format of the string produced by __str__ is controlled by self.format (default %Y-%m-%d %H:%M:%S). Supports comparisons with other datetime instances using the same calendar; comparison with -native python datetime instances is possible for cftime.datetime +native python datetime instances is possible for cftime._datetime instances using 'gregorian' and 'proleptic_gregorian' calendars. Instance variables are year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr, format, and calendar. """ def __init__(self, *args, **kwargs): - datetime.__init__(self, *args, **kwargs) + _datetime.__init__(self, *args, **kwargs) self.calendar = "proleptic_gregorian" self.datetime_compatible = True assert_valid_date(self, is_leap_proleptic_gregorian, False) @@ -1261,7 +1273,7 @@ cdef _findall(text, substr): # calendar. ;) -cdef _strftime(datetime dt, fmt): +cdef _strftime(_datetime dt, fmt): if _illegal_s.search(fmt): raise TypeError("This strftime implementation does not handle %s") # don't use strftime method at all. @@ -1321,7 +1333,7 @@ cdef int * month_lengths(bint (*is_leap)(int), int year): else: return _dpm -cdef void assert_valid_date(datetime dt, bint (*is_leap)(int), +cdef void assert_valid_date(_datetime dt, bint (*is_leap)(int), bint julian_gregorian_mixed, bint has_year_zero=False, bint is_360_day=False) except *: @@ -1356,7 +1368,7 @@ cdef void assert_valid_date(datetime dt, bint (*is_leap)(int), if dt.microsecond < 0 or dt.microsecond > 999999: raise ValueError("invalid microsecond provided in {0!r}".format(dt)) -# Add a datetime.timedelta to a cftime.datetime instance. Uses +# Add a datetime.timedelta to a cftime._datetime instance. Uses # integer arithmetic to avoid rounding errors and preserve # microsecond accuracy. # @@ -1370,7 +1382,7 @@ cdef void assert_valid_date(datetime dt, bint (*is_leap)(int), # The date of the transition from the Julian to Gregorian calendar and # the number of invalid dates are hard-wired (1582-10-4 is the last day # of the Julian calendar, after which follows 1582-10-15). -cdef tuple add_timedelta(datetime dt, delta, bint (*is_leap)(int), bint julian_gregorian_mixed, bint has_year_zero): +cdef tuple add_timedelta(_datetime dt, delta, bint (*is_leap)(int), bint julian_gregorian_mixed, bint has_year_zero): cdef int microsecond, second, minute, hour, day, month, year cdef int delta_microseconds, delta_seconds, delta_days cdef int* month_length @@ -1444,13 +1456,13 @@ cdef tuple add_timedelta(datetime dt, delta, bint (*is_leap)(int), bint julian_g return (year, month, day, hour, minute, second, microsecond, -1, -1) -# Add a datetime.timedelta to a cftime.datetime instance with the 360_day calendar. +# Add a datetime.timedelta to a cftime._datetime instance with the 360_day calendar. # # Assumes that the 360_day,365_day and 366_day calendars (unlike the rest of supported # calendars) have the year 0. Also, there are no leap years and all # months are 30 days long, so we can compute month and year by using # "//" and "%". -cdef tuple add_timedelta_360_day(datetime dt, delta): +cdef tuple add_timedelta_360_day(_datetime dt, delta): cdef int microsecond, second, minute, hour, day, month, year cdef int delta_microseconds, delta_seconds, delta_days @@ -1870,7 +1882,7 @@ def DateFromJulianDay(JD, calendar='standard', only_use_cftime_datetimes=True, if calendar='julian', Julian Day follows julian calendar. - If only_use_cftime_datetimes is set to True, then cftime.datetime + If only_use_cftime_datetimes is set to True, then cftime._datetime objects are returned for all calendars. Otherwise the datetime object is a native python datetime object if the date falls in the Gregorian calendar (i.e. calendar='proleptic_gregorian', or calendar = 'standard'/'gregorian' @@ -2121,7 +2133,7 @@ are: @keyword only_use_cftime_datetimes: if False, datetime.datetime objects are returned from num2date where possible; if True dates which subclass -cftime.datetime are returned for all calendars. Default True. +cftime._datetime are returned for all calendars. Default True. @keyword only_use_python_datetimes: always return python datetime.datetime objects and raise an error if this is not possible. Ignored unless diff --git a/test/test_cftime.py b/test/test_cftime.py index 3e68b5ee..87a1792e 100644 --- a/test/test_cftime.py +++ b/test/test_cftime.py @@ -248,7 +248,7 @@ def test_tz_naive(self): # check date2num,num2date methods. # use datetime from cftime, since this date doesn't # exist in "normal" calendars. - d = datetimex(2000, 2, 30) + d = datetimex(2000, 2, 30, calendar='360_day') t1 = self.cdftime_360day.date2num(d) assert_almost_equal(t1, 360 * 400.) d2 = self.cdftime_360day.num2date(t1) @@ -1581,7 +1581,7 @@ def test_num2date_only_use_cftime_datetimes_post_gregorian( def test_repr(): #expected = 'cftime.datetime(2000-01-01 00:00:00)' - expected = 'cftime.datetime(2000, 1, 1, 0, 0, 0, 0)' + expected = 'cftime.DatetimeGregorian(2000, 1, 1, 0, 0, 0, 0)' assert repr(datetimex(2000, 1, 1)) == expected From 4c577453bb7d89771711ebb26fb2f5bf940b3195 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Wed, 26 Aug 2020 20:00:31 -0600 Subject: [PATCH 02/16] update --- cftime/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cftime/__init__.py b/cftime/__init__.py index 415214a0..7138316d 100644 --- a/cftime/__init__.py +++ b/cftime/__init__.py @@ -6,5 +6,4 @@ from ._cftime import microsec_units, millisec_units, \ sec_units, hr_units, day_units, min_units from ._cftime import num2date, date2num, date2index, num2pydate -from ._cftime import to_calendar_specific_datetime from ._cftime import __version__ From 574430bca7df9c2f7c02875487953f9e48003514 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Wed, 26 Aug 2020 20:03:35 -0600 Subject: [PATCH 03/16] bump version number --- Changelog | 6 ++++-- cftime/_cftime.pyx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changelog b/Changelog index 78ae9c1d..e5b500fb 100644 --- a/Changelog +++ b/Changelog @@ -1,6 +1,8 @@ -since version 1.2.1 -=================== +version 1.2.2 (not yet released) +================================ * zero pad years in strtime (issue #194) + * have cftime.datetime create calendar-specific instances that support + addition and subtraction (issue #198.) version 1.2.1 (release tag v1.2.1rel) ===================================== diff --git a/cftime/_cftime.pyx b/cftime/_cftime.pyx index f2f96220..64b4a909 100644 --- a/cftime/_cftime.pyx +++ b/cftime/_cftime.pyx @@ -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.1' +__version__ = '1.2.2' # 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. From aa9895bfb90423a0be85a9435fa55e55fb71d443 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Wed, 26 Aug 2020 20:04:04 -0600 Subject: [PATCH 04/16] update --- Changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog b/Changelog index e5b500fb..9b9fbd0b 100644 --- a/Changelog +++ b/Changelog @@ -2,7 +2,7 @@ version 1.2.2 (not yet released) ================================ * zero pad years in strtime (issue #194) * have cftime.datetime create calendar-specific instances that support - addition and subtraction (issue #198.) + addition and subtraction (issue #198). version 1.2.1 (release tag v1.2.1rel) ===================================== From 741f22a517da5c3614d0fe9d647c2427616ef649 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Fri, 28 Aug 2020 21:23:16 -0600 Subject: [PATCH 05/16] expose base class, renamed to datetime_base --- cftime/__init__.py | 2 +- cftime/_cftime.pyx | 72 +++++++++++++++++++++++----------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/cftime/__init__.py b/cftime/__init__.py index 7138316d..b185a0be 100644 --- a/cftime/__init__.py +++ b/cftime/__init__.py @@ -1,6 +1,6 @@ from ._cftime import utime, JulianDayFromDate, DateFromJulianDay, UNIT_CONVERSION_FACTORS from ._cftime import _parse_date, date2index, time2index -from ._cftime import datetime, real_datetime +from ._cftime import datetime, real_datetime, datetime_base from ._cftime import DatetimeNoLeap, DatetimeAllLeap, Datetime360Day, DatetimeJulian, \ DatetimeGregorian, DatetimeProlepticGregorian from ._cftime import microsec_units, millisec_units, \ diff --git a/cftime/_cftime.pyx b/cftime/_cftime.pyx index 64b4a909..ccb16da6 100644 --- a/cftime/_cftime.pyx +++ b/cftime/_cftime.pyx @@ -148,7 +148,7 @@ def _dateparse(timestr,calendar): pass if not basedate: if not utc_offset: - basedate = _datetime(year, month, day, hour, minute, second, + basedate = datetime_base(year, month, day, hour, minute, second, microsecond) else: raise ValueError('cannot use utc_offset for this reference date/calendar') @@ -246,7 +246,7 @@ def date2num(dates,units,calendar='standard'): # remove time zone offset if getattr(date, 'tzinfo',None) is not None: date = date.replace(tzinfo=None) - date.utcoffset() - else: # convert date to same calendar specific cftime._datetime instance + else: # convert date to same calendar specific cftime.datetime_base instance if not isinstance(date, DATE_TYPES[calendar]): date = to_calendar_specific_datetime(date, calendar, False) if ismasked and mask.flat[n]: @@ -435,7 +435,7 @@ def num2date( **only_use_cftime_datetimes**: if False, python datetime.datetime objects are returned from num2date where possible; if True dates which - subclass cftime._datetime are returned for all calendars. Default **True**. + subclass cftime.datetime_base are returned for all calendars. Default **True**. **only_use_python_datetimes**: always return python datetime.datetime objects and raise an error if this is not possible. Ignored unless @@ -498,7 +498,7 @@ def num2date( # Through np.timedelta64, convert integers scaled to have units of # microseconds to datetime.timedelta objects, the timedelta type compatible - # with all cftime._datetime objects. + # with all cftime.datetime_base objects. deltas = scaled_times.astype("timedelta64[us]").astype(timedelta) try: return basedate + deltas @@ -848,12 +848,12 @@ def datetime(year, month, day, hour=0, minute=0, Create a calendar specific datetime instance. """ if calendar == '': - return _datetime(year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr,calendar=calendar) + return datetime_base(year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr,calendar=calendar) else: date_type = DATE_TYPES[calendar] return date_type(year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr) -cdef class _datetime(object): +cdef class datetime_base(object): """ The base class implementing most methods of datetime classes that mimic datetime.datetime but support calendars other than the proleptic @@ -866,7 +866,7 @@ Gregorial calendar. # Python's datetime.datetime uses the proleptic Gregorian # calendar. This boolean is used to decide whether a - # cftime._datetime instance can be converted to + # cftime.datetime_base instance can be converted to # datetime.datetime. cdef readonly bint datetime_compatible @@ -1009,9 +1009,9 @@ Gregorial calendar. self.second, self.microsecond) def __richcmp__(self, other, int op): - cdef _datetime dt, dt_other + cdef datetime_base dt, dt_other dt = self - if isinstance(other, _datetime): + if isinstance(other, datetime_base): dt_other = other # comparing two datetime instances if dt.calendar == dt_other.calendar: @@ -1066,11 +1066,11 @@ Gregorial calendar. return NotImplemented def __add__(self, other): - cdef _datetime dt - if isinstance(self, _datetime) and isinstance(other, timedelta): + cdef datetime_base dt + if isinstance(self, datetime_base) and isinstance(other, timedelta): dt = self delta = other - elif isinstance(self, timedelta) and isinstance(other, _datetime): + elif isinstance(self, timedelta) and isinstance(other, datetime_base): dt = other delta = self else: @@ -1078,10 +1078,10 @@ Gregorial calendar. return dt._add_timedelta(delta) def __sub__(self, other): - cdef _datetime dt - if isinstance(self, _datetime): # left arg is a datetime instance + cdef datetime_base dt + if isinstance(self, datetime_base): # left arg is a datetime instance dt = self - if isinstance(other, _datetime): + if isinstance(other, datetime_base): # datetime - datetime if dt.calendar != other.calendar: raise ValueError("cannot compute the time difference between dates with different calendars") @@ -1125,13 +1125,13 @@ datetime object.""" return NotImplemented @cython.embedsignature(True) -cdef class DatetimeNoLeap(_datetime): +cdef class DatetimeNoLeap(datetime_base): """ Phony datetime object which mimics the python datetime object, but uses the "noleap" ("365_day") calendar. """ def __init__(self, *args, **kwargs): - _datetime.__init__(self, *args, **kwargs) + datetime_base.__init__(self, *args, **kwargs) self.calendar = "noleap" self.datetime_compatible = False assert_valid_date(self, no_leap, False, has_year_zero=True) @@ -1144,13 +1144,13 @@ but uses the "noleap" ("365_day") calendar. return _dpm[self.month-1] @cython.embedsignature(True) -cdef class DatetimeAllLeap(_datetime): +cdef class DatetimeAllLeap(datetime_base): """ Phony datetime object which mimics the python datetime object, but uses the "all_leap" ("366_day") calendar. """ def __init__(self, *args, **kwargs): - _datetime.__init__(self, *args, **kwargs) + datetime_base.__init__(self, *args, **kwargs) self.calendar = "all_leap" self.datetime_compatible = False assert_valid_date(self, all_leap, False, has_year_zero=True) @@ -1163,13 +1163,13 @@ but uses the "all_leap" ("366_day") calendar. return _dpm_leap[self.month-1] @cython.embedsignature(True) -cdef class Datetime360Day(_datetime): +cdef class Datetime360Day(datetime_base): """ Phony datetime object which mimics the python datetime object, but uses the "360_day" calendar. """ def __init__(self, *args, **kwargs): - _datetime.__init__(self, *args, **kwargs) + datetime_base.__init__(self, *args, **kwargs) self.calendar = "360_day" self.datetime_compatible = False assert_valid_date(self, no_leap, False, has_year_zero=True, is_360_day=True) @@ -1182,13 +1182,13 @@ but uses the "360_day" calendar. return _dpm_360[self.month-1] @cython.embedsignature(True) -cdef class DatetimeJulian(_datetime): +cdef class DatetimeJulian(datetime_base): """ Phony datetime object which mimics the python datetime object, but uses the "julian" calendar. """ def __init__(self, *args, **kwargs): - _datetime.__init__(self, *args, **kwargs) + datetime_base.__init__(self, *args, **kwargs) self.calendar = "julian" self.datetime_compatible = False assert_valid_date(self, is_leap_julian, False) @@ -1197,7 +1197,7 @@ but uses the "julian" calendar. return DatetimeJulian(*add_timedelta(self, delta, is_leap_julian, False, False)) @cython.embedsignature(True) -cdef class DatetimeGregorian(_datetime): +cdef class DatetimeGregorian(datetime_base): """ Phony datetime object which mimics the python datetime object, but uses the mixed Julian-Gregorian ("standard", "gregorian") calendar. @@ -1211,7 +1211,7 @@ datetime.datetime instances and used to compute time differences a datetime.datetime instance or vice versa. """ def __init__(self, *args, **kwargs): - _datetime.__init__(self, *args, **kwargs) + datetime_base.__init__(self, *args, **kwargs) self.calendar = "gregorian" # dates after 1582-10-15 can be converted to and compared to @@ -1226,7 +1226,7 @@ a datetime.datetime instance or vice versa. return DatetimeGregorian(*add_timedelta(self, delta, is_leap_gregorian, True, False)) @cython.embedsignature(True) -cdef class DatetimeProlepticGregorian(_datetime): +cdef class DatetimeProlepticGregorian(datetime_base): """ Phony datetime object which mimics the python datetime object, but allows for dates that don't exist in the proleptic gregorian calendar. @@ -1237,14 +1237,14 @@ Has strftime, timetuple, replace, __repr__, and __str__ methods. The format of the string produced by __str__ is controlled by self.format (default %Y-%m-%d %H:%M:%S). Supports comparisons with other datetime instances using the same calendar; comparison with -native python datetime instances is possible for cftime._datetime +native python datetime instances is possible for cftime.datetime_base instances using 'gregorian' and 'proleptic_gregorian' calendars. Instance variables are year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr, format, and calendar. """ def __init__(self, *args, **kwargs): - _datetime.__init__(self, *args, **kwargs) + datetime_base.__init__(self, *args, **kwargs) self.calendar = "proleptic_gregorian" self.datetime_compatible = True assert_valid_date(self, is_leap_proleptic_gregorian, False) @@ -1273,7 +1273,7 @@ cdef _findall(text, substr): # calendar. ;) -cdef _strftime(_datetime dt, fmt): +cdef _strftime(datetime_base dt, fmt): if _illegal_s.search(fmt): raise TypeError("This strftime implementation does not handle %s") # don't use strftime method at all. @@ -1333,7 +1333,7 @@ cdef int * month_lengths(bint (*is_leap)(int), int year): else: return _dpm -cdef void assert_valid_date(_datetime dt, bint (*is_leap)(int), +cdef void assert_valid_date(datetime_base dt, bint (*is_leap)(int), bint julian_gregorian_mixed, bint has_year_zero=False, bint is_360_day=False) except *: @@ -1368,7 +1368,7 @@ cdef void assert_valid_date(_datetime dt, bint (*is_leap)(int), if dt.microsecond < 0 or dt.microsecond > 999999: raise ValueError("invalid microsecond provided in {0!r}".format(dt)) -# Add a datetime.timedelta to a cftime._datetime instance. Uses +# Add a datetime.timedelta to a cftime.datetime_base instance. Uses # integer arithmetic to avoid rounding errors and preserve # microsecond accuracy. # @@ -1382,7 +1382,7 @@ cdef void assert_valid_date(_datetime dt, bint (*is_leap)(int), # The date of the transition from the Julian to Gregorian calendar and # the number of invalid dates are hard-wired (1582-10-4 is the last day # of the Julian calendar, after which follows 1582-10-15). -cdef tuple add_timedelta(_datetime dt, delta, bint (*is_leap)(int), bint julian_gregorian_mixed, bint has_year_zero): +cdef tuple add_timedelta(datetime_base dt, delta, bint (*is_leap)(int), bint julian_gregorian_mixed, bint has_year_zero): cdef int microsecond, second, minute, hour, day, month, year cdef int delta_microseconds, delta_seconds, delta_days cdef int* month_length @@ -1456,13 +1456,13 @@ cdef tuple add_timedelta(_datetime dt, delta, bint (*is_leap)(int), bint julian_ return (year, month, day, hour, minute, second, microsecond, -1, -1) -# Add a datetime.timedelta to a cftime._datetime instance with the 360_day calendar. +# Add a datetime.timedelta to a cftime.datetime_base instance with the 360_day calendar. # # Assumes that the 360_day,365_day and 366_day calendars (unlike the rest of supported # calendars) have the year 0. Also, there are no leap years and all # months are 30 days long, so we can compute month and year by using # "//" and "%". -cdef tuple add_timedelta_360_day(_datetime dt, delta): +cdef tuple add_timedelta_360_day(datetime_base dt, delta): cdef int microsecond, second, minute, hour, day, month, year cdef int delta_microseconds, delta_seconds, delta_days @@ -1882,7 +1882,7 @@ def DateFromJulianDay(JD, calendar='standard', only_use_cftime_datetimes=True, if calendar='julian', Julian Day follows julian calendar. - If only_use_cftime_datetimes is set to True, then cftime._datetime + If only_use_cftime_datetimes is set to True, then cftime.datetime_base objects are returned for all calendars. Otherwise the datetime object is a native python datetime object if the date falls in the Gregorian calendar (i.e. calendar='proleptic_gregorian', or calendar = 'standard'/'gregorian' @@ -2133,7 +2133,7 @@ are: @keyword only_use_cftime_datetimes: if False, datetime.datetime objects are returned from num2date where possible; if True dates which subclass -cftime._datetime are returned for all calendars. Default True. +cftime.datetime_base are returned for all calendars. Default True. @keyword only_use_python_datetimes: always return python datetime.datetime objects and raise an error if this is not possible. Ignored unless From 67af9f03cb79ab4229276c9332152c1099926bd7 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Fri, 28 Aug 2020 21:26:25 -0600 Subject: [PATCH 06/16] update docstring --- cftime/_cftime.pyx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cftime/_cftime.pyx b/cftime/_cftime.pyx index ccb16da6..1b98b6fe 100644 --- a/cftime/_cftime.pyx +++ b/cftime/_cftime.pyx @@ -857,7 +857,8 @@ cdef class datetime_base(object): """ The base class implementing most methods of datetime classes that mimic datetime.datetime but support calendars other than the proleptic -Gregorial calendar. +Gregorial calendar. The factory function cftime.datetime should be used +to create calendar-specific sub-class instances. """ cdef readonly int year, month, day, hour, minute cdef readonly int second, microsecond From 39fe1a18c2c7a5202f92d623559521f6fbf1857b Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Sat, 29 Aug 2020 13:45:20 -0600 Subject: [PATCH 07/16] update --- Changelog | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Changelog b/Changelog index 9b9fbd0b..16d838e3 100644 --- a/Changelog +++ b/Changelog @@ -1,8 +1,11 @@ version 1.2.2 (not yet released) ================================ * zero pad years in strtime (issue #194) - * have cftime.datetime create calendar-specific instances that support - addition and subtraction (issue #198). + * have `cftime.datetime` create calendar-specific instances that support + addition and subtraction (issue #198). Name of base class changed + from `cftime.datetime` to `cftime.datetime_base` (if your are using + `isinstance(my_datetime, cftime.datetime)` this will need to be changed + to `isinstance(my_datetime, cftime.datetime_base)`. version 1.2.1 (release tag v1.2.1rel) ===================================== From 3d1c2d96287496856f1104268f1994cea0f20017 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Sat, 29 Aug 2020 16:51:49 -0600 Subject: [PATCH 08/16] add test for issue #198 --- cftime/__init__.py | 2 +- test/test_cftime.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cftime/__init__.py b/cftime/__init__.py index b185a0be..49198c68 100644 --- a/cftime/__init__.py +++ b/cftime/__init__.py @@ -1,5 +1,5 @@ from ._cftime import utime, JulianDayFromDate, DateFromJulianDay, UNIT_CONVERSION_FACTORS -from ._cftime import _parse_date, date2index, time2index +from ._cftime import _parse_date, date2index, time2index, DATE_TYPES from ._cftime import datetime, real_datetime, datetime_base from ._cftime import DatetimeNoLeap, DatetimeAllLeap, Datetime360Day, DatetimeJulian, \ DatetimeGregorian, DatetimeProlepticGregorian diff --git a/test/test_cftime.py b/test/test_cftime.py index 87a1792e..7fb6fd9c 100644 --- a/test/test_cftime.py +++ b/test/test_cftime.py @@ -819,6 +819,17 @@ def test_tz_naive(self): cal = 'proleptic_gregorian' dt2 = num2date(date2num(dt1, units, cal), units, cal) assert(dt1 == dt2) +# issue #198 - cftime.datetime creates calendar specific datetimes that +# support addition/subtraction of timedeltas. + dt = cftime.datetime(2020, 1, 1, calendar='') + assert(isinstance(dt, cftime.datetime_base)) + dt = cftime.datetime(2020, 1, 1, calendar="julian") + dt += timedelta(hours=1) + assert(str(dt) == '2020-01-01 01:00:00') + assert(isinstance(dt, cftime.DatetimeJulian)) + for cal in cftime.DATE_TYPES.keys(): + dt = cftime.datetime(2020, 1, 1, calendar=cal) + assert(isinstance(dt, cftime.DATE_TYPES[cal])) class TestDate2index(unittest.TestCase): @@ -1390,8 +1401,7 @@ def test_parse_incorrect_unitstring(self): ValueError, cftime._cftime.date2num, datetime(1900, 1, 1, 0), datestr, 'standard') -_DATE_TYPES = [DatetimeNoLeap, DatetimeAllLeap, DatetimeJulian, Datetime360Day, - DatetimeGregorian, DatetimeProlepticGregorian] +_DATE_TYPES = cftime.DATE_TYPES.values() @pytest.fixture(params=_DATE_TYPES) From 32f5797b63259bca03d35086a6e9f0fd0ef4b5c3 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Mon, 31 Aug 2020 19:50:40 -0600 Subject: [PATCH 09/16] update docstrings --- cftime/_cftime.pyx | 51 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/cftime/_cftime.pyx b/cftime/_cftime.pyx index 1b98b6fe..c2520a62 100644 --- a/cftime/_cftime.pyx +++ b/cftime/_cftime.pyx @@ -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.2' +__version__ = '1.3.0' # 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. @@ -853,12 +853,25 @@ Create a calendar specific datetime instance. date_type = DATE_TYPES[calendar] return date_type(year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr) +@cython.embedsignature(True) cdef class datetime_base(object): """ The base class implementing most methods of datetime classes that mimic datetime.datetime but support calendars other than the proleptic -Gregorial calendar. The factory function cftime.datetime should be used +Gregorian calendar. The factory function cftime.datetime should be used to create calendar-specific sub-class instances. + +Calendar specific sub-classes support timedelta operations by overloading +/-. +Comparisons with other datetime_base sub-class instances using the same +calendar are supported. +Comparison with native python datetime instances is possible +for cftime.datetime_base sub-class instances using +'gregorian' and 'proleptic_gregorian' calendars. + +Has isoformat, strftime, timetuple, replace, dayofwk, dayofyr, daysinmonth, +__repr__, and __str__ methods. +The default format of the string produced by strftime is controlled by self.format +(default %Y-%m-%d %H:%M:%S). """ cdef readonly int year, month, day, hour, minute cdef readonly int second, microsecond @@ -1130,6 +1143,10 @@ cdef class DatetimeNoLeap(datetime_base): """ Phony datetime object which mimics the python datetime object, but uses the "noleap" ("365_day") calendar. + +The factory function cftime.datetime should be used +to create instances of this class using the `calendar` kwarg to specify +the calendar. """ def __init__(self, *args, **kwargs): datetime_base.__init__(self, *args, **kwargs) @@ -1149,6 +1166,10 @@ cdef class DatetimeAllLeap(datetime_base): """ Phony datetime object which mimics the python datetime object, but uses the "all_leap" ("366_day") calendar. + +The factory function cftime.datetime should be used +to create instances of this class using the `calendar` kwarg to specify +the calendar. """ def __init__(self, *args, **kwargs): datetime_base.__init__(self, *args, **kwargs) @@ -1168,6 +1189,10 @@ cdef class Datetime360Day(datetime_base): """ Phony datetime object which mimics the python datetime object, but uses the "360_day" calendar. + +The factory function cftime.datetime should be used +to create instances of this class using the `calendar` kwarg to specify +the calendar. """ def __init__(self, *args, **kwargs): datetime_base.__init__(self, *args, **kwargs) @@ -1187,6 +1212,10 @@ cdef class DatetimeJulian(datetime_base): """ Phony datetime object which mimics the python datetime object, but uses the "julian" calendar. + +The factory function cftime.datetime should be used +to create instances of this class using the `calendar` kwarg to specify +the calendar. """ def __init__(self, *args, **kwargs): datetime_base.__init__(self, *args, **kwargs) @@ -1210,6 +1239,10 @@ Instances using the date after 1582-10-15 can be compared to datetime.datetime instances and used to compute time differences (datetime.timedelta) by subtracting a DatetimeGregorian instance from a datetime.datetime instance or vice versa. + +The factory function cftime.datetime should be used +to create instances of this class using the `calendar` kwarg to specify +the calendar. """ def __init__(self, *args, **kwargs): datetime_base.__init__(self, *args, **kwargs) @@ -1232,17 +1265,9 @@ cdef class DatetimeProlepticGregorian(datetime_base): Phony datetime object which mimics the python datetime object, but allows for dates that don't exist in the proleptic gregorian calendar. -Supports timedelta operations by overloading + and -. - -Has strftime, timetuple, replace, __repr__, and __str__ methods. The -format of the string produced by __str__ is controlled by self.format -(default %Y-%m-%d %H:%M:%S). Supports comparisons with other -datetime instances using the same calendar; comparison with -native python datetime instances is possible for cftime.datetime_base -instances using 'gregorian' and 'proleptic_gregorian' calendars. - -Instance variables are year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr, -format, and calendar. +The factory function cftime.datetime should be used +to create instances of this class using the `calendar` kwarg to specify +the calendar. """ def __init__(self, *args, **kwargs): datetime_base.__init__(self, *args, **kwargs) From cb6a78e2872e6e25d944572abf81a0c793bdff7b Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Mon, 31 Aug 2020 19:51:07 -0600 Subject: [PATCH 10/16] update --- Changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog b/Changelog index 16d838e3..c22b42d0 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,4 @@ -version 1.2.2 (not yet released) +version 1.3.0 (not yet released) ================================ * zero pad years in strtime (issue #194) * have `cftime.datetime` create calendar-specific instances that support From 3376525312cb233fade7b91635b3644befaf8183 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Sat, 5 Sep 2020 16:37:31 -0600 Subject: [PATCH 11/16] update test --- test/test_cftime.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_cftime.py b/test/test_cftime.py index 7fb6fd9c..dec1cf15 100644 --- a/test/test_cftime.py +++ b/test/test_cftime.py @@ -18,6 +18,10 @@ DatetimeGregorian, DatetimeJulian, DatetimeNoLeap, DatetimeProlepticGregorian, JulianDayFromDate, _parse_date, date2index, date2num, num2date, utime, UNIT_CONVERSION_FACTORS) +try: + from cftime import datetime_base +except ImportError: + datetime_base = datetime try: from datetime import timezone @@ -830,6 +834,7 @@ def test_tz_naive(self): for cal in cftime.DATE_TYPES.keys(): dt = cftime.datetime(2020, 1, 1, calendar=cal) assert(isinstance(dt, cftime.DATE_TYPES[cal])) + assert(isinstance(dt, datetime_base)) class TestDate2index(unittest.TestCase): From 41eb2bf711c2858aa9ec872acaa065b06d6c7e30 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Sat, 5 Sep 2020 16:40:17 -0600 Subject: [PATCH 12/16] update --- Changelog | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Changelog b/Changelog index c22b42d0..b75ac85f 100644 --- a/Changelog +++ b/Changelog @@ -3,9 +3,17 @@ version 1.3.0 (not yet released) * zero pad years in strtime (issue #194) * have `cftime.datetime` create calendar-specific instances that support addition and subtraction (issue #198). Name of base class changed - from `cftime.datetime` to `cftime.datetime_base` (if your are using - `isinstance(my_datetime, cftime.datetime)` this will need to be changed - to `isinstance(my_datetime, cftime.datetime_base)`. + from `cftime.datetime` to `cftime.datetime_base` since `cftime.datetime` + is not a factory function. Iif your are using + `isinstance(my_datetime, cftime.datetime` this will need to be changed + to `isinstance(my_datetime, cftime.datetime_base)`. To maintain backward + compatibility with older versions of cftime, use + ```python + try: + from cftime import datetime_base + except ImportError: + datetime_base = datetime + ``` version 1.2.1 (release tag v1.2.1rel) ===================================== From afca28720c994e2beb717559484ceabcfc8c3ac7 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Sat, 5 Sep 2020 16:41:47 -0600 Subject: [PATCH 13/16] update --- Changelog.md | 1 + 1 file changed, 1 insertion(+) create mode 120000 Changelog.md diff --git a/Changelog.md b/Changelog.md new file mode 120000 index 00000000..58bc9728 --- /dev/null +++ b/Changelog.md @@ -0,0 +1 @@ +Changelog \ No newline at end of file From 7d757644bfc54f2a8205b5a06bc9197a303f2846 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Sat, 5 Sep 2020 16:42:30 -0600 Subject: [PATCH 14/16] update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f883c0e..c27b9394 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Time-handling functionality from netcdf4-python [![Commits Status](https://img.shields.io/github/commits-since/UniData/cftime/latest.svg)](https://github.com/UniData/cftime/commits/master) ## News -For details on the latest updates, see the [Changelog](https://github.com/Unidata/cftime/blob/master/Changelog). +For details on the latest updates, see the [Changelog](https://github.com/Unidata/cftime/blob/master/Changelog.md). 07/20/2020: Version 1.2.1 released. Fixes a couple of regressions introduced in 1.2.0. See Changelog for details. From c5c2369d0edffc370cc0f94b696ec95acdae00a6 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Sat, 5 Sep 2020 16:45:46 -0600 Subject: [PATCH 15/16] update --- Changelog.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) mode change 120000 => 100644 Changelog.md diff --git a/Changelog.md b/Changelog.md deleted file mode 120000 index 58bc9728..00000000 --- a/Changelog.md +++ /dev/null @@ -1 +0,0 @@ -Changelog \ No newline at end of file diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 00000000..b75ac85f --- /dev/null +++ b/Changelog.md @@ -0,0 +1,100 @@ +version 1.3.0 (not yet released) +================================ + * zero pad years in strtime (issue #194) + * have `cftime.datetime` create calendar-specific instances that support + addition and subtraction (issue #198). Name of base class changed + from `cftime.datetime` to `cftime.datetime_base` since `cftime.datetime` + is not a factory function. Iif your are using + `isinstance(my_datetime, cftime.datetime` this will need to be changed + to `isinstance(my_datetime, cftime.datetime_base)`. To maintain backward + compatibility with older versions of cftime, use + ```python + try: + from cftime import datetime_base + except ImportError: + datetime_base = datetime + ``` + +version 1.2.1 (release tag v1.2.1rel) +===================================== + * 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) +===================================== + * Return the default values of dayofwk and dayofyr when calendar + is '' (issue #173). + * fix treatment of masked arrays in num2date and date2num (issue #175). + Also make sure masked arrays are output from num2date/date2num if + masked arrays are input. + * Where possible, use timedelta arithmetic to decode times exactly within + num2date (issue #171). + * Make taking the difference between two cftime datetimes to produce a + timedelta exact to the microsecond; depending on the units encoding, + this enables date2num to be exact as well (issue #109). + * utime.date2num/utime.num2date now just call module level functions. + JulianDayFromDate/DateFromJulianDay no longer used internally (PR #180). + + +version 1.1.3 (release tag v1.1.3rel) +===================================== + * add isoformat method for compatibility with python datetime (issue #152). + * make 'standard' default calendar for cftime.datetime + so that dayofwk,dayofyr methods don't fail (issue #169). + +version 1.1.2 (release tag v1.1.2rel) +===================================== + * change dayofwk and dayofyr attributes into properties (issue #158) + * fix for issue #165 (python datetime should be returned when + only_use_cftime_datetimes=False). + +version 1.1.1.2 (release tag v1.1.1.2rel) +========================================= + * include pyproject.toml in MANIFEST.in so it gets + included in source tarball (issue #154). + +version 1.1.1.1 (release tag v1.1.1.1rel) +========================================= + * Fix error installing with pip on python 3.8 by following + PEP 517 (issue #148, PR #149) + +version 1.1.1 (release tag v1.1.1rel) +===================================== + + * fix microsecond formatting issue, ensure identical results + computed for arrays and scales (issue #143, PR #146). + +version 1.1.0 (release tag v1.1.0rel) +===================================== + + * improved exceptions for time differences (issue #128, PR #131). + + * fix intersphinx entries (issue #133, PR #133) + + * make only_use_cftime_datetimes=True by default, so cftime datetime + instances are returned by default by num2date (instead of returning python + datetime instances where possible). Issue #136, PR #135. + + * Add daysinmonth attribute (issue #137, PR #138). + + * If only_use_python_datetimes=True and only_use_cftime_datetimes=False, + num2date only returns python datetime instances and raises an exception + if this is not possible. num2pydate convenience function added which just calls + num2date with only_use_python_datetimes=True and + only_use_cftime_datetimes=False. + Remove positive times check, raise ValueError if python datetime + tries to compute a date before MINYEAR (issue #134, PR #139) + + * Fix for fractional seconds in reference date in units string (issue #140, + PR # 141). + +version 1.0.4.2 release +======================= + + * fix for issue #126 (date2num error when converting a DatetimeProlepticGregorian + object). PR #127. From 449be55572e76b3de5252e932e1274b584e9bec5 Mon Sep 17 00:00:00 2001 From: Jeff Whitaker Date: Sat, 5 Sep 2020 16:47:15 -0600 Subject: [PATCH 16/16] revert --- Changelog.md | 100 --------------------------------------------------- README.md | 2 +- 2 files changed, 1 insertion(+), 101 deletions(-) delete mode 100644 Changelog.md diff --git a/Changelog.md b/Changelog.md deleted file mode 100644 index b75ac85f..00000000 --- a/Changelog.md +++ /dev/null @@ -1,100 +0,0 @@ -version 1.3.0 (not yet released) -================================ - * zero pad years in strtime (issue #194) - * have `cftime.datetime` create calendar-specific instances that support - addition and subtraction (issue #198). Name of base class changed - from `cftime.datetime` to `cftime.datetime_base` since `cftime.datetime` - is not a factory function. Iif your are using - `isinstance(my_datetime, cftime.datetime` this will need to be changed - to `isinstance(my_datetime, cftime.datetime_base)`. To maintain backward - compatibility with older versions of cftime, use - ```python - try: - from cftime import datetime_base - except ImportError: - datetime_base = datetime - ``` - -version 1.2.1 (release tag v1.2.1rel) -===================================== - * 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) -===================================== - * Return the default values of dayofwk and dayofyr when calendar - is '' (issue #173). - * fix treatment of masked arrays in num2date and date2num (issue #175). - Also make sure masked arrays are output from num2date/date2num if - masked arrays are input. - * Where possible, use timedelta arithmetic to decode times exactly within - num2date (issue #171). - * Make taking the difference between two cftime datetimes to produce a - timedelta exact to the microsecond; depending on the units encoding, - this enables date2num to be exact as well (issue #109). - * utime.date2num/utime.num2date now just call module level functions. - JulianDayFromDate/DateFromJulianDay no longer used internally (PR #180). - - -version 1.1.3 (release tag v1.1.3rel) -===================================== - * add isoformat method for compatibility with python datetime (issue #152). - * make 'standard' default calendar for cftime.datetime - so that dayofwk,dayofyr methods don't fail (issue #169). - -version 1.1.2 (release tag v1.1.2rel) -===================================== - * change dayofwk and dayofyr attributes into properties (issue #158) - * fix for issue #165 (python datetime should be returned when - only_use_cftime_datetimes=False). - -version 1.1.1.2 (release tag v1.1.1.2rel) -========================================= - * include pyproject.toml in MANIFEST.in so it gets - included in source tarball (issue #154). - -version 1.1.1.1 (release tag v1.1.1.1rel) -========================================= - * Fix error installing with pip on python 3.8 by following - PEP 517 (issue #148, PR #149) - -version 1.1.1 (release tag v1.1.1rel) -===================================== - - * fix microsecond formatting issue, ensure identical results - computed for arrays and scales (issue #143, PR #146). - -version 1.1.0 (release tag v1.1.0rel) -===================================== - - * improved exceptions for time differences (issue #128, PR #131). - - * fix intersphinx entries (issue #133, PR #133) - - * make only_use_cftime_datetimes=True by default, so cftime datetime - instances are returned by default by num2date (instead of returning python - datetime instances where possible). Issue #136, PR #135. - - * Add daysinmonth attribute (issue #137, PR #138). - - * If only_use_python_datetimes=True and only_use_cftime_datetimes=False, - num2date only returns python datetime instances and raises an exception - if this is not possible. num2pydate convenience function added which just calls - num2date with only_use_python_datetimes=True and - only_use_cftime_datetimes=False. - Remove positive times check, raise ValueError if python datetime - tries to compute a date before MINYEAR (issue #134, PR #139) - - * Fix for fractional seconds in reference date in units string (issue #140, - PR # 141). - -version 1.0.4.2 release -======================= - - * fix for issue #126 (date2num error when converting a DatetimeProlepticGregorian - object). PR #127. diff --git a/README.md b/README.md index c27b9394..3f883c0e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Time-handling functionality from netcdf4-python [![Commits Status](https://img.shields.io/github/commits-since/UniData/cftime/latest.svg)](https://github.com/UniData/cftime/commits/master) ## News -For details on the latest updates, see the [Changelog](https://github.com/Unidata/cftime/blob/master/Changelog.md). +For details on the latest updates, see the [Changelog](https://github.com/Unidata/cftime/blob/master/Changelog). 07/20/2020: Version 1.2.1 released. Fixes a couple of regressions introduced in 1.2.0. See Changelog for details.