[Git][debian-gis-team/cftime][upstream] New upstream version 1.5.0+ds
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Fri May 21 05:53:06 BST 2021
Bas Couwenberg pushed to branch upstream at Debian GIS Project / cftime
Commits:
ee2bdb35 by Bas Couwenberg at 2021-05-21T06:17:02+02:00
New upstream version 1.5.0+ds
- - - - -
6 changed files:
- Changelog
- README.md
- src/cftime/__init__.py
- src/cftime/_cftime.pyx
- − src/cftime/_cftime_legacy.pyx
- test/test_cftime.py
Changes:
=====================================
Changelog
=====================================
@@ -1,3 +1,20 @@
+version 1.5.0 (release tag v1.5.0.rel)
+======================================
+ * clean-up deprecated calendar specific subclasses (PR #231).
+ * added string formatting support to `cftime.datetime` objects
+ (via `cftime.datetime.__format__`) PR #232.
+ * add support for astronomical year numbering (including year zero) for
+ real-world calendars using 'has_year_zero' cftime.datetime kwarg (PR #234).
+ Default is False for 'real-world' calendars ('julian', 'gregorian'/'standard',
+ 'proleptic_gregorian'). Ignored for idealized calendars like '360_day
+ (they always have year zero).
+ * add "change_calendar" cftime.datetime method to switch to another
+ 'real-world' calendar. Enable comparison of cftime.datetime instances
+ with different 'real-world' calendars (using the new change_calendar method)
+ * remove legacy `utime` class, and legacy `JulianDayFromDate` and
+ `DateFromJulianDay` functions (replaced by `cftime.datetime.toordinal`
+ and `cftime.datetime.fromordinal`). PR #235.
+ * Change ValueError to TypeError in __sub__ (issue #236, PR #236).
version 1.4.1 (release tag v1.4.1.rel)
======================================
=====================================
README.md
=====================================
@@ -7,10 +7,21 @@ Time-handling functionality from netcdf4-python
[](https://github.com/Unidata/cftime/tags)
[](https://github.com/Unidata/cftime/releases)
[](https://github.com/UniData/cftime/commits/master)
+[](https://zenodo.org/badge/latestdoi/73107250)
## News
For details on the latest updates, see the [Changelog](https://github.com/Unidata/cftime/blob/master/Changelog).
+5/20/2021: Version 1.5.0 released. Includes support for astronomical year numbering
+(including the year zero) for real-world calendars ('julian', 'gregorian'/'standard',
+and 'proleptic_gregorian') using 'has_year_zero' `cftime.datetime` kwarg..
+Added a 'change_calendar' `cftime.datetime` method to switch to another
+'real-world' calendar to enable comparison of instances with different calendars.
+Some legacy classes and functions removed (`utime`, `JulianDayFromDate` and
+`DateFromJulianDay`). The functionality of `JulianDayFromDate` and
+`DateFromJulianDay` is now available from the methods `cftime.datetime.toordinal`
+and `cftime.datetime.fromordinal`.
+
2/2/2021: Version 1.4.1 released. Restore use of calendar-specific subclasses
in `cftime.num2date`, `cftime.datetime.__add__`, and `cftime.datetime.__sub__`.
The use of this will be removed in a later release.
=====================================
src/cftime/__init__.py
=====================================
@@ -1,10 +1,11 @@
-from ._cftime import datetime, real_datetime, _parse_date
+from ._cftime import (datetime, real_datetime,
+ _parse_date, _dateparse, _datesplit)
from ._cftime import num2date, date2num, date2index, time2index, num2pydate
-from ._cftime import microsec_units, millisec_units, \
- sec_units, hr_units, day_units, min_units,\
- UNIT_CONVERSION_FACTORS
-from ._cftime import __version__
-# legacy functions in _cftime_legacy.pyx
-from ._cftime import DatetimeNoLeap, DatetimeAllLeap, Datetime360Day, DatetimeJulian, \
- DatetimeGregorian, DatetimeProlepticGregorian
-from ._cftime import utime, JulianDayFromDate, DateFromJulianDay
+from ._cftime import (microsec_units, millisec_units,
+ sec_units, hr_units, day_units, min_units,
+ UNIT_CONVERSION_FACTORS)
+from ._cftime import __version__, CFWarning
+# these will be removed in a future release
+from ._cftime import (DatetimeNoLeap, DatetimeAllLeap, Datetime360Day,
+ Datetime360Day, DatetimeJulian,
+ DatetimeGregorian, DatetimeProlepticGregorian)
=====================================
src/cftime/_cftime.pyx
=====================================
@@ -15,7 +15,6 @@ from datetime import timedelta, MINYEAR, MAXYEAR
import time # strftime
import warnings
-
microsec_units = ['microseconds','microsecond', 'microsec', 'microsecs']
millisec_units = ['milliseconds', 'millisecond', 'millisec', 'millisecs', 'msec', 'msecs', 'ms']
sec_units = ['second', 'seconds', 'sec', 'secs', 's']
@@ -30,6 +29,7 @@ _units = microsec_units+millisec_units+sec_units+min_units+hr_units+day_units
# for definitions.
_calendars = ['standard', 'gregorian', 'proleptic_gregorian',
'noleap', 'julian', 'all_leap', '365_day', '366_day', '360_day']
+_idealized_calendars= ['all_leap','noleap','366_day','365_day']
# Following are number of days per month
cdef int[12] _dayspermonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
cdef int[12] _dayspermonth_leap = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
@@ -37,11 +37,7 @@ cdef int[12] _dayspermonth_leap = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 3
cdef int[13] _cumdayspermonth = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]
cdef int[13] _cumdayspermonth_leap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]
-# Reverse operator lookup for datetime.__richcmp__
-_rop_lookup = {Py_LT: '__gt__', Py_LE: '__ge__', Py_EQ: '__eq__',
- Py_GT: '__lt__', Py_GE: '__le__', Py_NE: '__ne__'}
-
-__version__ = '1.4.1'
+__version__ = '1.5.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.
@@ -86,12 +82,16 @@ def _datesplit(timestr):
return units.lower(), remainder
-def _dateparse(timestr,calendar):
+def _dateparse(timestr,calendar,has_year_zero=None):
"""parse a string of the form time-units since yyyy-mm-dd hh:mm:ss,
return a datetime instance"""
# same as version in cftime, but returns a timezone naive
# python datetime instance with the utc_offset included.
+ calendar = calendar.lower()
+ # set calendar-specific defaults for has_year_zero
+ if has_year_zero is None:
+ has_year_zero = _year_zero_defaults(calendar)
(units, isostring) = _datesplit(timestr)
if not ((units in month_units and calendar=='360_day') or units in _units):
if units in month_units and calendar != '360_day':
@@ -113,19 +113,19 @@ def _dateparse(timestr,calendar):
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)
+ microsecond,calendar=calendar,has_year_zero=has_year_zero)
# 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)
+ gregorian = datetime(1582,10,15,calendar=calendar,has_year_zero=date.has_year_zero)
return ((calendar == 'proleptic_gregorian' and date.year >= MINYEAR and date.year <= MAXYEAR) or \
(calendar in ['gregorian','standard'] and date > gregorian and date.year <= MAXYEAR))
@cython.embedsignature(True)
-def date2num(dates,units,calendar=None):
+def date2num(dates,units,calendar=None,has_year_zero=None):
"""
Return numeric time values given datetime objects. The units
of the numeric time values are described by the **units** argument
@@ -146,12 +146,24 @@ def date2num(dates,units,calendar=None):
**calendar**: describes the calendar to be used in the time calculations.
All the values currently defined in the
- [CF metadata convention](http://cfconventions.org)
+ `CF metadata convention <http://cfconventions.org>`__ are supported.
Valid calendars **'standard', 'gregorian', 'proleptic_gregorian'
'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'**.
- Default is `None` which means the calendar associated with the rist
+ Default is `None` which means the calendar associated with the first
input datetime instance will be used.
+ **has_year_zero**: If set to True, astronomical year numbering
+ is used and the year zero exists. If set to False for real-world
+ calendars, then historical year numbering is used and the year 1 is
+ preceded by year -1 and no year zero exists.
+ The defaults are False for real-world calendars
+ and True for idealized calendars.
+ The defaults can only be over-ridden for the real-world calendars,
+ for the the idealized calendars the year zero
+ always exists and the has_year_zero kwarg is ignored.
+ This kwarg is not needed to define calendar systems allowed by CF
+ (the calendar-specific defaults do this).
+
returns a numeric time value, or an array of numeric time values
with approximately 1 microsecond accuracy.
"""
@@ -178,6 +190,22 @@ def date2num(dates,units,calendar=None):
all_python_datetimes = False
break
+ # if has_year_zero is None and calendar is None, use first input cftime.datetime instance
+ # to determine if year zero is to be included.
+ # If calendar is specified, use calendar specific defaults
+ if has_year_zero is None:
+ if calendar is None:
+ d0 = dates.item(0)
+ if isinstance(d0,datetime_python):
+ has_year_zero = False
+ else:
+ try:
+ has_year_zero = d0.has_year_zero
+ except AttributeError:
+ raise ValueError('has_year_zero not specified',type(d0))
+ else:
+ has_year_zero = _year_zero_defaults(calendar)
+
# if calendar is None or '', use calendar of first input cftime.datetime instances.
# if inputs are 'real' python datetime instances, use propleptic gregorian.
if not calendar:
@@ -194,10 +222,10 @@ def date2num(dates,units,calendar=None):
raise ValueError('no calendar specified',type(d0))
calendar = calendar.lower()
- basedate = _dateparse(units,calendar=calendar)
+ basedate = _dateparse(units,calendar=calendar,has_year_zero=has_year_zero)
(unit, isostring) = _datesplit(units)
# real-world calendars cannot have zero as a reference year.
- if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
+ if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian'] and not has_year_zero:
if basedate.year == 0:
msg='zero not allowed as a reference year, does not exist in Julian or Gregorian calendars'
raise ValueError(msg)
@@ -217,7 +245,7 @@ def date2num(dates,units,calendar=None):
else:
use_python_datetime = False
# convert basedate to specified calendar
- basedate = to_calendar_specific_datetime(basedate, calendar, False)
+ basedate = to_calendar_specific_datetime(basedate, calendar, False, has_year_zero=has_year_zero)
times = []; n = 0
for date in dates.flat:
# use python datetime if possible.
@@ -226,7 +254,7 @@ def date2num(dates,units,calendar=None):
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
- date = to_calendar_specific_datetime(date, calendar, False)
+ date = to_calendar_specific_datetime(date, calendar, False, has_year_zero=has_year_zero)
if ismasked and mask.flat[n]:
times.append(None)
else:
@@ -307,7 +335,7 @@ DATE_TYPES = {
"gregorian": DatetimeGregorian
}
-def to_calendar_specific_datetime(dt, calendar, use_python_datetime):
+def to_calendar_specific_datetime(dt, calendar, use_python_datetime,has_year_zero=None):
if use_python_datetime:
return real_datetime(
dt.year,
@@ -327,7 +355,7 @@ def to_calendar_specific_datetime(dt, calendar, use_python_datetime):
dt.minute,
dt.second,
dt.microsecond,
- calendar=calendar)
+ calendar=calendar,has_year_zero=has_year_zero)
_MAX_INT64 = np.iinfo("int64").max
_MIN_INT64 = np.iinfo("int64").min
@@ -390,7 +418,8 @@ def num2date(
units,
calendar='standard',
only_use_cftime_datetimes=True,
- only_use_python_datetimes=False
+ only_use_python_datetimes=False,
+ has_year_zero=None
):
"""
Return datetime objects given numeric time values. The units
@@ -408,7 +437,7 @@ def num2date(
**calendar**: describes the calendar used in the time calculations.
All the values currently defined in the
- [CF metadata convention](http://cfconventions.org)
+ `CF metadata convention <http://cfconventions.org>`__ are supported.
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.
@@ -421,6 +450,18 @@ def num2date(
objects and raise an error if this is not possible. Ignored unless
**only_use_cftime_datetimes=False**. Default **False**.
+ **has_year_zero**: if set to True, astronomical year numbering
+ is used and the year zero exists. If set to False for real-world
+ calendars, then historical year numbering is used and the year 1 is
+ preceded by year -1 and no year zero exists.
+ The defaults are False for real-world calendars
+ and True for idealized calendars.
+ The defaults can only be over-ridden for the real-world calendars,
+ for the the idealized calendars the year zero
+ always exists and the has_year_zero kwarg is ignored.
+ This kwarg is not needed to define calendar systems allowed by CF
+ (the calendar-specific defaults do this).
+
returns a datetime instance, or an array of datetime instances with
microsecond accuracy, if possible.
@@ -437,7 +478,10 @@ def num2date(
contains one.
"""
calendar = calendar.lower()
- basedate = _dateparse(units,calendar=calendar)
+ # set calendar-specific defaults for has_year_zero
+ if has_year_zero is None:
+ has_year_zero = _year_zero_defaults(calendar)
+ basedate = _dateparse(units,calendar=calendar,has_year_zero=has_year_zero)
can_use_python_datetime=_can_use_python_datetime(basedate,calendar)
if not only_use_cftime_datetimes and only_use_python_datetimes:
@@ -448,9 +492,9 @@ def num2date(
unit, ignore = _datesplit(units)
# real-world calendars limited to positive reference years.
- if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
+ if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian'] and not has_year_zero:
if basedate.year == 0:
- msg='zero not allowed as a reference year, does not exist in Julian or Gregorian calendars'
+ msg='zero not allowed as a reference year unless has_year_zero=True'
raise ValueError(msg)
use_python_datetime = False
@@ -462,7 +506,7 @@ def num2date(
# return python datetime if possible.
use_python_datetime = True
- basedate = to_calendar_specific_datetime(basedate, calendar, use_python_datetime)
+ basedate = to_calendar_specific_datetime(basedate, calendar, use_python_datetime, has_year_zero=has_year_zero)
if unit not in UNIT_CONVERSION_FACTORS:
raise ValueError("Unsupported time units provided, {!r}.".format(unit))
@@ -487,7 +531,7 @@ def num2date(
@cython.embedsignature(True)
-def date2index(dates, nctime, calendar=None, select='exact'):
+def date2index(dates, nctime, calendar=None, select='exact', has_year_zero=None):
"""
Return indices of a netCDF time variable corresponding to the given dates.
@@ -497,14 +541,13 @@ def date2index(dates, nctime, calendar=None, select='exact'):
**nctime**: A netCDF time variable object. The nctime object must have a
**units** attribute.
- **calendar**: describes the calendar used in the time calculations.
+ **calendar**: describes the calendar to be used in the time calculations.
All the values currently defined in the
- [CF metadata convention](http://cfconventions.org)
+ `CF metadata convention <http://cfconventions.org>`__ are supported.
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.
+ Default is `None` which means the calendar associated with the first
+ input datetime instance will be used.
**select**: **'exact', 'before', 'after', 'nearest'**
The index selection method. **exact** will return the indices perfectly
@@ -513,6 +556,18 @@ def date2index(dates, nctime, calendar=None, select='exact'):
an exact match cannot be found. **nearest** will return the indices that
correspond to the closest dates.
+ **has_year_zero**: if set to True, astronomical year numbering
+ is used and the year zero exists. If set to False for real-world
+ calendars, then historical year numbering is used and the year 1 is
+ preceded by year -1 and no year zero exists.
+ The defaults are False for real-world calendars
+ and True for idealized calendars.
+ The defaults can only be over-ridden for the real-world calendars,
+ for the the idealized calendars the year zero
+ always exists and the has_year_zero kwarg is ignored.
+ This kwarg is not needed to define calendar systems allowed by CF
+ (the calendar-specific defaults do this).
+
returns an index (indices) of the netCDF time variable corresponding
to the given datetime object(s).
"""
@@ -520,22 +575,51 @@ def date2index(dates, nctime, calendar=None, select='exact'):
nctime.units
except AttributeError:
raise AttributeError("netcdf time variable is missing a 'units' attribute")
- if calendar == None:
- calendar = getattr(nctime, 'calendar', 'standard')
+
+ dates_test = np.asanyarray(dates) # convert to numpy array
+
+ # if has_year_zero is None and calendar is None, use first input cftime.datetime instance
+ # to determine if year zero is to be included.
+ # If calendar is specified, use calendar specific defaults
+ if has_year_zero is None:
+ if calendar is None:
+ d0 = dates_test.item(0)
+ if isinstance(d0,datetime_python):
+ has_year_zero = False
+ else:
+ try:
+ has_year_zero = d0.has_year_zero
+ except AttributeError:
+ raise ValueError('has_year_zero not specified',type(d0))
+ else:
+ has_year_zero = _year_zero_defaults(calendar)
+
+ # if calendar is None or '', use calendar of first input cftime.datetime instances.
+ # if inputs are 'real' python datetime instances, use propleptic gregorian.
+ if not calendar:
+ d0 = dates_test.item(0)
+ if isinstance(d0,datetime_python):
+ calendar = 'proleptic_gregorian'
+ else:
+ try:
+ calendar = d0.calendar
+ except AttributeError:
+ raise ValueError('no calendar specified',type(d0))
+
calendar = calendar.lower()
- basedate = _dateparse(nctime.units,calendar=calendar)
+ basedate = _dateparse(nctime.units,calendar=calendar,has_year_zero=has_year_zero)
# real-world calendars limited to positive reference years.
- if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
+ if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian'] and not has_year_zero:
if basedate.year == 0:
msg='zero not allowed as a reference year, does not exist in Julian or Gregorian calendars'
raise ValueError(msg)
if _can_use_python_datetime(basedate,calendar):
# use python datetime
- times = date2num(dates,nctime.units,calendar=calendar)
+ times = date2num(dates,nctime.units,calendar=calendar,has_year_zero=has_year_zero)
return time2index(times, nctime, calendar, select)
else: # use cftime module for other cases
- return _date2index(dates, nctime, calendar, select)
+ return _date2index(dates, nctime, calendar, select, has_year_zero=has_year_zero)
cdef _parse_timezone(tzstring):
@@ -597,7 +681,7 @@ cpdef _parse_date(datestring):
int(groups["fraction"]),\
tzoffset_mins
-cdef _check_index(indices, times, nctime, calendar, select):
+cdef _check_index(indices, times, nctime, select):
"""Return True if the time indices given correspond to the given times,
False otherwise.
@@ -612,9 +696,6 @@ cdef _check_index(indices, times, nctime, calendar, select):
nctime : netCDF Variable object
NetCDF time object.
- calendar : string
- Calendar of nctime.
-
select : string
Index selection method.
"""
@@ -661,10 +742,8 @@ cdef _check_index(indices, times, nctime, calendar, select):
return np.all(delta_check <= delta_after) and np.all(delta_check <= delta_before)
-def _date2index(dates, nctime, calendar=None, select='exact'):
+def _date2index(dates, nctime, calendar=None, select='exact', has_year_zero=None):
"""
- _date2index(dates, nctime, calendar=None, select='exact')
-
Return indices of a netCDF time variable corresponding to the given dates.
**dates**: A datetime object or a sequence of datetime objects.
@@ -687,15 +766,24 @@ def _date2index(dates, nctime, calendar=None, select='exact'):
corresponding to the dates just before or just after the given dates if
an exact match cannot be found. `nearest` will return the indices that
correspond to the closest dates.
+
+ **has_year_zero**: if set to True, astronomical year numbering
+ is used and the year zero exists. If set to False for real-world
+ calendars, then historical year numbering is used and the year 1 is
+ preceded by year -1 and no year zero exists.
+ The defaults are False for real-world calendars
+ and True for idealized calendars.
+ The defaults can only be over-ridden for the real-world calendars,
+ for the the idealized calendars the year zero
+ always exists and the has_year_zero kwarg is ignored.
+ This kwarg is not needed to define calendar systems allowed by CF
+ (the calendar-specific defaults do this).
"""
try:
nctime.units
except AttributeError:
raise AttributeError("netcdf time variable is missing a 'units' attribute")
- # Setting the calendar.
- if calendar == None:
- calendar = getattr(nctime, 'calendar', 'standard')
- times = date2num(dates,nctime.units,calendar=calendar)
+ times = date2num(dates,nctime.units,calendar=calendar, has_year_zero=has_year_zero)
return time2index(times, nctime, calendar=calendar, select=select)
@@ -753,7 +841,7 @@ def time2index(times, nctime, calendar=None, select='exact'):
# Checking that the index really corresponds to the given time.
# If the times do not correspond, then it means that the times
# are not increasing uniformly and we try the bisection method.
- if not _check_index(index, times, nctime, calendar, select):
+ if not _check_index(index, times, nctime, select):
# Use the bisection method. Assumes nctime is ordered.
import bisect
@@ -820,6 +908,24 @@ cdef to_tuple(dt):
return (dt.year, dt.month, dt.day, dt.hour, dt.minute,
dt.second, dt.microsecond)
+cdef _year_zero_defaults(calendar):
+ if calendar: calendar = calendar.lower()
+ if calendar in ['standard','gregorian','julian']:
+ return False
+ elif calendar in ['proleptic_gregorian']:
+ #return True # ISO 8601 year zero=1 BC
+ return False
+ elif calendar in _idealized_calendars:
+ return True
+ else:
+ return False
+
+# factory function without optional kwargs that can be used in datetime.__reduce__
+def _create_datetime(args, kwargs): return datetime(*args, **kwargs)
+# custorm warning for invalid CF dates.
+class CFWarning(UserWarning):
+ pass
+
@cython.embedsignature(True)
cdef class datetime(object):
"""
@@ -834,7 +940,7 @@ 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.
+`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.
@@ -844,8 +950,20 @@ 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.
+If the has_year_zero kwarg is set to True, astronomical year numbering
+is used and the year zero exists. If set to False for real-world
+calendars, then historical year numbering is used and the year 1 is
+preceded by year -1 and no year zero exists.
+The defaults are False for real-world calendars
+and True for idealized calendars.
+The defaults can only be over-ridden for the real-world calendars,
+for the the idealized calendars the year zero
+always exists and the has_year_zero kwarg is ignored.
+This kwarg is not needed to define calendar systems allowed by CF
+(the calendar-specific defaults do this).
+
Has isoformat, strftime, timetuple, replace, dayofwk, dayofyr, daysinmonth,
-__repr__, __add__, __sub__, __str__ and comparison methods.
+__repr__,__format__, __add__, __sub__, __str__ and comparison methods.
dayofwk, dayofyr, daysinmonth, __add__ and __sub__ only work for calendar-aware
instances.
@@ -868,7 +986,7 @@ The default format of the string produced by strftime is controlled by self.form
def __init__(self, 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'):
+ int dayofyr = -1, calendar='standard', has_year_zero=None):
self.year = year
self.month = month
@@ -882,6 +1000,16 @@ The default format of the string produced by strftime is controlled by self.form
self.tzinfo = None
if calendar:
calendar = calendar.lower()
+ # set calendar-specific defaults for has_year_zero
+ if has_year_zero is None:
+ has_year_zero = _year_zero_defaults(calendar)
+ if not has_year_zero and calendar in _idealized_calendars:
+ warnings.warn('has_year_zero kwarg ignored for idealized calendars (always True)')
+ #if (calendar in ['julian','gregorian','standard'] and year <= 0) or\
+ # (calendar == 'proleptic_gregorian' and not has_year_zero and year < 1):
+ # msg="this date/calendar/year zero convention is not supported by CF"
+ # warnings.warn(msg,category=CFWarning)
+ self.has_year_zero = has_year_zero
if calendar == 'gregorian' or calendar == 'standard':
# dates after 1582-10-15 can be converted to and compared to
# proleptic Gregorian dates
@@ -890,38 +1018,31 @@ The default format of the string produced by strftime is controlled by self.form
self.datetime_compatible = True
else:
self.datetime_compatible = False
- assert_valid_date(self, is_leap_gregorian, True)
- self.has_year_zero = False
+ assert_valid_date(self, is_leap_gregorian, True, has_year_zero=has_year_zero)
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
+ assert_valid_date(self, is_leap_julian, False, has_year_zero=has_year_zero)
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
+ assert_valid_date(self, is_leap_proleptic_gregorian, False, has_year_zero=has_year_zero)
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))
@@ -981,6 +1102,14 @@ The default format of the string produced by strftime is controlled by self.form
format = self.format
return _strftime(self, format)
+ def __format__(self, format):
+ # the string format "{t_obj}".format(t_obj=t_obj)
+ # without an explicit format gives an empty string (format='')
+ # so set this to None to get the default strftime behaviour
+ if format == '':
+ format = None
+ return self.strftime(format)
+
def replace(self, **kwargs):
"""Return datetime with new specified fields."""
args = {"year": self.year,
@@ -1022,14 +1151,18 @@ The default format of the string produced by strftime is controlled by self.form
self.microsecond)
def __repr__(self):
+ if self.__class__.__name__ != 'datetime': # a calendar-specific sub-class
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8}, has_year_zero={9})".format('cftime',
+ self.__class__.__name__,
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond,self.has_year_zero)
if self.calendar == None:
- return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8}, calendar={9})".format('cftime',
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8}, calendar={9}, has_year_zero={10})".format('cftime',
self.__class__.__name__,
- self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond,self.calendar)
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond,self.calendar,self.has_year_zero)
else:
- return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8}, calendar='{9}')".format('cftime',
+ return "{0}.{1}({2}, {3}, {4}, {5}, {6}, {7}, {8}, calendar='{9}', has_year_zero={10})".format('cftime',
self.__class__.__name__,
- self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond,self.calendar)
+ self.year,self.month,self.day,self.hour,self.minute,self.second,self.microsecond,self.calendar,self.has_year_zero)
def __str__(self):
return self.isoformat(' ')
@@ -1068,19 +1201,27 @@ The default format of the string produced by strftime is controlled by self.form
self.second, self.microsecond)
def __richcmp__(self, other, int op):
- cdef datetime dt, dt_other
+ cdef datetime dt, dt_other, d1, d2
dt = self
if isinstance(other, datetime):
dt_other = other
# comparing two datetime instances
- if dt.calendar == dt_other.calendar:
+ if dt.calendar == dt_other.calendar and dt.has_year_zero == dt_other.has_year_zero:
return PyObject_RichCompare(dt.to_tuple(), dt_other.to_tuple(), op)
else:
- # Note: it *is* possible to compare datetime
- # instances that use difference calendars by using
- # date2num, but this implementation does
- # not attempt it.
- raise TypeError("cannot compare {0!r} and {1!r} (different calendars)".format(dt, dt_other))
+ # convert both to common calendar (ISO 8601), then compare
+ try:
+ if self.calendar == 'proleptic_gregorian' and self.has_year_zero:
+ d1 = self
+ else:
+ d1 = self.change_calendar('proleptic_gregorian',has_year_zero=True)
+ if other.calendar == 'proleptic_gregorian' and other.has_year_zero:
+ d2 = other
+ else:
+ d2 = other.change_calendar('proleptic_gregorian',has_year_zero=True)
+ except ValueError: # change_calendar won't work for idealized calendars (ValueError)
+ raise TypeError("cannot compare {0!r} and {1!r}".format(dt, dt_other))
+ return PyObject_RichCompare(d1.to_tuple(), d2.to_tuple(), op)
elif isinstance(other, datetime_python):
# comparing datetime and real_datetime
if not dt.datetime_compatible:
@@ -1091,37 +1232,65 @@ The default format of the string produced by strftime is controlled by self.form
return NotImplemented
cdef _getstate(self):
- return (self.year, self.month, self.day, self.hour,
- self.minute, self.second, self.microsecond,
- self._dayofwk, self._dayofyr, self.calendar)
+ """return args and kwargs needed to create class instance"""
+ args = (self.year, self.month, self.day)
+ kwargs = {'hour': self.hour,
+ 'minute': self.minute,
+ 'second': self.second,
+ 'microsecond': self.microsecond,
+ 'dayofwk': self._dayofwk,
+ 'dayofyr': self._dayofyr,
+ 'calendar': self.calendar,
+ 'has_year_zero': self.has_year_zero}
+ return args, kwargs
def __reduce__(self):
"""special method that allows instance to be pickled"""
- return (self.__class__, self._getstate())
+ args, kwargs = self._getstate()
+ return (_create_datetime, (args, kwargs))
cdef _add_timedelta(self, other):
return NotImplemented
@staticmethod
- def fromordinal(jday,calendar='standard'):
- """Create a datetime instance from a julian day ordinal and calendar
- (inverseeof toordinal)."""
+ def fromordinal(jday,calendar='standard',has_year_zero=None):
+ """Create a datetime instance from a julian day ordinal, calendar
+ and (optionally) year zero convention (inverse of toordinal). The
+ Julian day number is the number of days since noon UTC January 1, 4713
+ in the proleptic julian calendar with no year zero (November 24, 4713
+ in the proleptic gregorian calendar that includes the year zero). For
+ idealized calendars, the origin is noon UTC of the year zero."""
+ calendar = calendar.lower()
+ # set calendar-specific defaults for has_year_zero
+ if has_year_zero is None:
+ has_year_zero = _year_zero_defaults(calendar)
if calendar in ['standard','julian','gregorian']:
- units = 'days since -4713-1-1-12'
+ if has_year_zero:
+ units = 'days since -4712-1-1-12'
+ else:
+ units = 'days since -4713-1-1-12'
elif calendar == 'proleptic_gregorian':
- units = 'days since -4714-11-24-12'
+ if has_year_zero:
+ units = 'days since -4713-11-24-12'
+ else:
+ units = 'days since -4714-11-24-12'
else:
units = 'days since 0-1-1-12'
- return num2date(jday,units=units,calendar=calendar)
+ # suppress warning about invalid CF date (year <= 0)
+ #with warnings.catch_warnings():
+ # warnings.simplefilter("ignore",category=CFWarning)
+ jd = num2date(jday,units=units,calendar=calendar,has_year_zero=has_year_zero)
+ return jd
def toordinal(self,fractional=False):
"""Return (integer) julian day ordinal.
Day 0 starts at noon January 1 of the year -4713 for the
- julian, gregorian and standard calendars.
+ julian, gregorian and standard calendars (year -4712 if year
+ zero allowd).
Day 0 starts at noon on November 24 of the year -4714 for the
- proleptic gregorian calendar.
+ proleptic gregorian calendar (year -4713 if year zero allowed).
Day 0 starts at noon on January 1 of the year zero is for the
360_day, 365_day, 366_day, all_leap and noleap calendars.
@@ -1142,15 +1311,27 @@ The default format of the string produced by strftime is controlled by self.form
else:
return ijd
+ def change_calendar(self,calendar,has_year_zero=None):
+ cdef datetime dt
+ """return a new cftime.datetime instance with a different 'real-world' calendar."""
+ if calendar in _idealized_calendars or self.calendar in _idealized_calendars:
+ raise ValueError('change_calendar only works for real-world calendars')
+ # fixed frame of reference is days since -4713-1-1 in the Julian calendar with no year zero
+ return self.fromordinal(self.toordinal(fractional=True),
+ calendar=calendar,has_year_zero=has_year_zero)
+
def __add__(self, other):
cdef datetime dt
+ cdef bint has_year_zero
if isinstance(self, datetime) and isinstance(other, timedelta):
dt = self
calendar = self.calendar
+ has_year_zero = self.has_year_zero
delta = other
elif isinstance(self, timedelta) and isinstance(other, datetime):
dt = other
calendar = other.calendar
+ has_year_zero = other.has_year_zero
delta = self
else:
return NotImplemented
@@ -1166,27 +1347,30 @@ The default format of the string produced by strftime is controlled by self.form
#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))
+ #return dt.__class__(*add_timedelta(dt, delta, is_leap_julian, False, has_year_zero),calendar=calendar,has_year_zero=has_year_zero)
+ return DatetimeJulian(*add_timedelta(dt, delta, is_leap_julian, False, has_year_zero),has_year_zero=has_year_zero)
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))
+ #return dt.__class__(*add_timedelta(dt, delta, is_leap_gregorian, True, has_year_zero),calendar=calendar,has_year_zero=has_year_zero)
+ return DatetimeGregorian(*add_timedelta(dt, delta, is_leap_gregorian, True, has_year_zero),has_year_zero=has_year_zero)
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))
+ #return dt.__class__(*add_timedelta(dt, delta, is_leap_proleptic_gregorian, False, has_year_zero),calendar=calendar,has_year_zero=has_year_zero)
+ return DatetimeProlepticGregorian(*add_timedelta(dt, delta, is_leap_proleptic_gregorian, False, has_year_zero),has_year_zero=has_year_zero)
else:
return NotImplemented
def __sub__(self, other):
cdef datetime dt
+ cdef bint has_year_zero
if isinstance(self, datetime): # left arg is a datetime instance
dt = self
if isinstance(other, datetime):
# datetime - datetime
if dt.calendar != other.calendar:
- raise ValueError("cannot compute the time difference between dates with different calendars")
+ raise TypeError("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")
+ raise TypeError("cannot compute the time difference between dates that are not calendar-aware")
+ if dt.has_year_zero != other.has_year_zero:
+ raise TypeError("cannot compute the time difference between dates with year zero conventions")
ordinal_self = self.toordinal() # julian day
ordinal_other = other.toordinal()
days = ordinal_self - ordinal_other
@@ -1203,12 +1387,13 @@ Cannot compute the time difference between dates with different calendars.
One of the datetime objects may have been converted to a native python
datetime instance. Try using only_use_cftime_datetimes=True when creating the
datetime object."""
- raise ValueError(msg)
+ raise TypeError(msg)
return dt._to_real_datetime() - other
elif isinstance(other, timedelta):
# datetime - timedelta
# return calendar-specific subclasses for backward compatbility,
# even though after 1.3.0 this is no longer necessary.
+ has_year_zero=self.has_year_zero
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))
@@ -1219,15 +1404,17 @@ datetime object."""
#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))
+ #return self.__class__(*add_timedelta(self, -other,
+ # is_leap_julian, False, has_year_zero),calendar=self.calendar,has_year_zero=self.has_year_zero)
+ return DatetimeJulian(*add_timedelta(self, -other, is_leap_julian, False, has_year_zero),has_year_zero=self.has_year_zero)
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))
+ #return self.__class__(*add_timedelta(self, -other,
+ # is_leap_gregorian, True, has_year_zero),calendar=self.calendar,has_year_zero=self.has_year_zero)
+ return DatetimeGregorian(*add_timedelta(self, -other, is_leap_gregorian, True, has_year_zero),has_year_zero=self.has_year_zero)
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))
+ # is_leap_proleptic_gregorian, False, has_year_zero),calendar=self.calendar,has_year_zero=self.has_year_zero)
+ return DatetimeProlepticGregorian(*add_timedelta(self, -other, is_leap_proleptic_gregorian, False, has_year_zero),has_year_zero=self.has_year_zero)
else:
return NotImplemented
else:
@@ -1241,7 +1428,7 @@ Cannot compute the time difference between dates with different calendars.
One of the datetime objects may have been converted to a native python
datetime instance. Try using only_use_cftime_datetimes=True when creating the
datetime object."""
- raise ValueError(msg)
+ raise TypeError(msg)
return self - other._to_real_datetime()
else:
return NotImplemented
@@ -1300,33 +1487,33 @@ cdef _strftime(datetime dt, fmt):
s = s[:site] + syear + s[site + 4:]
return s
-cdef bint is_leap_julian(int year):
+cdef bint is_leap_julian(int year, bint has_year_zero):
"Return 1 if year is a leap year in the Julian calendar, 0 otherwise."
- return _is_leap(year, calendar='julian')
+ return _is_leap(year, calendar='julian',has_year_zero=has_year_zero)
-cdef bint is_leap_proleptic_gregorian(int year):
+cdef bint is_leap_proleptic_gregorian(int year, bint has_year_zero):
"Return 1 if year is a leap year in the Proleptic Gregorian calendar, 0 otherwise."
- return _is_leap(year, calendar='proleptic_gregorian')
+ return _is_leap(year, calendar='proleptic_gregorian',has_year_zero=has_year_zero)
-cdef bint is_leap_gregorian(int year):
+cdef bint is_leap_gregorian(int year, bint has_year_zero):
"Return 1 if year is a leap year in the Gregorian calendar, 0 otherwise."
- return _is_leap(year, calendar='standard')
+ return _is_leap(year, calendar='standard',has_year_zero=has_year_zero)
-cdef bint all_leap(int year):
+cdef bint all_leap(int year, bint has_year_zero):
"Return True for all years."
return True
-cdef bint no_leap(int year):
+cdef bint no_leap(int year, bint has_year_zero):
"Return False for all years."
return False
-cdef int * month_lengths(bint (*is_leap)(int), int year):
- if is_leap(year):
+cdef int * month_lengths(bint (*is_leap)(int,bint), int year, bint has_year_zero):
+ if is_leap(year,has_year_zero):
return _dayspermonth_leap
else:
return _dayspermonth
-cdef void assert_valid_date(datetime dt, bint (*is_leap)(int),
+cdef void assert_valid_date(datetime dt, bint (*is_leap)(int, bint),
bint julian_gregorian_mixed,
bint has_year_zero=False,
bint is_360_day=False) except *:
@@ -1338,7 +1525,8 @@ cdef void assert_valid_date(datetime dt, bint (*is_leap)(int),
if is_360_day:
month_length = 12*[30]
else:
- month_length = month_lengths(is_leap, dt.year)
+ month_length = month_lengths(is_leap, dt.year, has_year_zero)
+
if dt.month < 1 or dt.month > 12:
raise ValueError("invalid month provided in {0!r}".format(dt))
@@ -1375,7 +1563,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), 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
@@ -1395,7 +1583,7 @@ cdef tuple add_timedelta(datetime dt, delta, bint (*is_leap)(int), bint julian_g
month = dt.month
year = dt.year
- month_length = month_lengths(is_leap, year)
+ month_length = month_lengths(is_leap, year, has_year_zero)
n_invalid_dates = 10 if julian_gregorian_mixed else 0
@@ -1423,7 +1611,7 @@ cdef tuple add_timedelta(datetime dt, delta, bint (*is_leap)(int), bint julian_g
year -= 1
if year == 0 and not has_year_zero:
year = -1
- month_length = month_lengths(is_leap, year)
+ month_length = month_lengths(is_leap, year, has_year_zero)
day = month_length[month-1]
else:
day += delta_days
@@ -1441,7 +1629,7 @@ cdef tuple add_timedelta(datetime dt, delta, bint (*is_leap)(int), bint julian_g
year += 1
if year == 0 and not has_year_zero:
year = 1
- month_length = month_lengths(is_leap, year)
+ month_length = month_lengths(is_leap, year, has_year_zero)
day = 1
else:
day += delta_days
@@ -1491,15 +1679,18 @@ cdef tuple add_timedelta_360_day(datetime dt, delta):
return (year, month, day, hour, minute, second, microsecond, -1, -1)
-cdef _is_leap(int year, calendar, has_year_zero=False):
+cdef _is_leap(int year, calendar, has_year_zero=None):
cdef int tyear
cdef bint leap
calendar = _check_calendar(calendar)
+ # set calendar-specific defaults for has_year_zero
+ if has_year_zero is None:
+ has_year_zero = _year_zero_defaults(calendar)
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.
+ # are leap years. year zero is a leap year if it exists.
if year < 0 and not has_year_zero:
tyear = year + 1
else:
@@ -1523,6 +1714,7 @@ cdef _is_leap(int year, calendar, has_year_zero=False):
cdef _check_calendar(calendar):
"""validate calendars, convert to subset of names to get rid of synonyms"""
+ calendar = calendar.lower()
if calendar not in _calendars:
raise ValueError('unsupported calendar')
calout = calendar
@@ -1542,7 +1734,7 @@ cdef _check_calendar(calendar):
# with modifications to handle non-real-world calendars and negative years.
-cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=False,has_year_zero=False):
+cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=False,has_year_zero=None):
"""Compute integer Julian Day from year,month,day and calendar.
Allowed calendars are 'standard', 'gregorian', 'julian',
@@ -1553,18 +1745,22 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
'all_leap' is a synonym for '366_day'
'gregorian' is a synonym for 'standard'
- Negative years allowed back to -4714
- (proleptic_gregorian) or -4713 (standard or gregorian calendar).
-
- Negative year values are allowed in 360_day,365_day,366_day calendars.
-
Integer julian day is number of days since noon UTC -4713-1-1
in the julian or mixed julian/gregorian calendar, or noon UTC
- -4714-11-24 in the proleptic_gregorian calendar. Reference
- date is noon UTC 0-1-1 for other calendars.
-
- There is no year zero in standard (mixed), julian, or proleptic_gregorian
- calendars by default. If has_year_zero=True, then year zero is included.
+ -4714-11-24 in the proleptic_gregorian calendar (without year zero).
+ Reference date is noon UTC 0-1-1 for other calendars.
+
+ If the has_year_zero kwarg is set to True, astronomical year numbering
+ is used and the year zero exists. If set to False for real-world
+ calendars, then historical year numbering is used and the year 1 is
+ preceded by year -1 and no year zero exists.
+ The defaults are False for real-world calendars
+ and True for idealized calendars.
+ The defaults can only be over-ridden for the real-world calendars,
+ for the the idealized calendars the year zero
+ always exists and the has_year_zero kwarg is ignored.
+ This kwarg is not needed to define calendar systems allowed by CF
+ (the calendar-specific defaults do this).
Subtract 0.5 to get 00 UTC on that day.
@@ -1581,7 +1777,9 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
msg = "date %04d-%02d-%02d does not exist in the %s calendar" %\
(year,month,day,calendar)
raise ValueError(msg)
-
+ # set calendar-specific defaults for has_year_zero
+ if has_year_zero is None:
+ has_year_zero = _year_zero_defaults(calendar)
if calendar == '360_day':
return year*360 + (month-1)*30 + day - 1
elif calendar == '365_day':
@@ -1595,9 +1793,6 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
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,has_year_zero=has_year_zero)
if not leap and month == 2 and day == 29:
raise ValueError('%s is not a leap year' % year)
@@ -1634,6 +1829,8 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
else:
return jday_greg
+# legacy calendar specific sub-classes (will be removed in a future release).
+
@cython.embedsignature(True)
cdef class DatetimeNoLeap(datetime):
"""
@@ -1643,14 +1840,6 @@ but uses the "noleap" ("365_day") calendar.
def __init__(self, *args, **kwargs):
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)
- cdef _getstate(self):
- return (self.year, self.month, self.day, self.hour,
- self.minute, self.second, self.microsecond,
- self._dayofwk, self._dayofyr)
@cython.embedsignature(True)
cdef class DatetimeAllLeap(datetime):
@@ -1661,14 +1850,6 @@ but uses the "all_leap" ("366_day") calendar.
def __init__(self, *args, **kwargs):
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)
- cdef _getstate(self):
- return (self.year, self.month, self.day, self.hour,
- self.minute, self.second, self.microsecond,
- self._dayofwk, self._dayofyr)
@cython.embedsignature(True)
cdef class Datetime360Day(datetime):
@@ -1679,14 +1860,6 @@ but uses the "360_day" calendar.
def __init__(self, *args, **kwargs):
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)
- cdef _getstate(self):
- return (self.year, self.month, self.day, self.hour,
- self.minute, self.second, self.microsecond,
- self._dayofwk, self._dayofyr)
@cython.embedsignature(True)
cdef class DatetimeJulian(datetime):
@@ -1697,71 +1870,23 @@ but uses the "julian" calendar.
def __init__(self, *args, **kwargs):
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)
- cdef _getstate(self):
- return (self.year, self.month, self.day, self.hour,
- self.minute, self.second, self.microsecond,
- self._dayofwk, self._dayofyr)
-
@cython.embedsignature(True)
cdef class DatetimeGregorian(datetime):
"""
Phony datetime object which mimics the python datetime object,
but uses the mixed Julian-Gregorian ("standard", "gregorian") calendar.
-
-The last date of the Julian calendar is 1582-10-4, which is followed
-by 1582-10-15, using the Gregorian calendar.
-
-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.
"""
def __init__(self, *args, **kwargs):
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)
- cdef _getstate(self):
- return (self.year, self.month, self.day, self.hour,
- self.minute, self.second, self.microsecond,
- self._dayofwk, self._dayofyr)
@cython.embedsignature(True)
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.
-
-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
-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):
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)
- cdef _getstate(self):
- return (self.year, self.month, self.day, self.hour,
- self.minute, self.second, self.microsecond,
- self._dayofwk, self._dayofyr)
-
-# include legacy stuff no longer used by cftime.datetime
-include "_cftime_legacy.pyx"
=====================================
src/cftime/_cftime_legacy.pyx deleted
=====================================
@@ -1,504 +0,0 @@
-# stuff below no longer used by cftime.datetime, kept here for backwards compatibility.
-
-# The following function (_IntJulianDayToDate) is based on
-# algorithms described in the book
-# "Calendrical Calculations" by Dershowitz and Rheingold, 3rd edition, Cambridge University Press, 2007
-# and the C implementation provided at https://reingold.co/calendar.C
-# with modifications to handle non-real-world calendars and negative years.
-
-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).
-
- Allowed calendars are 'standard', 'gregorian', 'julian',
- 'proleptic_gregorian','360_day', '365_day', '366_day', 'noleap',
- 'all_leap'.
-
- 'noleap' is a synonym for '365_day'
- 'all_leap' is a synonym for '366_day'
- 'gregorian' is a synonym for 'standard'
-
- optional kwarg 'skip_transition': When True, assume a 10-day
- gap in Julian day numbers between Oct 4 and Oct 15 1582 (the transition
- from Julian to Gregorian calendars). Default False, ignored
- unless calendar = 'standard'."""
- cdef int year,month,day,dow,doy,yp1,jday_count,nextra
- cdef int[12] dayspermonth
- cdef int[13] cumdayspermonth
-
- # validate inputs.
- calendar = _check_calendar(calendar)
-
- # compute day of week.
- dow = (jday + 1) % 7
- # convert to ISO 8601 (0 = Monday, 6 = Sunday), like python datetime
- dow -= 1
- if dow == -1: dow = 6
-
- # handle all calendars except standard, julian, proleptic_gregorian.
- if calendar == '360_day':
- year = jday//360
- nextra = jday - year*360
- doy = nextra + 1 # Julday numbering starts at 0, doy starts at 1
- month = nextra//30 + 1
- day = doy - (month-1)*30
- return year,month,day,dow,doy
- elif calendar == '365_day':
- year = jday//365
- nextra = jday - year*365
- doy = nextra + 1 # Julday numbering starts at 0, doy starts at 1
- month = 1
- while doy > _cumdayspermonth[month]:
- month += 1
- day = doy - _cumdayspermonth[month-1]
- return year,month,day,dow,doy
- elif calendar == '366_day':
- year = jday//366
- nextra = jday - year*366
- doy = nextra + 1 # Julday numbering starts at 0, doy starts at 1
- month = 1
- while doy > _cumdayspermonth_leap[month]:
- month += 1
- day = doy - _cumdayspermonth_leap[month-1]
- return year,month,day,dow,doy
-
- # handle standard, julian, proleptic_gregorian calendars.
- if jday < 0:
- raise ValueError('julian day must be a positive integer')
-
- # start with initial guess of year that is before jday=1 in both
- # Julian and Gregorian calendars.
- year = jday//366 - 4714
-
- # account for 10 days in Julian/Gregorian transition.
- if not skip_transition and calendar == 'standard' and jday > 2299160:
- jday += 10
-
- yp1 = year + 1
- if yp1 == 0 and not has_year_zero:
- yp1 = 1 # no year 0
- # initialize jday_count to Jan 1 of next year
- jday_count = _IntJulianDayFromDate(yp1,1,1,calendar,skip_transition=True,has_year_zero=has_year_zero)
- # Advance years until we find the right one
- # (stop iteration when jday_count jday >= specified jday)
- while jday >= jday_count:
- year += 1
- if year == 0 and not has_year_zero:
- year = 1
- yp1 = year + 1
- if yp1 == 0 and not has_year_zero:
- yp1 = 1
- jday_count = _IntJulianDayFromDate(yp1,1,1,calendar,skip_transition=True,has_year_zero=has_year_zero)
- # now we know year.
- # set days in specified month, cumulative days in computed year.
- if _is_leap(year, calendar,has_year_zero=has_year_zero):
- dayspermonth = _dayspermonth_leap
- cumdayspermonth = _cumdayspermonth_leap
- else:
- dayspermonth = _dayspermonth
- cumdayspermonth = _cumdayspermonth
- # initialized month to Jan, initialize jday_count to end of Jan of
- # calculated year.
- month = 1
- jday_count =\
- _IntJulianDayFromDate(year,month,dayspermonth[month-1],calendar,skip_transition=True,has_year_zero=has_year_zero)
- # now iterate by month until jday_count >= specified jday
- while jday > jday_count:
- month += 1
- jday_count =\
- _IntJulianDayFromDate(year,month,dayspermonth[month-1],calendar,skip_transition=True,has_year_zero=has_year_zero)
- # back up jday_count to 1st day of computed month
- jday_count = _IntJulianDayFromDate(year,month,1,calendar,skip_transition=True,has_year_zero=has_year_zero)
- # now jday_count represents day 1 of computed month in computed year
- # so computed day is just difference between jday_count and specified jday.
- day = jday - jday_count + 1
- # compute day in specified year.
- doy = cumdayspermonth[month-1]+day
- return year,month,day,dow,doy
-
-def _round_half_up(x):
- # 'round half up' so 0.5 rounded to 1 (instead of 0 as in numpy.round)
- return np.ceil(np.floor(2.*x)/2.)
-
- at cython.embedsignature(True)
-def JulianDayFromDate(date, calendar='standard'):
- """JulianDayFromDate(date, calendar='standard')
-
- creates a Julian Day from a 'datetime-like' object. Returns the fractional
- Julian Day (approximately 100 microsecond accuracy).
-
- if calendar='standard' or 'gregorian' (default), Julian day follows Julian
- Calendar on and before 1582-10-5, Gregorian calendar after 1582-10-15.
-
- if calendar='proleptic_gregorian', Julian Day follows gregorian calendar.
-
- if calendar='julian', Julian Day follows julian calendar.
- """
-
- # check if input was scalar and change return accordingly
- isscalar = False
- try:
- date[0]
- except:
- isscalar = True
-
- date = np.atleast_1d(np.array(date))
- year = np.empty(len(date), dtype=np.int32)
- month = year.copy()
- day = year.copy()
- hour = year.copy()
- minute = year.copy()
- second = year.copy()
- microsecond = year.copy()
- jd = np.empty(year.shape, np.longdouble)
- cdef long double[:] jd_view = jd
- cdef Py_ssize_t i_max = len(date)
- cdef Py_ssize_t i
- for i in range(i_max):
- d = date[i]
- if getattr(d, 'tzinfo', None) is not None:
- d = d.replace(tzinfo=None) - d.utcoffset()
-
- year[i] = d.year
- month[i] = d.month
- day[i] = d.day
- hour[i] = d.hour
- minute[i] = d.minute
- second[i] = d.second
- microsecond[i] = d.microsecond
- jd_view[i] = <double>_IntJulianDayFromDate(<int>year[i],<int>month[i],<int>day[i],calendar)
-
- # at this point jd is an integer representing noon UTC on the given
- # year,month,day.
- # compute fractional day from hour,minute,second,microsecond
- fracday = hour / 24.0 + minute / 1440.0 + (second + microsecond/1.e6) / 86400.0
- jd = jd - 0.5 + fracday
-
- if isscalar:
- return jd[0]
- else:
- return jd
-
- at cython.embedsignature(True)
-def DateFromJulianDay(JD, calendar='standard', only_use_cftime_datetimes=True,
- return_tuple=False):
- """
-
- returns a 'datetime-like' object given Julian Day. Julian Day is a
- fractional day with approximately 100 microsecond accuracy.
-
- if calendar='standard' or 'gregorian' (default), Julian day follows Julian
- Calendar on and before 1582-10-5, Gregorian calendar after 1582-10-15.
-
- if calendar='proleptic_gregorian', Julian Day follows gregorian calendar.
-
- if calendar='julian', Julian Day follows julian calendar.
-
- 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'
- and the date is after 1582-10-15).
- """
-
- julian = np.atleast_1d(np.array(JD, dtype=np.longdouble))
-
- def getdateinfo(julian):
- # get the day (Z) and the fraction of the day (F)
- # use 'round half up' rounding instead of numpy's even rounding
- # so that 0.5 is rounded to 1.0, not 0 (cftime issue #49)
- Z = np.atleast_1d(np.int32(_round_half_up(julian)))
- F = (julian + 0.5 - Z).astype(np.longdouble)
-
- cdef Py_ssize_t i_max = len(Z)
- year = np.empty(i_max, dtype=np.int32)
- month = np.empty(i_max, dtype=np.int32)
- day = np.empty(i_max, dtype=np.int32)
- dayofyr = np.zeros(i_max,dtype=np.int32)
- dayofwk = np.zeros(i_max,dtype=np.int32)
- cdef int ijd
- cdef Py_ssize_t i
- for i in range(i_max):
- ijd = Z[i]
- year[i],month[i],day[i],dayofwk[i],dayofyr[i] = _IntJulianDayToDate(ijd,calendar)
-
- if calendar in ['standard', 'gregorian']:
- ind_before = np.where(julian < 2299160.5)
- ind_before = np.asarray(ind_before).any()
- else:
- ind_before = False
-
- # compute hour, minute, second, microsecond, convert to int32
- hour = np.clip((F * 24.).astype(np.int64), 0, 23)
- F -= hour / 24.
- minute = np.clip((F * 1440.).astype(np.int64), 0, 59)
- second = np.clip((F - minute / 1440.) * 86400., 0, None)
- microsecond = (second % 1)*1.e6
- hour = hour.astype(np.int32)
- minute = minute.astype(np.int32)
- second = second.astype(np.int32)
- microsecond = microsecond.astype(np.int32)
-
- return year,month,day,hour,minute,second,microsecond,dayofyr,dayofwk,ind_before
-
- year,month,day,hour,minute,second,microsecond,dayofyr,dayofwk,ind_before =\
- getdateinfo(julian)
- # round to nearest second if within ms_eps microseconds
- # (to avoid ugly errors in datetime formatting - alternative
- # to adding small offset all the time as was done previously)
- # see netcdf4-python issue #433 and cftime issue #78
- # this is done by rounding microsends up or down, then
- # recomputing year,month,day etc
- # ms_eps is proportional to julian day,
- # about 47 microseconds in 2000 for Julian base date in -4713
- ms_eps = np.atleast_1d(np.array(np.finfo(np.float64).eps,np.longdouble))
- ms_eps = 86400000000.*np.maximum(ms_eps*julian, ms_eps)
- microsecond = np.where(microsecond < ms_eps, 0, microsecond)
- indxms = microsecond > 1000000-ms_eps
- if indxms.any():
- julian[indxms] = julian[indxms] + 2*ms_eps[indxms]/86400000000..
- year[indxms],month[indxms],day[indxms],hour[indxms],minute[indxms],second[indxms],microsecond2,dayofyr[indxms],dayofwk[indxms],ind_before2 =\
- getdateinfo(julian[indxms])
- microsecond[indxms] = 0
-
- # check if input was scalar and change return accordingly
- isscalar = False
- try:
- JD[0]
- except:
- isscalar = True
-
- if calendar == 'proleptic_gregorian':
- # datetime.datetime does not support years < 1
- #if year < 0:
- if only_use_cftime_datetimes:
- datetime_type = DatetimeProlepticGregorian
- else:
- if (year < 0).any(): # netcdftime issue #28
- datetime_type = DatetimeProlepticGregorian
- else:
- datetime_type = real_datetime
- elif calendar in ('standard', 'gregorian'):
- # return a 'real' datetime instance if calendar is proleptic
- # Gregorian or Gregorian and all dates are after the
- # Julian/Gregorian transition
- if ind_before and not only_use_cftime_datetimes:
- datetime_type = real_datetime
- else:
- datetime_type = DatetimeGregorian
- elif calendar == "julian":
- datetime_type = DatetimeJulian
- elif calendar in ["noleap","365_day"]:
- datetime_type = DatetimeNoLeap
- elif calendar in ["all_leap","366_day"]:
- datetime_type = DatetimeAllLeap
- elif calendar == "360_day":
- datetime_type = Datetime360Day
- else:
- raise ValueError("unsupported calendar: {0}".format(calendar))
-
- if not isscalar:
- if return_tuple:
- return np.array([args for args in
- zip(year, month, day, hour, minute, second,
- microsecond,dayofwk,dayofyr)])
- else:
- return np.array([datetime_type(*args)
- for args in
- zip(year, month, day, hour, minute, second,
- microsecond)])
-
- else:
- if return_tuple:
- return (year[0], month[0], day[0], hour[0],
- minute[0], second[0], microsecond[0],
- dayofwk[0], dayofyr[0])
- else:
- return datetime_type(year[0], month[0], day[0], hour[0],
- minute[0], second[0], microsecond[0])
-
-class utime:
-
- """
-Performs conversions of netCDF time coordinate
-data to/from datetime objects.
-
-To initialize: `t = utime(unit_string,calendar='standard'`
-
-where
-
-`unit_string` is a string of the form
-`time-units since <time-origin>` defining the time units.
-
-Valid time-units are days, hours, minutes and seconds (the singular forms
-are also accepted). An example unit_string would be `hours
-since 0001-01-01 00:00:00`. months is allowed as a time unit
-*only* for the 360_day calendar.
-
-The calendar keyword describes the calendar used in the time calculations.
-All the values currently defined in the U{CF metadata convention
-<http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.1/cf-conventions.html#time-coordinate>}
-are accepted. The default is 'standard', which corresponds to the mixed
-Gregorian/Julian calendar used by the udunits library. Valid calendars
-are:
-
-'gregorian' or 'standard' (default):
-
-Mixed Gregorian/Julian calendar as defined by udunits.
-
-'proleptic_gregorian':
-
-A Gregorian calendar extended to dates before 1582-10-15. That is, a year
-is a leap year if either (i) it is divisible by 4 but not by 100 or (ii)
-it is divisible by 400.
-
-'noleap' or '365_day':
-
-Gregorian calendar without leap years, i.e., all years are 365 days long..
-all_leap or 366_day Gregorian calendar with every year being a leap year,
-i.e., all years are 366 days long.
-
-'360_day':
-
-All years are 360 days divided into 30 day months.
-
-'julian':
-
-Proleptic Julian calendar, extended to dates after 1582-10-5. A year is a
-leap year if it is divisible by 4.
-
-The num2date and date2num class methods can used to convert datetime
-instances to/from the specified time units using the specified calendar.
-
-Example usage:
-
->>> from cftime import utime
->>> from datetime import datetime
->>> cdftime = utime('hours since 0001-01-01 00:00:00')
->>> date = datetime.now()
->>> print date
-2016-10-05 08:46:27.245015
->>>
->>> t = cdftime.date2num(date)
->>> print t
-17669840.7742
->>>
->>> date = cdftime.num2date(t)
->>> print date
-2016-10-05 08:46:27.244996
->>>
-
-The resolution of the transformation operation is approximately a microsecond.
-
-Warning: Dates between 1582-10-5 and 1582-10-15 do not exist in the
-'standard' or 'gregorian' calendars. An exception will be raised if you pass
-a 'datetime-like' object in that range to the date2num class method.
-
-Words of Wisdom from the British MetOffice concerning reference dates:
-
-"udunits implements the mixed Gregorian/Julian calendar system, as
-followed in England, in which dates prior to 1582-10-15 are assumed to use
-the Julian calendar. Other software cannot be relied upon to handle the
-change of calendar in the same way, so for robustness it is recommended
-that the reference date be later than 1582. If earlier dates must be used,
-it should be noted that udunits treats 0 AD as identical to 1 AD."
-
- at ivar origin: datetime instance defining the origin of the netCDF time variable.
- at ivar calendar: the calendar used (as specified by the `calendar` keyword).
- at ivar unit_string: a string defining the the netCDF time variable.
- at ivar units: the units part of `unit_string` (i.e. 'days', 'hours', 'seconds').
- """
-
- def __init__(self, unit_string, calendar='standard',
- only_use_cftime_datetimes=True,only_use_python_datetimes=False):
- """
- at param unit_string: a string of the form
-`time-units since <time-origin>` defining the time units.
-
-Valid time-units are days, hours, minutes and seconds (the singular forms
-are also accepted). An example unit_string would be `hours
-since 0001-01-01 00:00:00`. months is allowed as a time unit
-*only* for the 360_day calendar.
-
- at keyword calendar: describes the calendar used in the time calculations.
-All the values currently defined in the U{CF metadata convention
-<http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.1/cf-conventions.html#time-coordinate>}
-are accepted. The default is `standard`, which corresponds to the mixed
-Gregorian/Julian calendar used by the udunits library. Valid calendars
-are:
- - `gregorian` or `standard` (default):
- Mixed Gregorian/Julian calendar as defined by udunits.
- - `proleptic_gregorian`:
- A Gregorian calendar extended to dates before 1582-10-15. That is, a year
- is a leap year if either (i) it is divisible by 4 but not by 100 or (ii)
- it is divisible by 400.
- - `noleap` or `365_day`:
- Gregorian calendar without leap years, i.e., all years are 365 days long.
- - `all_leap` or `366_day`:
- Gregorian calendar with every year being a leap year, i.e.,
- all years are 366 days long.
- -`360_day`:
- All years are 360 days divided into 30 day months.
- -`julian`:
- Proleptic Julian calendar, extended to dates after 1582-10-5. A year is a
- leap year if it is divisible by 4.
-
- at 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.
-
- at keyword only_use_python_datetimes: always return python datetime.datetime
-objects and raise an error if this is not possible. Ignored unless
-**only_use_cftime_datetimes=False**. Default **False**.
-
- at returns: A class instance which may be used for converting times from netCDF
-units to datetime objects.
- """
- calendar = calendar.lower()
- if calendar in _calendars:
- self.calendar = calendar
- else:
- raise ValueError(
- "calendar must be one of %s, got '%s'" % (str(_calendars), calendar))
- self.origin = _dateparse(unit_string,calendar=calendar)
- units, isostring = _datesplit(unit_string)
- self.units = units
- self.unit_string = unit_string
- self.only_use_cftime_datetimes = only_use_cftime_datetimes
- self.only_use_python_datetimes = only_use_python_datetimes
-
- def date2num(self, date):
- """
- Returns `time_value` in units described by `unit_string`, using
- the specified `calendar`, given a 'datetime-like' object.
-
- The datetime object must represent UTC with no time-zone offset.
- If there is a time-zone offset implied by L{unit_string}, it will
- be applied to the returned numeric values.
-
- Resolution is approximately a microsecond.
-
- If calendar = 'standard' or 'gregorian' (indicating
- that the mixed Julian/Gregorian calendar is to be used), an
- exception will be raised if the 'datetime-like' object describes
- a date between 1582-10-5 and 1582-10-15.
-
- Works for scalars, sequences and numpy arrays.
- Returns a scalar if input is a scalar, else returns a numpy array.
- """
- return date2num(date,self.unit_string,calendar=self.calendar)
-
- def num2date(self, time_value):
- """
- Return a 'datetime-like' object given a `time_value` in units
- described by `unit_string`, using `calendar`.
-
- dates are in UTC with no offset, even if L{unit_string} contains
- a time zone offset from UTC.
-
- Resolution is approximately a microsecond.
-
- Works for scalars, sequences and numpy arrays.
- Returns a scalar if input is a scalar, else returns a numpy array.
- """
- return num2date(time_value,self.unit_string,calendar=self.calendar,only_use_cftime_datetimes=self.only_use_cftime_datetimes,only_use_python_datetimes=self.only_use_python_datetimes)
=====================================
test/test_cftime.py
=====================================
@@ -4,6 +4,7 @@ import copy
import operator
import sys
import unittest
+import warnings
from collections import namedtuple
from datetime import datetime, timedelta, MINYEAR
@@ -14,10 +15,10 @@ from numpy.testing import assert_almost_equal, assert_equal
import cftime
from cftime import datetime as datetimex
from cftime import real_datetime
-from cftime import (DateFromJulianDay, Datetime360Day, DatetimeAllLeap,
+from cftime import (Datetime360Day, DatetimeAllLeap,
DatetimeGregorian, DatetimeJulian, DatetimeNoLeap,
- DatetimeProlepticGregorian, JulianDayFromDate, _parse_date,
- date2index, date2num, num2date, utime, UNIT_CONVERSION_FACTORS)
+ DatetimeProlepticGregorian, _parse_date,
+ date2index, date2num, num2date, UNIT_CONVERSION_FACTORS)
try:
from datetime import timezone
@@ -43,6 +44,24 @@ except ImportError: # python2.7
return timedelta(hours=0)
+# legacy class included here since tests use it.
+class utime:
+ def __init__(self, unit_string, calendar='standard',
+ only_use_cftime_datetimes=True,
+ only_use_python_datetimes=False):
+ calendar = calendar.lower()
+ units, isostring = cftime._datesplit(unit_string)
+ self.origin = cftime._dateparse(unit_string,calendar=calendar)
+ self.units = units
+ self.calendar = calendar
+ self.unit_string = unit_string
+ self.only_use_cftime_datetimes = only_use_cftime_datetimes
+ self.only_use_python_datetimes = only_use_python_datetimes
+ def date2num(self, date):
+ return date2num(date,self.unit_string,calendar=self.calendar)
+ def num2date(self, time_value):
+ return num2date(time_value,self.unit_string,calendar=self.calendar,only_use_cftime_datetimes=self.only_use_cftime_datetimes,only_use_python_datetimes=self.only_use_python_datetimes)
+
utc = timezone(timedelta(hours=0), 'UTC')
est = timezone(timedelta(hours=-5), 'UTC')
@@ -291,16 +310,16 @@ class cftimeTestCase(unittest.TestCase):
self.assertTrue(str(d) == str(date))
# test julian day from date, date from julian day
d = cftime.datetime(1858, 11, 17, calendar='standard')
- # toordinal should produce same result as JulianDayFromDate
+ # astronomical year numbering (with year zero)
+ dz = cftime.datetime(1858, 11, 17, calendar='standard',has_year_zero=True)
+ # toordinal (with fractional = True) is same as old JulianDayFromDate
mjd1 = d.toordinal(fractional=True)
- mjd2 = JulianDayFromDate(d)
+ mjd1z = dz.toordinal(fractional=True)
assert_almost_equal(mjd1, 2400000.5)
- assert_almost_equal(mjd1,mjd2)
- # fromordinal should produce the same result as DateFromJulianDay
- date1 = DateFromJulianDay(mjd1)
- date2 = cftime.datetime.fromordinal(mjd1)
+ assert_almost_equal(mjd1z, 2400000.5)
+ # fromordinal (same as old DateFromJulianDay)
+ date1 = cftime.datetime.fromordinal(mjd1)
self.assertTrue(str(date1) == str(d))
- self.assertTrue(str(date1) == str(date2))
# test iso 8601 units string
d = datetime(1970, 1, 1, 1)
t = self.cdftime_iso.date2num(d)
@@ -559,18 +578,27 @@ class cftimeTestCase(unittest.TestCase):
# caused incorrect rountrip num2date(date2num(date)) roundtrip for dates with year
# < 0.
u = utime("seconds since 1-1-1",calendar='julian')
- date1 = datetimex(-1, 1, 1)
- date2 = u.num2date(u.date2num(date1))
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ date1 = datetimex(-1, 1, 1)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ date2 = u.num2date(u.date2num(date1))
assert (date2.year == date1.year)
assert (date2.month == date1.month)
assert (date2.day == date1.day)
assert (date2.hour == date1.hour)
assert (date2.minute == date1.minute)
assert (date2.second == date1.second)
- assert_almost_equal(JulianDayFromDate(date1), 1721057.5)
+ assert_almost_equal(cftime.datetime.toordinal(date1,fractional=True), 1721057.5)
# issue 596 - negative years fail in utime.num2date
- u = utime("seconds since 1-1-1", "proleptic_gregorian")
- d = u.num2date(u.date2num(datetimex(-1, 1, 1)))
+ units="seconds since 1-1-1"
+ calendar="proleptic_gregorian"
+ yrzero=False
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ din=datetimex(-1,1,1,calendar=calendar,has_year_zero=yrzero)
+ d=num2date(date2num(din,units),units,calendar=calendar,has_year_zero=yrzero)
assert (d.year == -1)
assert (d.month == 1)
assert (d.day == 1)
@@ -611,7 +639,7 @@ class cftimeTestCase(unittest.TestCase):
# n should always be 0 as all units refer to the same point in time
assert_almost_equal(n, 0)
# cftime issue #49
- d = DateFromJulianDay(2450022.5, "standard")
+ d = cftime.datetime.fromordinal(2450022.5, calendar="standard")
assert (d.year == 1995)
assert (d.month == 11)
assert (d.day == 1)
@@ -619,20 +647,22 @@ class cftimeTestCase(unittest.TestCase):
assert (d.minute == 0)
assert (d.second == 0)
# cftime issue #52
- d = DateFromJulianDay(1684958.5,calendar='gregorian')
- assert (d.year == -100)
- assert (d.month == 3)
- assert (d.day == 2)
- assert (d.hour == 0)
- assert (d.minute == 0)
- assert (d.second == 0)
- d = DateFromJulianDay(1684958.5,calendar='standard')
- assert (d.year == -100)
- assert (d.month == 3)
- assert (d.day == 2)
- assert (d.hour == 0)
- assert (d.minute == 0)
- assert (d.second == 0)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ d = cftime.datetime.fromordinal(1684958.5,calendar='gregorian')
+ assert (d.year == -100)
+ assert (d.month == 3)
+ assert (d.day == 2)
+ assert (d.hour == 0)
+ assert (d.minute == 0)
+ assert (d.second == 0)
+ d = cftime.datetime.fromordinal(1684958.5,calendar='standard')
+ assert (d.year == -100)
+ assert (d.month == 3)
+ assert (d.day == 2)
+ assert (d.hour == 0)
+ assert (d.minute == 0)
+ assert (d.second == 0)
# test dayofwk, dayofyr attribute setting (cftime issue #13)
d1 = DatetimeGregorian(2020,2,29)
d2 = real_datetime(2020,2,29)
@@ -679,9 +709,11 @@ class cftimeTestCase(unittest.TestCase):
# gives 2446433 (365 days more - is it counting year 0?)
# however http://aa.usno.navy.mil/data/docs/JulianDate.php gives
# 2446068, which agrees with us
- units = "days since -4713-01-01T00:00:00Z"
- t = date2num(datetime(1985,1,2), units, calendar="standard")
- assert_almost_equal(t, 2446068)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ units = "days since -4713-01-01T00:00:00Z"
+ t = date2num(datetime(1985,1,2), units, calendar="standard")
+ assert_almost_equal(t, 2446068)
# issue #68: allow months since for 360_day calendar
d = num2date(1, 'months since 0000-01-01 00:00:00', calendar='360_day')
@@ -743,7 +775,7 @@ class cftimeTestCase(unittest.TestCase):
test = dates == np.ma.masked_array([datetime(1848, 1, 17, 6, 0, 0, 40), None],mask=[0,1])
assert(test.all())
dates = num2date(times, units=units, calendar='standard')
- assert(str(dates)=="[cftime.DatetimeGregorian(1848, 1, 17, 6, 0, 0, 40) --]")
+ assert(str(dates)=="[cftime.DatetimeGregorian(1848, 1, 17, 6, 0, 0, 40, has_year_zero=False)\n --]")
# check that time range of 200,000 + years can be represented accurately
calendar='standard'
_MAX_INT64 = np.iinfo("int64").max
@@ -776,15 +808,20 @@ class cftimeTestCase(unittest.TestCase):
assert (date2num(dates, units="hours since 2010-02-01 00:00:00") == 24.)
# issue #187 - roundtrip near second boundary
dt1 = datetime(1810, 4, 24, 16, 15, 10)
- units = 'days since -4713-01-01 12:00'
- dt2 = num2date(date2num(dt1, units), units, calendar='proleptic_gregorian')
- dt2 = num2date(date2num(dt1, units, calendar='standard'), units)
- assert(dt1 == dt2)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ units = 'days since -4713-01-01 12:00'
+ dt2 = num2date(date2num(dt1, units), units, calendar='proleptic_gregorian')
+ dt2 = num2date(date2num(dt1, units, calendar='standard'), units)
+ assert(dt1 == dt2)
# issue #189 - leap years calculated incorrectly for negative years in proleptic_gregorian calendar
- dt1 = datetime(2020, 4, 24, 16, 15, 10)
+ dt1 = datetime(2020, 4, 24, 16, 15, 10) # python datetime
units = 'days since -4713-01-01 12:00'
cal = 'proleptic_gregorian'
- dt2 = num2date(date2num(dt1, units, cal), units, cal)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ dt2 = num2date(date2num(dt1, units, cal, has_year_zero=False), units,
+ cal, has_year_zero=False)
assert(dt1 == dt2)
# issue #198 - cftime.datetime creates calendar specific datetimes that
# support addition/subtraction of timedeltas.
@@ -796,19 +833,21 @@ class cftimeTestCase(unittest.TestCase):
assert(cftime.date2num(cftime.datetime(18000, 12, 1, 0, 0), 'days since 18000-1-1', '360_day') == 330.0)
# julian day not including year zero
d = cftime.datetime(2020, 12, 1, 12, calendar='julian')
- units = 'days since -4713-1-1 12:00'
- jd = cftime.date2num(d,units,calendar='julian')
- assert(jd == 2459198.0)
- # if calendar=None, use input date to determine calendar
- jd = cftime.date2num(d,units,calendar=None)
- assert(jd == 2459198.0)
- # if no calendar specified, use calendar associated with datetime
- # instance.
- jd = cftime.date2num(d,units)
- assert(jd == 2459198.0)
- # use 'standard' calendar
- jd = cftime.date2num(d,units,calendar='standard')
- assert(jd == 2459185.0)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ units = 'days since -4713-1-1 12:00'
+ jd = cftime.date2num(d,units,calendar='julian')
+ assert(jd == 2459198.0)
+ # if calendar=None, use input date to determine calendar
+ jd = cftime.date2num(d,units,calendar=None)
+ assert(jd == 2459198.0)
+ # if no calendar specified, use calendar associated with datetime
+ # instance.
+ jd = cftime.date2num(d,units)
+ assert(jd == 2459198.0)
+ # use 'standard' calendar
+ jd = cftime.date2num(d,units,calendar='standard')
+ assert(jd == 2459185.0)
# issue #211
# (masked array handling in date2num - AttributeError:
@@ -820,6 +859,47 @@ class cftimeTestCase(unittest.TestCase):
cftime.date2num(m, units="seconds since 2000-1-1")==[4.602096e+08]
)
+ # test astronomical year numbering
+ jdref=2400000
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ for calendar in ['julian','gregorian','proleptic_gregorian']:
+ has_year_zero=False
+ try:
+ # this should raise ValueError
+ d = cftime.datetime(0,1,1,0,has_year_zero=has_year_zero,calendar=calendar)
+ except ValueError:
+ d = cftime.datetime(-1,1,1,0,has_year_zero=has_year_zero,calendar=calendar)
+ pass
+ else:
+ raise AssertionError # fail if ValueError not raised
+ d2 = cftime.datetime(1,1,1,0,has_year_zero=has_year_zero,calendar=calendar)
+ assert((d2-d).days==366) # 1-1-1 is 366 days after -1-1-1 if no year zero.
+ has_year_zero=True
+ d = cftime.datetime(0,1,1,0,has_year_zero=has_year_zero,calendar=calendar)
+ d2 = cftime.datetime(1,1,1,0,has_year_zero=has_year_zero,calendar=calendar)
+ assert((d2-d).days==366) # 1-1-1 is 366 days after 0-1-1 if year zero allowed.
+ for has_year_zero in [True,False]:
+ if calendar == 'julian':
+ d = cftime.datetime(1858,11, 4,12,has_year_zero=has_year_zero,calendar=calendar)
+ else:
+ d = cftime.datetime(1858,11,16,12,has_year_zero=has_year_zero,calendar=calendar)
+ if has_year_zero:
+ if calendar == 'proleptic_gregorian':
+ d0 = cftime.datetime(-4713,11,24,12,has_year_zero=has_year_zero,calendar=calendar)
+ else:
+ d0 = cftime.datetime(-4712, 1, 1,12,has_year_zero=has_year_zero,calendar=calendar)
+ else:
+ if calendar == 'proleptic_gregorian':
+ d0 = cftime.datetime(-4714,11,24,12,has_year_zero=has_year_zero,calendar=calendar)
+ else:
+ d0 = cftime.datetime(-4713, 1, 1,12,has_year_zero=has_year_zero,calendar=calendar)
+ jd = d.toordinal()
+ assert((d-d0).days == jdref)
+ assert(jd == jdref)
+ assert(d.toordinal() == jdref)
+ d2 = cftime.datetime.fromordinal(jd,calendar=calendar,has_year_zero=has_year_zero)
+ assert(d2 == d)
class TestDate2index(unittest.TestCase):
@@ -933,11 +1013,12 @@ class TestDate2index(unittest.TestCase):
raise ValueError('This test should have failed.')
def test_select_dummy(self):
+ calendar='standard'
nutime = self.TestTime(datetime(1950, 1, 1), 366, 24,
- 'hours since 1400-01-01', 'standard')
+ 'hours since 1400-01-01', calendar)
- dates = [datetime(1950, 1, 2, 6), datetime(
- 1950, 1, 3), datetime(1950, 1, 3, 18)]
+ dates = [datetimex(1950, 1, 2, 6, calendar=calendar), datetimex(
+ 1950, 1, 3, calendar=calendar), datetimex(1950, 1, 3, 18, calendar=calendar)]
t = date2index(dates, nutime, select='before')
assert_equal(t, [1, 2, 2])
@@ -951,6 +1032,7 @@ class TestDate2index(unittest.TestCase):
def test_select_nc(self):
nutime = self.time_vars['time']
+ # these are python datetimes ('proleptic_gregorian' calendar).
dates = [datetime(1950, 1, 2, 6), datetime(
1950, 1, 3), datetime(1950, 1, 3, 18)]
@@ -1040,7 +1122,9 @@ class issue584TestCase(unittest.TestCase):
# Pick the date corresponding to the Julian day of 1.0 to test
# the transition from positive to negative Julian days.
- julian_day = converter.date2num(datetimex(-4712, 1, 2, 12))
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ julian_day = converter.date2num(datetimex(-4712, 1, 2, 12))
# should be a Tuesday
old_date = converter.num2date(julian_day)
@@ -1062,6 +1146,11 @@ class DateTime(unittest.TestCase):
self.date1_365_day = DatetimeNoLeap(-5000, 1, 2, 12)
self.date2_365_day = DatetimeNoLeap(-5000, 1, 3, 12)
self.date3_gregorian = DatetimeGregorian(1969, 7, 20, 12)
+ self.date3_gregorian_yearzero = DatetimeGregorian(1969, 7, 20, 12, has_year_zero=True)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ self.date4_proleptic_gregorian = cftime.datetime(-1,5,5,2,30,59,999999,calendar='proleptic_gregorian',has_year_zero=False)
+ self.date4_julian = self.date4_proleptic_gregorian.change_calendar('julian',True)
# last day of the Julian calendar in the mixed Julian/Gregorian calendar
self.date4_gregorian = DatetimeGregorian(1582, 10, 4)
@@ -1110,8 +1199,10 @@ class DateTime(unittest.TestCase):
DatetimeAllLeap(11, 2, 1))
# The Gregorian calendar has no year zero.
- self.assertEqual(DatetimeGregorian(-1, 12, 31) + self.delta,
- DatetimeGregorian(1, 1, 1, 1))
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ self.assertEqual(DatetimeGregorian(-1, 12, 31) + self.delta,
+ DatetimeGregorian(1, 1, 1, 1))
def invalid_add_1():
self.date1_365_day + 1
@@ -1156,9 +1247,11 @@ class DateTime(unittest.TestCase):
self.assertEqual(self.date6_proleptic_gregorian - self.delta,
DatetimeProlepticGregorian(1582, 10, 13, 23))
- # The Gregorian calendar has no year zero.
- self.assertEqual(DatetimeGregorian(1, 1, 1) - self.delta,
- DatetimeGregorian(-1, 12, 30, 23))
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ # The Gregorian calendar has no year zero.
+ self.assertEqual(DatetimeGregorian(1, 1, 1) - self.delta,
+ DatetimeGregorian(-1, 12, 30, 23))
# The 360_day calendar has year zero.
@@ -1188,11 +1281,11 @@ class DateTime(unittest.TestCase):
def invalid_sub_5():
self.date3_gregorian - self.date1_365_day
- for func in [invalid_sub_1, invalid_sub_2]:
- self.assertRaises(TypeError, func)
+ def invalid_sub_6():
+ self.date3_gregorian - self.date3_gregorian_yearzero
- for func in [invalid_sub_3, invalid_sub_4, invalid_sub_5]:
- self.assertRaises(ValueError, func)
+ for func in [invalid_sub_1, invalid_sub_2, invalid_sub_3, invalid_sub_4, invalid_sub_5, invalid_sub_6]:
+ self.assertRaises(TypeError, func)
def test_replace(self):
self.assertEqual(self.date1_365_day.replace(year=4000).year, 4000)
@@ -1224,7 +1317,9 @@ class DateTime(unittest.TestCase):
"1969-07-20 12:00:00")
def invalid_year():
- DatetimeGregorian(0, 1, 1) + self.delta
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ DatetimeGregorian(0, 1, 1) + self.delta
def invalid_month():
DatetimeGregorian(1, 13, 1) + self.delta
@@ -1250,6 +1345,9 @@ class DateTime(unittest.TestCase):
self.assertTrue(self.datetime_date1 > self.date3_gregorian)
# compare datetime and real_datetime
self.assertFalse(self.date3_gregorian > self.datetime_date1)
+ # compare different calendars (uses change_calendar method)
+ self.assertTrue(self.date3_gregorian_yearzero == self.date3_gregorian)
+ self.assertTrue(self.date4_proleptic_gregorian == self.date4_julian)
def not_comparable_1():
"compare two datetime instances with different calendars"
@@ -1269,9 +1367,9 @@ class DateTime(unittest.TestCase):
def not_comparable_5():
"compare non-datetime to a datetime instance"
- 0 < self.date_1_365_day
+ 0 < self.date1_365_day
- for func in [not_comparable_1, not_comparable_2, not_comparable_3, not_comparable_4]:
+ for func in [not_comparable_1, not_comparable_2, not_comparable_3, not_comparable_4, not_comparable_5]:
self.assertRaises(TypeError, func)
@pytest.mark.skipif(sys.version_info.major != 2,
@@ -1427,12 +1525,17 @@ def days_per_month_leap_year(date_type, month):
def test_zero_year(date_type):
- # Year 0 is valid in the 360,365 and 366 day calendars
- if date_type in [DatetimeNoLeap, DatetimeAllLeap, Datetime360Day]:
- date_type(0, 1, 1)
- else:
- with pytest.raises(ValueError):
+ # Year 0 is valid in the 360,365 and 366 day and
+ # Proleptic Gregorian calendars by default.
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ #if date_type in [DatetimeNoLeap, DatetimeAllLeap, Datetime360Day,
+ # DatetimeProlepticGregorian]:
+ if date_type in [DatetimeNoLeap, DatetimeAllLeap, Datetime360Day]:
date_type(0, 1, 1)
+ else:
+ with pytest.raises(ValueError):
+ date_type(0, 1, 1)
def test_invalid_month(date_type):
@@ -1553,8 +1656,10 @@ _EXPECTED_DATE_TYPES = {'noleap': DatetimeNoLeap,
)
def test_num2date_only_use_cftime_datetimes_negative_years(
calendar, expected_date_type):
- result = num2date(-1000., units='days since 0001-01-01', calendar=calendar,
- only_use_cftime_datetimes=True)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore",category=cftime.CFWarning)
+ result = num2date(-1000., units='days since 0001-01-01', calendar=calendar,
+ only_use_cftime_datetimes=True)
assert isinstance(result, datetimex)
assert (result.calendar == adjust_calendar(calendar))
@@ -1584,11 +1689,18 @@ def test_num2date_only_use_cftime_datetimes_post_gregorian(
def test_repr():
- expected = "cftime.datetime(2000, 1, 1, 0, 0, 0, 0, calendar='gregorian')"
+ expected = "cftime.datetime(2000, 1, 1, 0, 0, 0, 0, calendar='gregorian', has_year_zero=False)"
assert repr(datetimex(2000, 1, 1, calendar='standard')) == expected
- expected = "cftime.datetime(2000, 1, 1, 0, 0, 0, 0, calendar='')"
+ expected = "cftime.datetime(2000, 1, 1, 0, 0, 0, 0, calendar='', has_year_zero=False)"
assert repr(datetimex(2000, 1, 1, calendar=None)) == expected
+def test_string_format():
+ dt = cftime.datetime(2000, 1, 1)
+ # check the default formatting is the same as strftime
+ assert dt.strftime() == '{0}'.format(dt)
+ # check a given format string acts like strftime
+ assert dt.strftime('%H%m%d') == '{0:%H%m%d}'.format(dt)
+ assert 'the year is 2000' == 'the year is {dt:%Y}'.format(dt=dt)
def test_dayofyr_after_replace(date_type):
date = date_type(1, 1, 1)
View it on GitLab: https://salsa.debian.org/debian-gis-team/cftime/-/commit/ee2bdb35fc699f4e3c2b48318d29216e8219470d
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/cftime/-/commit/ee2bdb35fc699f4e3c2b48318d29216e8219470d
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20210521/80a47b65/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list