diff --git a/Changelog b/Changelog
index 78ae9c1d..e4a48282 100644
--- a/Changelog
+++ b/Changelog
@@ -1,6 +1,11 @@
-since version 1.2.1
-===================
+version 1.3.0 (release tag v1.3.0rel)
+=====================================
* zero pad years in strtime (issue #194)
+ * have cftime.datetime constuctor create 'calendar-aware' instances (default is
+ 'standard' calendar, if calendar='' or None the instance is not calendar aware and some
+ methods, like dayofwk, dayofyr, __add__ and __sub__, will not work). Fixes issue #198.
+ The calendar specific sub-classes are now deprecated, but remain for now
+ as stubs that just instantiate the base class and override __repr__.
version 1.2.1 (release tag v1.2.1rel)
=====================================
diff --git a/cftime/_cftime.pyx b/cftime/_cftime.pyx
index 0caa6641..83ad25b8 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.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.
@@ -104,9 +104,6 @@ class real_datetime(datetime_python):
return get_days_in_month(_is_leap(self.year,'proleptic_gregorian'), self.month)
nanosecond = 0 # workaround for pandas bug (cftime issue #77)
-# start of the gregorian calendar
-gregorian = real_datetime(1582,10,15)
-
def _datesplit(timestr):
"""split a time string into two components, units and the remainder
after 'since'
@@ -137,34 +134,25 @@ def _dateparse(timestr,calendar):
# parse the date string.
year, month, day, hour, minute, second, microsecond, utc_offset =\
_parse_date( isostring.strip() )
- basedate = None
- if year >= MINYEAR and year <= MAXYEAR:
- try:
- basedate = real_datetime(year, month, day, hour, minute, second,
- microsecond)
- # subtract utc_offset from basedate time instance (which is timezone naive)
- basedate -= timedelta(days=utc_offset/1440.)
- except ValueError:
- pass
- if not basedate:
- if not utc_offset:
- 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']:
- if basedate.year == 0:
+ if year == 0:
msg='zero not allowed as a reference year, does not exist in Julian or Gregorian calendars'
raise ValueError(msg)
- if calendar in ['noleap', '365_day'] and basedate.month == 2 and basedate.day == 29:
+ if calendar in ['noleap', '365_day'] and month == 2 and day == 29:
raise ValueError(
'cannot specify a leap day as the reference time with the noleap calendar')
- if calendar == '360_day' and basedate.day > 30:
+ if calendar == '360_day' and day > 30:
raise ValueError(
'there are only 30 days in every month with the 360_day calendar')
+ basedate = datetime(year, month, day, hour, minute, second,
+ microsecond,calendar=calendar)
+ # subtract utc_offset from basedate time instance (which is timezone naive)
+ if utc_offset:
+ basedate -= timedelta(days=utc_offset/1440.)
return basedate
def _can_use_python_datetime(date,calendar):
+ gregorian = datetime(1582,10,15,calendar=calendar)
return ((calendar == 'proleptic_gregorian' and date.year >= MINYEAR and date.year <= MAXYEAR) or \
(calendar in ['gregorian','standard'] and date > gregorian and date.year <= MAXYEAR))
@@ -237,8 +225,7 @@ def date2num(dates,units,calendar='standard'):
else:
use_python_datetime = False
# convert basedate to specified calendar
- if not isinstance(basedate, DATE_TYPES[calendar]):
- basedate = to_calendar_specific_datetime(basedate, calendar, False)
+ basedate = to_calendar_specific_datetime(basedate, calendar, False)
times = []; n = 0
for date in dates.flat:
# use python datetime if possible.
@@ -247,8 +234,7 @@ def date2num(dates,units,calendar='standard'):
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
- if not isinstance(date, DATE_TYPES[calendar]):
- date = to_calendar_specific_datetime(date, calendar, False)
+ date = to_calendar_specific_datetime(date, calendar, False)
if ismasked and mask.flat[n]:
times.append(None)
else:
@@ -332,20 +318,42 @@ 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:
+# return real_datetime(
+# dt.year,
+# dt.month,
+# dt.day,
+# dt.hour,
+# dt.minute,
+# dt.second,
+# dt.microsecond)
+# else:
+# return datetime(
+# dt.year,
+# dt.month,
+# dt.day,
+# dt.hour,
+# dt.minute,
+# dt.second,
+# dt.microsecond,
+# calendar=calendar)
+# return calendar-specific subclasses for backward compatbility,
+# even though after 1.3.0 this is no longer necessary.
+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
)
@@ -691,21 +699,21 @@ def _date2index(dates, nctime, calendar=None, select='exact'):
The datetime objects should not include a time-zone offset.
**nctime**: A netCDF time variable object. The nctime object must have a
- C{units} attribute. The entries are assumed to be stored in increasing
+ `units` attribute. The entries are assumed to be stored in increasing
order.
**calendar**: Describes the calendar used in the time calculation.
- Valid calendars C{'standard', 'gregorian', 'proleptic_gregorian'
- 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'}.
- Default is C{'standard'}, which is a mixed Julian/Gregorian calendar
- If C{calendar} is None, its value is given by C{nctime.calendar} or
- C{standard} if no such attribute exists.
-
- **select**: C{'exact', 'before', 'after', 'nearest'}
- The index selection method. C{exact} will return the indices perfectly
- matching the dates given. C{before} and C{after} will return the indices
+ Valid calendars 'standard', 'gregorian', 'proleptic_gregorian'
+ 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'.
+ Default is 'standard', which is a mixed Julian/Gregorian calendar
+ If `calendar` is None, its value is given by `nctime.calendar` or
+ `standard` if no such attribute exists.
+
+ **select**: 'exact', 'before', 'after', 'nearest'
+ The index selection method. `exact` will return the indices perfectly
+ matching the dates given. `before` and `after` will return the indices
corresponding to the dates just before or just after the given dates if
- an exact match cannot be found. C{nearest} will return the indices that
+ an exact match cannot be found. `nearest` will return the indices that
correspond to the closest dates.
"""
try:
@@ -727,21 +735,21 @@ def time2index(times, nctime, calendar=None, select='exact'):
**times**: A numeric time or a sequence of numeric times.
**nctime**: A netCDF time variable object. The nctime object must have a
- C{units} attribute. The entries are assumed to be stored in increasing
+ `units` attribute. The entries are assumed to be stored in increasing
order.
**calendar**: Describes the calendar used in the time calculation.
- Valid calendars C{'standard', 'gregorian', 'proleptic_gregorian'
- 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'}.
- Default is C{'standard'}, which is a mixed Julian/Gregorian calendar
- If C{calendar} is None, its value is given by C{nctime.calendar} or
- C{standard} if no such attribute exists.
+ Valid calendars 'standard', 'gregorian', 'proleptic_gregorian'
+ 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'.
+ Default is `standard`, which is a mixed Julian/Gregorian calendar
+ If `calendar` is None, its value is given by `nctime.calendar` or
+ `standard` if no such attribute exists.
**select**: **'exact', 'before', 'after', 'nearest'**
- The index selection method. C{exact} will return the indices perfectly
- matching the times given. C{before} and C{after} will return the indices
+ The index selection method. `exact` will return the indices perfectly
+ matching the times given. `before` and `after` will return the indices
corresponding to the times just before or just after the given times if
- an exact match cannot be found. C{nearest} will return the indices that
+ an exact match cannot be found. `nearest` will return the indices that
correspond to the closest times.
"""
try:
@@ -843,14 +851,41 @@ cdef to_tuple(dt):
@cython.embedsignature(True)
cdef class datetime(object):
"""
-The base class implementing most methods of datetime classes that
-mimic datetime.datetime but support calendars other than the proleptic
-Gregorial calendar.
+This class mimics datetime.datetime but support calendars other than the proleptic
+Gregorian calendar.
+
+Supports timedelta operations by overloading +/-, and
+comparisons with other instances using the same calendar.
+
+Comparison with native python datetime instances is possible
+for cftime.datetime instances using
+'gregorian' and 'proleptic_gregorian' calendars.
+
+All the calendars currently defined in the
+[CF metadata convention](http://cfconventions.org) are supported.
+Valid calendars are 'standard', 'gregorian', 'proleptic_gregorian'
+'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'.
+Default is 'standard', which is a mixed Julian/Gregorian calendar.
+'standard' and 'gregorian' are synonyms, as are 'all_leap'/'366_day'
+and 'noleap'/'365_day'.
+
+If the calendar kwarg is set to a blank string ('') or None (the default is 'standard') the
+instance will not be calendar-aware and some methods will not work.
+
+Has isoformat, strftime, timetuple, replace, dayofwk, dayofyr, daysinmonth,
+__repr__, __add__, __sub__, __str__ and comparison methods.
+
+dayofwk, dayofyr, daysinmonth, __add__ and __sub__ only work for calendar-aware
+instances.
+
+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
cdef readonly str calendar
cdef readonly int _dayofwk, _dayofyr
+ cdef readonly bint has_year_zero
# Python's datetime.datetime uses the proleptic Gregorian
# calendar. This boolean is used to decide whether a
@@ -869,10 +904,53 @@ Gregorial calendar.
self.minute = minute
self.second = second
self.microsecond = microsecond
- self.calendar = calendar
- self.datetime_compatible = True
self._dayofwk = dayofwk
self._dayofyr = dayofyr
+ if calendar:
+ calendar = calendar.lower()
+ if calendar == 'gregorian' or calendar == 'standard':
+ # dates after 1582-10-15 can be converted to and compared to
+ # proleptic Gregorian dates
+ self.calendar = 'gregorian'
+ if self.to_tuple() >= (1582, 10, 15, 0, 0, 0, 0):
+ self.datetime_compatible = True
+ else:
+ self.datetime_compatible = False
+ assert_valid_date(self, is_leap_gregorian, True)
+ self.has_year_zero = False
+ elif calendar == 'noleap' or calendar == '365_day':
+ self.calendar = 'noleap'
+ self.datetime_compatible = False
+ assert_valid_date(self, no_leap, False, has_year_zero=True)
+ self.has_year_zero = True
+ elif calendar == 'all_leap' or calendar == '366_day':
+ self.calendar = 'all_leap'
+ self.datetime_compatible = False
+ assert_valid_date(self, all_leap, False, has_year_zero=True)
+ self.has_year_zero = True
+ elif calendar == '360_day':
+ self.calendar = calendar
+ self.datetime_compatible = False
+ assert_valid_date(self, no_leap, False, has_year_zero=True, is_360_day=True)
+ self.has_year_zero = True
+ elif calendar == 'julian':
+ self.calendar = calendar
+ self.datetime_compatible = False
+ assert_valid_date(self, is_leap_julian, False)
+ self.has_year_zero = False
+ elif calendar == 'proleptic_gregorian':
+ self.calendar = calendar
+ self.datetime_compatible = True
+ assert_valid_date(self, is_leap_proleptic_gregorian, False)
+ self.has_year_zero = False
+ elif calendar == '' or calendar is None:
+ # instance not calendar-aware, some method will not work
+ self.calendar = ''
+ self.datetime_compatible = False
+ self.has_year_zero = False
+ else:
+ raise ValueError(
+ "calendar must be one of %s, got '%s'" % (str(_calendars), calendar))
@property
def format(self):
@@ -880,9 +958,11 @@ Gregorial calendar.
@property
def dayofwk(self):
- if self._dayofwk < 0 and self.calendar != '':
- jd = _IntJulianDayFromDate(self.year,self.month,self.day,self.calendar)
- year,month,day,dayofwk,dayofyr = _IntJulianDayToDate(jd,self.calendar)
+ if self._dayofwk < 0 and self.calendar:
+ jd = _IntJulianDayFromDate(self.year,self.month,self.day,self.calendar,
+ skip_transition=False,has_year_zero=self.has_year_zero)
+ year,month,day,dayofwk,dayofyr = _IntJulianDayToDate(jd,self.calendar,
+ skip_transition=False,has_year_zero=self.has_year_zero)
# cache results for dayofwk, dayofyr
self._dayofwk = dayofwk
self._dayofyr = dayofyr
@@ -892,9 +972,11 @@ Gregorial calendar.
@property
def dayofyr(self):
- if self._dayofyr < 0 and self.calendar != '':
- jd = _IntJulianDayFromDate(self.year,self.month,self.day,self.calendar)
- year,month,day,dayofwk,dayofyr = _IntJulianDayToDate(jd,self.calendar)
+ if self._dayofyr < 0 and self.calendar:
+ jd = _IntJulianDayFromDate(self.year,self.month,self.day,self.calendar,
+ skip_transition=False,has_year_zero=self.has_year_zero)
+ year,month,day,dayofwk,dayofyr = _IntJulianDayToDate(jd,self.calendar,
+ skip_transition=False,has_year_zero=self.has_year_zero)
# cache results for dayofwk, dayofyr
self._dayofwk = dayofwk
self._dayofyr = dayofyr
@@ -904,7 +986,15 @@ Gregorial calendar.
@property
def daysinmonth(self):
- return get_days_in_month(_is_leap(self.year,self.calendar), self.month)
+ if self.calendar == 'noleap':
+ return _dpm[self.month-1]
+ elif self.calendar == 'all_leap':
+ return _dpm_leap[self.month-1]
+ elif self.calendar == '360_day':
+ return _dpm_360[self.month-1]
+ else:
+ return get_days_in_month(_is_leap(self.year,self.calendar,
+ has_year_zero=self.has_year_zero), self.month)
def strftime(self, format=None):
"""
@@ -957,9 +1047,15 @@ Gregorial calendar.
self.microsecond)
def __repr__(self):
- return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8})".format('cftime',
- self.__class__.__name__,
- self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond)
+ if self.calendar == None:
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8}, calendar={9})".format('cftime',
+ self.__class__.__name__,
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond,self.calendar)
+ else:
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8}, calendar='{9}')".format('cftime',
+ self.__class__.__name__,
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond,self.calendar)
+
def __str__(self):
return self.isoformat(' ')
@@ -1057,13 +1153,36 @@ Gregorial calendar.
cdef datetime dt
if isinstance(self, datetime) and isinstance(other, timedelta):
dt = self
+ calendar = self.calendar
delta = other
elif isinstance(self, timedelta) and isinstance(other, datetime):
dt = other
+ calendar = other.calendar
delta = self
else:
return NotImplemented
- return dt._add_timedelta(delta)
+ # return calendar-specific subclasses for backward compatbility,
+ # even though after 1.3.0 this is no longer necessary.
+ if calendar == '360_day':
+ #return dt.__class__(*add_timedelta_360_day(dt, delta),calendar=calendar)
+ return Datetime360Day(*add_timedelta_360_day(dt, delta))
+ elif calendar == 'noleap':
+ #return dt.__class__(*add_timedelta(dt, delta, no_leap, False, True),calendar=calendar)
+ return DatetimeNoLeap(*add_timedelta(dt, delta, no_leap, False, True))
+ elif calendar == 'all_leap':
+ #return dt.__class__(*add_timedelta(dt, delta, all_leap, False, True),calendar=calendar)
+ return DatetimeAllLeap(*add_timedelta(dt, delta, all_leap, False, True))
+ elif calendar == 'julian':
+ #return dt.__class__(*add_timedelta(dt, delta, is_leap_julian, False, False),calendar=calendar)
+ return DatetimeJulian(*add_timedelta(dt, delta, is_leap_julian, False, False))
+ elif calendar == 'gregorian':
+ #return dt.__class__(*add_timedelta(dt, delta, is_leap_gregorian, True, False),calendar=calendar)
+ return DatetimeGregorian(*add_timedelta(dt, delta, is_leap_gregorian, True, False))
+ elif calendar == 'proleptic_gregorian':
+ #return dt.__class__(*add_timedelta(dt, delta, is_leap_proleptic_gregorian, False, False),calendar=calendar)
+ return DatetimeProlepticGregorian(*add_timedelta(dt, delta, is_leap_proleptic_gregorian, False, False))
+ else:
+ return NotImplemented
def __sub__(self, other):
cdef datetime dt
@@ -1075,8 +1194,10 @@ Gregorial calendar.
raise ValueError("cannot compute the time difference between dates with different calendars")
if dt.calendar == "":
raise ValueError("cannot compute the time difference between dates that are not calendar-aware")
- ordinal_self = _IntJulianDayFromDate(dt.year, dt.month, dt.day, dt.calendar)
- ordinal_other = _IntJulianDayFromDate(other.year, other.month, other.day, other.calendar)
+ ordinal_self = _IntJulianDayFromDate(dt.year, dt.month, dt.day, dt.calendar,
+ skip_transition=False,has_year_zero=self.has_year_zero)
+ ordinal_other = _IntJulianDayFromDate(other.year, other.month, other.day, other.calendar,
+ skip_transition=False,has_year_zero=self.has_year_zero)
days = ordinal_self - ordinal_other
seconds_self = dt.second + 60 * dt.minute + 3600 * dt.hour
seconds_other = other.second + 60 * other.minute + 3600 * other.hour
@@ -1095,7 +1216,29 @@ datetime object."""
return dt._to_real_datetime() - other
elif isinstance(other, timedelta):
# datetime - timedelta
- return dt._add_timedelta(-other)
+ # return calendar-specific subclasses for backward compatbility,
+ # even though after 1.3.0 this is no longer necessary.
+ if self.calendar == '360_day':
+ #return self.__class__(*add_timedelta_360_day(self, -other),calendar=self.calendar)
+ return Datetime360Day(*add_timedelta_360_day(self, -other))
+ elif self.calendar == 'noleap':
+ #return self.__class__(*add_timedelta(self, -other, no_leap, False, True),calendar=self.calendar)
+ return DatetimeNoLeap(*add_timedelta(self, -other, no_leap, False, True))
+ elif self.calendar == 'all_leap':
+ #return self.__class__(*add_timedelta(self, -other, all_leap, False, True),calendar=self.calendar)
+ return DatetimeAllLeap(*add_timedelta(self, -other, all_leap, False, True))
+ elif self.calendar == 'julian':
+ #return self.__class__(*add_timedelta(self, -other, is_leap_julian, False, False),calendar=self.calendar)
+ return DatetimeJulian(*add_timedelta(self, -other, is_leap_julian, False, False))
+ elif self.calendar == 'gregorian':
+ #return self.__class__(*add_timedelta(self, -other, is_leap_gregorian, True, False),calendar=self.calendar)
+ return DatetimeGregorian(*add_timedelta(self, -other, is_leap_gregorian, True, False))
+ elif self.calendar == 'proleptic_gregorian':
+ #return self.__class__(*add_timedelta(self, -other,
+ # is_leap_proleptic_gregorian, False, False),calendar=self.calendar)
+ return DatetimeProlepticGregorian(*add_timedelta(self, -other, is_leap_proleptic_gregorian, False, False))
+ else:
+ return NotImplemented
else:
return NotImplemented
else:
@@ -1112,6 +1255,9 @@ datetime object."""
else:
return NotImplemented
+# these calendar-specific sub-classes are no longer used, but stubs
+# remain for backward compatibility.
+
@cython.embedsignature(True)
cdef class DatetimeNoLeap(datetime):
"""
@@ -1119,17 +1265,12 @@ 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)
- self.calendar = "noleap"
- self.datetime_compatible = False
- assert_valid_date(self, no_leap, False, has_year_zero=True)
-
- cdef _add_timedelta(self, delta):
- return DatetimeNoLeap(*add_timedelta(self, delta, no_leap, False, True))
-
- @property
- def daysinmonth(self):
- return _dpm[self.month-1]
+ kwargs['calendar']='noleap'
+ super().__init__(*args, **kwargs)
+ def __repr__(self):
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8})".format('cftime',
+ self.__class__.__name__,
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond)
@cython.embedsignature(True)
cdef class DatetimeAllLeap(datetime):
@@ -1138,17 +1279,12 @@ 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)
- self.calendar = "all_leap"
- self.datetime_compatible = False
- assert_valid_date(self, all_leap, False, has_year_zero=True)
-
- cdef _add_timedelta(self, delta):
- return DatetimeAllLeap(*add_timedelta(self, delta, all_leap, False, True))
-
- @property
- def daysinmonth(self):
- return _dpm_leap[self.month-1]
+ kwargs['calendar']='all_leap'
+ super().__init__(*args, **kwargs)
+ def __repr__(self):
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8})".format('cftime',
+ self.__class__.__name__,
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond)
@cython.embedsignature(True)
cdef class Datetime360Day(datetime):
@@ -1157,17 +1293,12 @@ 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)
- self.calendar = "360_day"
- self.datetime_compatible = False
- assert_valid_date(self, no_leap, False, has_year_zero=True, is_360_day=True)
-
- cdef _add_timedelta(self, delta):
- return Datetime360Day(*add_timedelta_360_day(self, delta))
-
- @property
- def daysinmonth(self):
- return _dpm_360[self.month-1]
+ kwargs['calendar']='360_day'
+ super().__init__(*args, **kwargs)
+ def __repr__(self):
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8})".format('cftime',
+ self.__class__.__name__,
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond)
@cython.embedsignature(True)
cdef class DatetimeJulian(datetime):
@@ -1176,13 +1307,12 @@ Phony datetime object which mimics the python datetime object,
but uses the "julian" calendar.
"""
def __init__(self, *args, **kwargs):
- datetime.__init__(self, *args, **kwargs)
- self.calendar = "julian"
- self.datetime_compatible = False
- assert_valid_date(self, is_leap_julian, False)
-
- cdef _add_timedelta(self, delta):
- return DatetimeJulian(*add_timedelta(self, delta, is_leap_julian, False, False))
+ kwargs['calendar']='julian'
+ super().__init__(*args, **kwargs)
+ def __repr__(self):
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8})".format('cftime',
+ self.__class__.__name__,
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond)
@cython.embedsignature(True)
cdef class DatetimeGregorian(datetime):
@@ -1199,19 +1329,12 @@ 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)
- self.calendar = "gregorian"
-
- # dates after 1582-10-15 can be converted to and compared to
- # proleptic Gregorian dates
- if self.to_tuple() >= (1582, 10, 15, 0, 0, 0, 0):
- self.datetime_compatible = True
- else:
- self.datetime_compatible = False
- assert_valid_date(self, is_leap_gregorian, True)
-
- cdef _add_timedelta(self, delta):
- return DatetimeGregorian(*add_timedelta(self, delta, is_leap_gregorian, True, False))
+ kwargs['calendar']='gregorian'
+ super().__init__(*args, **kwargs)
+ def __repr__(self):
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8})".format('cftime',
+ self.__class__.__name__,
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond)
@cython.embedsignature(True)
cdef class DatetimeProlepticGregorian(datetime):
@@ -1232,14 +1355,12 @@ Instance variables are year,month,day,hour,minute,second,microsecond,dayofwk,day
format, and calendar.
"""
def __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)
-
- cdef _add_timedelta(self, delta):
- return DatetimeProlepticGregorian(*add_timedelta(self, delta,
- is_leap_proleptic_gregorian, False, False))
+ kwargs['calendar']='proleptic_gregorian'
+ super().__init__( *args, **kwargs)
+ def __repr__(self):
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8})".format('cftime',
+ self.__class__.__name__,
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond)
_illegal_s = re.compile(r"((^|[^%])(%%)*%s)")
@@ -1489,16 +1610,16 @@ cdef tuple add_timedelta_360_day(datetime dt, delta):
# Calendar calculations base on calcals.c by David W. Pierce
# http://meteora.ucsd.edu/~pierce/calcalcs
-cdef _is_leap(int year, calendar):
+cdef _is_leap(int year, calendar, has_year_zero=False):
cdef int tyear
cdef bint leap
calendar = _check_calendar(calendar)
- if year == 0:
+ if year == 0 and not has_year_zero:
raise ValueError('year zero does not exist in the %s calendar' %\
calendar)
# Because there is no year 0 in the Julian calendar, years -1, -5, -9, etc
# are leap years.
- if year < 0:
+ if year < 0 and not has_year_zero:
tyear = year + 1
else:
tyear = year
@@ -1519,7 +1640,7 @@ cdef _is_leap(int year, calendar):
leap = False
return leap
-cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=False):
+cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=False,has_year_zero=False):
"""Compute integer Julian Day from year,month,day and calendar.
Allowed calendars are 'standard', 'gregorian', 'julian',
@@ -1541,7 +1662,7 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
date is noon UTC 0-1-1 for other calendars.
There is no year zero in standard (mixed), julian, or proleptic_gregorian
- calendars.
+ calendars by default. If has_year_zero=True, then year zero is included.
Subtract 0.5 to get 00 UTC on that day.
@@ -1569,18 +1690,18 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
return _IntJulianDayFromDate_366day(year,month,day)
# handle standard, julian, proleptic_gregorian calendars.
- if year == 0:
+ if year == 0 and not has_year_zero:
raise ValueError('year zero does not exist in the %s calendar' %\
calendar)
if (calendar == 'proleptic_gregorian' and year < -4714) or\
(calendar in ['julian','standard'] and year < -4713):
raise ValueError('year out of range for %s calendar' % calendar)
- leap = _is_leap(year,calendar)
+ leap = _is_leap(year,calendar,has_year_zero=has_year_zero)
if not leap and month == 2 and day == 29:
raise ValueError('%s is not a leap year' % year)
# add year offset
- if year < 0:
+ if year < 0 and not has_year_zero:
year += 4801
else:
year += 4800
@@ -1616,7 +1737,7 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
return jday
-cdef _IntJulianDayToDate(int jday,calendar,skip_transition=False):
+cdef _IntJulianDayToDate(int jday,calendar,skip_transition=False,has_year_zero=False):
"""Compute the year,month,day,dow,doy given the integer Julian day.
and calendar. (dow = day of week with 0=Mon,6=Sun and doy is day of year).
@@ -1665,18 +1786,18 @@ cdef _IntJulianDayToDate(int jday,calendar,skip_transition=False):
# Advance years until we find the right one
yp1 = year + 1
- if yp1 == 0:
+ if yp1 == 0 and not has_year_zero:
yp1 = 1 # no year 0
- tjday = _IntJulianDayFromDate(yp1,1,1,calendar,skip_transition=True)
+ tjday = _IntJulianDayFromDate(yp1,1,1,calendar,skip_transition=True,has_year_zero=has_year_zero)
while jday >= tjday:
year += 1
- if year == 0:
+ if year == 0 and not has_year_zero:
year = 1
yp1 = year + 1
- if yp1 == 0:
+ if yp1 == 0 and not has_year_zero:
yp1 = 1
- tjday = _IntJulianDayFromDate(yp1,1,1,calendar,skip_transition=True)
- if _is_leap(year, calendar):
+ tjday = _IntJulianDayFromDate(yp1,1,1,calendar,skip_transition=True,has_year_zero=has_year_zero)
+ if _is_leap(year, calendar,has_year_zero=has_year_zero):
dpm2use = _dpm_leap
spm2use = _spm_366day
else:
@@ -1684,12 +1805,12 @@ cdef _IntJulianDayToDate(int jday,calendar,skip_transition=False):
spm2use = _spm_365day
month = 1
tjday =\
- _IntJulianDayFromDate(year,month,dpm2use[month-1],calendar,skip_transition=True)
+ _IntJulianDayFromDate(year,month,dpm2use[month-1],calendar,skip_transition=True,has_year_zero=has_year_zero)
while jday > tjday:
month += 1
tjday =\
- _IntJulianDayFromDate(year,month,dpm2use[month-1],calendar,skip_transition=True)
- tjday = _IntJulianDayFromDate(year,month,1,calendar,skip_transition=True)
+ _IntJulianDayFromDate(year,month,dpm2use[month-1],calendar,skip_transition=True,has_year_zero=has_year_zero)
+ tjday = _IntJulianDayFromDate(year,month,1,calendar,skip_transition=True,has_year_zero=has_year_zero)
day = jday - tjday + 1
if month == 1:
doy = day
@@ -1999,51 +2120,51 @@ class utime:
Performs conversions of netCDF time coordinate
data to/from datetime objects.
-To initialize: C{t = utime(unit_string,calendar='standard')}
+To initialize: `t = utime(unit_string,calendar='standard'`
where
-B{C{unit_string}} is a string of the form
-C{'time-units since Bases: The base class implementing most methods of datetime classes that
-mimic datetime.datetime but support calendars other than the proleptic
-Gregorial calendar. This class mimics datetime.datetime but support calendars other than the proleptic
+Gregorian calendar. Supports timedelta operations by overloading +/-, and
+comparisons with other instances using the same calendar. Comparison with native python datetime instances is possible
+for cftime.datetime instances using
+‘gregorian’ and ‘proleptic_gregorian’ calendars. All the calendars currently defined in the
+[CF metadata convention](http://cfconventions.org) are supported.
+Valid calendars are ‘standard’, ‘gregorian’, ‘proleptic_gregorian’
+‘noleap’, ‘365_day’, ‘360_day’, ‘julian’, ‘all_leap’, ‘366_day’.
+Default is ‘standard’, which is a mixed Julian/Gregorian calendar.
+‘standard’ and ‘gregorian’ are synonyms, as are ‘all_leap’/’366_day’
+and ‘noleap’/’365_day’. If the calendar kwarg is set to a blank string (‘’) the
+instance will not be calendar-aware and some methods will not work. Has isoformat, strftime, timetuple, replace, dayofwk, dayofyr, daysinmonth,
+__repr__, __add__, __sub__, __str__ and comparison methods. dayofwk, dayofyr, daysinmonth, __add__ and __sub__ only work for calendar-aware
+instances. The default format of the string produced by strftime is controlled by self.format
+(default %Y-%m-%d %H:%M:%S). Return indices of a netCDF time variable corresponding to the given times. times: A numeric time or a sequence of numeric times. nctime: A netCDF time variable object. The nctime object must have a
-C{units} attribute. The entries are assumed to be stored in increasing
+units attribute. The entries are assumed to be stored in increasing
order. calendar: Describes the calendar used in the time calculation.
-Valid calendars C{‘standard’, ‘gregorian’, ‘proleptic_gregorian’
-‘noleap’, ‘365_day’, ‘360_day’, ‘julian’, ‘all_leap’, ‘366_day’}.
-Default is C{‘standard’}, which is a mixed Julian/Gregorian calendar
-If C{calendar} is None, its value is given by C{nctime.calendar} or
-C{standard} if no such attribute exists.cftime.
datetime
(int year, int month, int day, int hour=0, int minute=0, int second=0, int microsecond=0, int dayofwk=-1, int dayofyr=-1, calendar='standard')¶object
isoformat
(self, sep='T', timespec='auto')¶
select: ‘exact’, ‘before’, ‘after’, ‘nearest’ -The index selection method. C{exact} will return the indices perfectly -matching the times given. C{before} and C{after} will return the indices +The index selection method. exact will return the indices perfectly +matching the times given. before and after will return the indices corresponding to the times just before or just after the given times if -an exact match cannot be found. C{nearest} will return the indices that +an exact match cannot be found. nearest will return the indices that correspond to the closest times.
diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js index e1c53bc3..393825bf 100644 --- a/docs/_build/html/searchindex.js +++ b/docs/_build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["api","index","installing"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["api.rst","index.rst","installing.rst"],objects:{"":{cftime:[0,0,0,"-"]},"cftime.datetime":{isoformat:[0,3,1,""],replace:[0,3,1,""],strftime:[0,3,1,""],timetuple:[0,3,1,""]},cftime:{DateFromJulianDay:[0,1,1,""],DatetimeAllLeap:[0,2,1,""],DatetimeGregorian:[0,2,1,""],DatetimeJulian:[0,2,1,""],DatetimeNoLeap:[0,2,1,""],DatetimeProlepticGregorian:[0,2,1,""],JulianDayFromDate:[0,1,1,""],date2index:[0,1,1,""],date2num:[0,1,1,""],datetime:[0,2,1,""],num2date:[0,1,1,""],num2pydate:[0,1,1,""],time2index:[0,1,1,""]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method"},terms:{"1st":0,"360_dai":0,"365_dai":0,"366_dai":0,"class":0,"default":[0,2],"int":0,"new":[0,2],"return":0,"true":0,For:0,Has:0,The:[0,2],Then:2,__repr__:0,__str__:0,_cftime:0,accuraci:0,after:0,all:0,all_leap:0,allow:0,also:2,alwai:0,api:1,appear:2,appli:0,approxim:0,arg:0,argument:0,arrai:0,assum:0,attribut:0,auto:0,base:0,befor:[0,2],behavior:0,being:2,between:0,breakpoint:0,build:2,build_ext:2,calcul:0,calendar:0,can:0,cannot:0,cartopi:2,cfconvent:0,cftime:[0,2],chang:2,channel:2,check:2,climat:1,clone:2,closest:0,command:2,commun:2,compar:0,comparison:0,complet:0,comput:0,conda:2,conform:1,contain:0,control:0,convent:[0,1],correspond:0,creat:0,ctime:0,current:0,cython:2,dai:0,date2index:0,date2num:0,date:0,datefromjuliandai:0,datetim:0,datetimeallleap:0,datetimegregorian:0,datetimejulian:0,datetimenoleap:0,datetimeprolepticgregorian:0,dayofwk:0,dayofyr:0,decod:1,defin:0,depend:1,describ:0,develop:1,differ:0,difficult:2,direct:0,document:0,don:[0,2],dst:0,easiest:2,entri:0,equival:0,error:0,even:0,everyth:2,exact:0,exist:0,explicit:0,extens:2,fall:0,fals:0,field:0,file:1,first:2,flag:0,follow:0,forecast:1,forg:2,form:0,format:0,found:0,fraction:0,from:0,get:2,github:2,given:0,gregori:0,gregorian:0,have:[0,2],hour:0,http:0,ignor:0,implement:0,includ:0,increas:0,index:[0,1],indic:0,inplac:2,instal:1,instanc:0,instruct:1,isoformat:0,its:0,januari:0,julian:0,juliandayfromd:0,just:0,keyword:0,kwarg:0,last:0,later:2,librari:1,like:0,line:2,list:0,localtim:0,mai:2,maintain:2,match:0,metadata:0,method:0,microsecond:0,millisecond:0,mimic:0,minut:0,mix:0,modul:1,month:0,months_sinc:0,most:0,must:0,nativ:0,nctime:0,nearest:0,need:2,netcdf:[0,1],noleap:0,none:0,note:0,num2dat:0,num2pyd:0,number:0,numer:0,numpi:2,object:0,offset:0,one:0,onli:0,only_use_cftime_datetim:0,only_use_python_datetim:0,oper:0,order:0,org:0,origin:0,other:0,otherwis:0,overload:0,page:1,pass:2,perfectli:0,phoni:0,pip:2,place:2,possibl:0,produc:0,prolept:0,proleptic_gregorian:0,pyarg:2,pynio:2,pytest:2,python:[0,1,2],rais:0,real:0,recommend:2,refer:0,releas:2,replac:0,repositori:2,repres:0,requir:1,return_tupl:0,run:2,same:0,search:1,second:0,section:0,see:0,select:0,self:0,sep:0,sequenc:0,set:0,setup:2,should:0,sinc:0,some:0,specifi:0,standard:0,start:0,store:0,strftime:0,string:0,strptime:0,struct_tim:0,subclass:0,subtract:0,suit:2,support:0,sure:2,test:2,than:0,thei:0,thi:0,time2index:0,time:[0,1],timedelta:0,timespec:0,timetupl:0,tool:2,unit:[0,1],unless:0,updat:2,use:2,use_only_python_datetim:0,used:0,uses:0,using:[0,2],utc:0,valid:0,valu:[0,1],variabl:[0,1],versa:0,vice:0,wai:2,weekdai:0,when:2,where:0,which:0,within:0,ydai:0,year:0,you:2,zone:0},titles:["API","cftime","Installation"],titleterms:{api:0,cftime:1,content:1,depend:2,develop:2,indic:1,instal:2,instruct:2,requir:2,tabl:1}}) \ No newline at end of file +Search.setIndex({docnames:["api","index","installing"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["api.rst","index.rst","installing.rst"],objects:{"":{cftime:[0,0,0,"-"]},"cftime.datetime":{isoformat:[0,3,1,""],replace:[0,3,1,""],strftime:[0,3,1,""],timetuple:[0,3,1,""]},cftime:{DateFromJulianDay:[0,1,1,""],DatetimeAllLeap:[0,2,1,""],DatetimeGregorian:[0,2,1,""],DatetimeJulian:[0,2,1,""],DatetimeNoLeap:[0,2,1,""],DatetimeProlepticGregorian:[0,2,1,""],JulianDayFromDate:[0,1,1,""],date2index:[0,1,1,""],date2num:[0,1,1,""],datetime:[0,2,1,""],num2date:[0,1,1,""],num2pydate:[0,1,1,""],time2index:[0,1,1,""]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method"},terms:{"1st":0,"360_dai":0,"365_dai":0,"366_dai":0,"class":0,"default":[0,2],"int":0,"new":[0,2],"return":0,"true":0,For:0,Has:0,The:[0,2],Then:2,__add__:0,__repr__:0,__str__:0,__sub__:0,_cftime:0,accuraci:0,after:0,all:0,all_leap:0,allow:0,also:2,alwai:0,api:1,appear:2,appli:0,approxim:0,arg:0,argument:0,arrai:0,assum:0,attribut:0,auto:0,awar:0,base:0,befor:[0,2],behavior:0,being:2,between:0,blank:0,breakpoint:0,build:2,build_ext:2,calcul:0,calendar:0,can:0,cannot:0,cartopi:2,cfconvent:0,cftime:[0,2],chang:2,channel:2,check:2,climat:1,clone:2,closest:0,command:2,commun:2,compar:0,comparison:0,complet:0,comput:0,conda:2,conform:1,contain:0,control:0,convent:[0,1],correspond:0,creat:0,ctime:0,current:0,cython:2,dai:0,date2index:0,date2num:0,date:0,datefromjuliandai:0,datetim:0,datetimeallleap:0,datetimegregorian:0,datetimejulian:0,datetimenoleap:0,datetimeprolepticgregorian:0,dayofwk:0,dayofyr:0,daysinmonth:0,decod:1,defin:0,depend:1,describ:0,develop:1,differ:0,difficult:2,direct:0,document:0,don:[0,2],dst:0,easiest:2,entri:0,equival:0,error:0,even:0,everyth:2,exact:0,exist:0,explicit:0,extens:2,fall:0,fals:0,field:0,file:1,first:2,flag:0,follow:0,forecast:1,forg:2,form:0,format:0,found:0,fraction:0,from:0,get:2,github:2,given:0,gregori:[],gregorian:0,have:[0,2],hour:0,http:0,ignor:0,implement:[],includ:0,increas:0,index:[0,1],indic:0,inplac:2,instal:1,instanc:0,instruct:1,isoformat:0,its:0,januari:0,julian:0,juliandayfromd:0,just:0,keyword:0,kwarg:0,last:0,later:2,librari:1,like:0,line:2,list:0,localtim:0,mai:2,maintain:2,match:0,metadata:0,method:0,microsecond:0,millisecond:0,mimic:0,minut:0,mix:0,modul:1,month:0,months_sinc:0,most:[],must:0,nativ:0,nctime:0,nearest:0,need:2,netcdf:[0,1],noleap:0,none:0,note:0,num2dat:0,num2pyd:0,number:0,numer:0,numpi:2,object:0,offset:0,one:0,onli:0,only_use_cftime_datetim:0,only_use_python_datetim:0,oper:0,order:0,org:0,origin:0,other:0,otherwis:0,overload:0,page:1,pass:2,perfectli:0,phoni:0,pip:2,place:2,possibl:0,produc:0,prolept:0,proleptic_gregorian:0,pyarg:2,pynio:2,pytest:2,python:[0,1,2],rais:0,real:0,recommend:2,refer:0,releas:2,replac:0,repositori:2,repres:0,requir:1,return_tupl:0,run:2,same:0,search:1,second:0,section:0,see:0,select:0,self:0,sep:0,sequenc:0,set:0,setup:2,should:0,sinc:0,some:0,specifi:0,standard:0,start:0,store:0,strftime:0,string:0,strptime:0,struct_tim:0,subclass:0,subtract:0,suit:2,support:0,sure:2,synonym:0,test:2,than:0,thei:0,thi:0,time2index:0,time:[0,1],timedelta:0,timespec:0,timetupl:0,tool:2,unit:[0,1],unless:0,updat:2,use:2,use_only_python_datetim:0,used:0,uses:0,using:[0,2],utc:0,valid:0,valu:[0,1],variabl:[0,1],versa:0,vice:0,wai:2,weekdai:0,when:2,where:0,which:0,within:0,work:0,ydai:0,year:0,you:2,zone:0},titles:["API","cftime","Installation"],titleterms:{api:0,cftime:1,content:1,depend:2,develop:2,indic:1,instal:2,instruct:2,requir:2,tabl:1}}) \ No newline at end of file diff --git a/test/test_cftime.py b/test/test_cftime.py index 3e68b5ee..b82a5ad3 100644 --- a/test/test_cftime.py +++ b/test/test_cftime.py @@ -51,6 +51,19 @@ def dst(self, dt): dtime = namedtuple('dtime', ('values', 'units', 'calendar')) dateformat = '%Y-%m-%d %H:%M:%S' +calendars=['standard', 'gregorian', 'proleptic_gregorian', 'noleap', 'julian',\ + 'all_leap', '365_day', '366_day', '360_day'] +def adjust_calendar(calendar): + # check for and remove calendar synonyms. + calendar = calendar.lower() + if calendar == 'gregorian' or calendar == 'standard': + return 'gregorian' + elif calendar == 'noleap' or calendar == '365_day': + return 'noleap' + elif calendar == 'all_leap' or calendar == '366_day': + return 'all_leap' + else: + return calendar class CFTimeVariable(object): '''dummy "netCDF" variable to hold time info''' @@ -248,7 +261,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='') t1 = self.cdftime_360day.date2num(d) assert_almost_equal(t1, 360 * 400.) d2 = self.cdftime_360day.num2date(t1) @@ -297,7 +310,7 @@ def test_tz_naive(self): t2 = date2num(d, units='days since 0001-01-01 00:00:00') assert(abs(t2 - t) < 1e-5) # values should be less than second # Check equality testing - d1 = datetimex(1979, 6, 21, 9, 23, 12) + d1 = datetimex(1979, 6, 21, 9, 23, 12, calendar='standard') d2 = datetime(1979, 6, 21, 9, 23, 12) assert(d1 == d2) # check timezone offset @@ -371,108 +384,55 @@ def test_tz_naive(self): # test rountrip accuracy # also tests error found in issue #349 - calendars=['standard', 'gregorian', 'proleptic_gregorian', 'noleap', 'julian',\ - 'all_leap', '365_day', '366_day', '360_day'] dateref = datetime(2015,2,28,12) - ntimes = 1001 verbose = True # print out max error diagnostics + ntimes = 101 + def roundtrip(delta,eps,units): + times1 = date2num(dateref,units,calendar=calendar) + times1 += delta*np.arange(0,ntimes) + dates1 = num2date(times1,units,calendar=calendar) + times2 = date2num(dates1,units,calendar=calendar) + dates2 = num2date(times2,units,calendar=calendar) + err = np.abs(times1 - times2) + assert(np.all(err