@@ -30,7 +30,7 @@ _units = microsec_units+millisec_units+sec_units+min_units+hr_units+day_units
3030# '366_day'=='all_leap','365_day'=='noleap')
3131# see http://cfconventions.org/cf-conventions/cf-conventions#calendar
3232# for definitions.
33- _calendars = [' standard' , ' gregorian' , ' proleptic_gregorian' ,
33+ _calendars = [' standard' , ' gregorian' , ' proleptic_gregorian' , ' tai ' ,
3434 ' noleap' , ' julian' , ' all_leap' , ' 365_day' , ' 366_day' , ' 360_day' ]
3535_idealized_calendars= [' all_leap' ,' noleap' ,' 366_day' ,' 365_day' ,' 360_day' ]
3636# Following are number of days per month
@@ -83,7 +83,7 @@ def _datesplit(timestr):
8383
8484 return units.lower(), remainder
8585
86- def _dateparse (timestr ,calendar ,has_year_zero = None ):
86+ def _dateparse (timestr , calendar , has_year_zero = None ):
8787 """ parse a string of the form time-units since yyyy-mm-dd hh:mm:ss,
8888 return a datetime instance"""
8989 # same as version in cftime, but returns a timezone naive
@@ -109,6 +109,9 @@ def _dateparse(timestr,calendar,has_year_zero=None):
109109 # parse the date string.
110110 year, month, day, hour, minute, second, microsecond, utc_offset = \
111111 _parse_date( isostring.strip() )
112+ if calendar == ' tai' :
113+ if year < 1958 or utc_offset:
114+ raise ValueError (' TAI calendar must have a reference date of 1958-01-01T00:00:00 or later (with no utc offset)' )
112115 if year == 0 and not has_year_zero and calendar in [' julian' , ' standard' , ' gregorian' , ' proleptic_gregorian' ]:
113116 msg= ' zero not allowed as a reference year when has_year_zero=False'
114117 raise ValueError (msg)
@@ -157,7 +160,7 @@ def date2num(dates, units, calendar=None, has_year_zero=None, longdouble=False):
157160 **calendar**: describes the calendar to be used in the time calculations.
158161 All the values currently defined in the
159162 `CF metadata convention <http://cfconventions.org/cf-conventions/cf-conventions#calendar>`__ are supported.
160- Valid calendars **'standard', 'gregorian', 'proleptic_gregorian'
163+ Valid calendars **'standard', 'gregorian', 'proleptic_gregorian', 'tai',
161164 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'**.
162165 Default is `None` which means the calendar associated with the first
163166 input datetime instance will be used.
@@ -373,6 +376,7 @@ UNIT_CONVERSION_FACTORS = {
373376
374377DATE_TYPES = {
375378 " proleptic_gregorian" : DatetimeProlepticGregorian,
379+ " tai" : DatetimeTAI,
376380 " standard" : DatetimeGregorian,
377381 " noleap" : DatetimeNoLeap,
378382 " 365_day" : DatetimeNoLeap,
@@ -537,7 +541,7 @@ def num2date(
537541 **calendar**: describes the calendar used in the time calculations.
538542 All the values currently defined in the
539543 `CF metadata convention <http://cfconventions.org/cf-conventions/cf-conventions#calendar>`__ are supported.
540- Valid calendars **'standard', 'gregorian', 'proleptic_gregorian'
544+ Valid calendars **'standard', 'gregorian', 'proleptic_gregorian', 'tai',
541545 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'**.
542546 Default is **'standard'**, which is a mixed Julian/Gregorian calendar.
543547
@@ -652,7 +656,7 @@ def date2index(dates, nctime, calendar=None, select='exact', has_year_zero=None)
652656 **calendar**: describes the calendar to be used in the time calculations.
653657 All the values currently defined in the
654658 `CF metadata convention <http://cfconventions.org/cf-conventions/cf-conventions#calendar>`__ are supported.
655- Valid calendars **'standard', 'gregorian', 'proleptic_gregorian'
659+ Valid calendars **'standard', 'gregorian', 'proleptic_gregorian', 'tai',
656660 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'**.
657661 Default is `None` which means the calendar associated with the first
658662 input datetime instance will be used.
@@ -864,7 +868,7 @@ def _date2index(dates, nctime, calendar=None, select='exact', has_year_zero=None
864868 order.
865869
866870 **calendar**: Describes the calendar used in the time calculation.
867- Valid calendars 'standard', 'gregorian', 'proleptic_gregorian'
871+ Valid calendars 'standard', 'gregorian', 'proleptic_gregorian', 'tai',
868872 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'.
869873 Default is 'standard', which is a mixed Julian/Gregorian calendar
870874 If `calendar` is None, its value is given by `nctime.calendar` or
@@ -911,7 +915,7 @@ def time2index(times, nctime, calendar=None, select='exact'):
911915 order.
912916
913917 **calendar**: Describes the calendar used in the time calculation.
914- Valid calendars 'standard', 'gregorian', 'proleptic_gregorian'
918+ Valid calendars 'standard', 'gregorian', 'proleptic_gregorian', 'tai',
915919 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'.
916920 Default is `standard`, which is a mixed Julian/Gregorian calendar
917921 If `calendar` is None, its value is given by `nctime.calendar` or
@@ -1072,7 +1076,7 @@ for cftime.datetime instances using
10721076
10731077All the calendars currently defined in the
10741078`CF metadata convention <http://cfconventions.org/cf-conventions/cf-conventions#calendar>`__ are supported.
1075- Valid calendars are 'standard', 'gregorian', 'proleptic_gregorian'
1079+ Valid calendars are 'standard', 'gregorian', 'proleptic_gregorian', 'tai',
10761080'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'.
10771081Default is 'standard', which is a mixed Julian/Gregorian calendar.
10781082'standard' and 'gregorian' are synonyms, as are 'all_leap'/'366_day'
@@ -1152,6 +1156,12 @@ The default format of the string produced by strftime is controlled by self.form
11521156 # and has a year zero, so for now this is the default in cftime.
11531157 if calendar in [' julian' ,' gregorian' ,' standard' ] and year <= 0 :
11541158 warnings.warn(cfwarnmsg,category = CFWarning)
1159+ # raise exception if date before 1958-01-01 requested for tai calendar.
1160+ if calendar == ' tai' :
1161+ if year < 1958 :
1162+ raise ValueError (' dates before 1958-01-01 not allowed in TAI calendar' )
1163+ if has_year_zero:
1164+ raise ValueError (' year zero not allowed in TAI calendar' )
11551165 # raise exception if year zero requested but has_year_zero set
11561166 # to False (issue #248).
11571167 if year == 0 and has_year_zero== False :
@@ -1185,7 +1195,7 @@ The default format of the string produced by strftime is controlled by self.form
11851195 self .calendar = calendar
11861196 self .datetime_compatible = False
11871197 assert_valid_date(self , is_leap_julian, False , has_year_zero = has_year_zero)
1188- elif calendar == ' proleptic_gregorian' :
1198+ elif calendar == ' proleptic_gregorian' or calendar == ' tai ' :
11891199 self .calendar = calendar
11901200 self .datetime_compatible = True
11911201 assert_valid_date(self , is_leap_proleptic_gregorian, False , has_year_zero = has_year_zero)
@@ -1275,7 +1285,7 @@ The default format of the string produced by strftime is controlled by self.form
12751285 if getattr (pydatetime, ' tzinfo' ,None ) is not None :
12761286 pydatetime = pydatetime.replace(tzinfo = None ) - pydatetime.utcoffset()
12771287 compatible_date = \
1278- calendar == ' proleptic_gregorian' or \
1288+ calendar == ' proleptic_gregorian' or calendar == ' tai ' or \
12791289 (calendar in [' gregorian' ,' standard' ] and (pydatetime.year > 1582 or \
12801290 (pydatetime.year == 1582 and pydatetime.month > 10 ) or \
12811291 (pydatetime.year == 1582 and pydatetime.month == 10 and pydatetime.day > 15 )))
@@ -1480,7 +1490,7 @@ The default format of the string produced by strftime is controlled by self.form
14801490 units = ' days since -4712-1-1-12'
14811491 else :
14821492 units = ' days since -4713-1-1-12'
1483- elif calendar == ' proleptic_gregorian' :
1493+ elif calendar == ' proleptic_gregorian' or calendar == ' tai ' :
14841494 if has_year_zero:
14851495 units = ' days since -4713-11-24-12'
14861496 else :
@@ -1566,6 +1576,9 @@ The default format of the string produced by strftime is controlled by self.form
15661576 elif calendar == ' proleptic_gregorian' :
15671577 # return dt.__class__(*add_timedelta(dt, delta, is_leap_proleptic_gregorian, False, has_year_zero),calendar=calendar,has_year_zero=has_year_zero)
15681578 return DatetimeProlepticGregorian(* add_timedelta(dt, delta, is_leap_proleptic_gregorian, False , has_year_zero),has_year_zero = has_year_zero)
1579+ elif calendar == ' tai' :
1580+ # return dt.__class__(*add_timedelta(dt, delta, is_leap_proleptic_gregorian, False, has_year_zero),calendar=calendar,has_year_zero=has_year_zero)
1581+ return DatetimeTAI(* add_timedelta(dt, delta, is_leap_proleptic_gregorian, False , has_year_zero),has_year_zero = has_year_zero)
15691582 else :
15701583 return NotImplemented
15711584
@@ -1626,6 +1639,10 @@ datetime object."""
16261639 # return self.__class__(*add_timedelta(self, -other,
16271640 # is_leap_proleptic_gregorian, False, has_year_zero),calendar=self.calendar,has_year_zero=self.has_year_zero)
16281641 return DatetimeProlepticGregorian(* add_timedelta(self , - other, is_leap_proleptic_gregorian, False , has_year_zero),has_year_zero = self .has_year_zero)
1642+ elif self .calendar == ' tai' :
1643+ # return self.__class__(*add_timedelta(self, -other,
1644+ # is_leap_proleptic_gregorian, False, has_year_zero),calendar=self.calendar,has_year_zero=self.has_year_zero)
1645+ return DatetimeTAI(* add_timedelta(self , - other, is_leap_proleptic_gregorian, False , has_year_zero),has_year_zero = self .has_year_zero)
16291646 else :
16301647 return NotImplemented
16311648 else :
@@ -1944,7 +1961,7 @@ cdef _is_leap(int year, calendar, has_year_zero=None):
19441961 tyear = year + 1
19451962 else :
19461963 tyear = year
1947- if calendar == ' proleptic_gregorian' or (calendar == ' standard' and year > 1581 ):
1964+ if calendar == ' proleptic_gregorian' or calendar == ' tai ' or (calendar == ' standard' and year > 1581 ):
19481965 if tyear % 4 : # not divisible by 4
19491966 leap = False
19501967 elif tyear % 100 : # not divisible by 100
@@ -2040,7 +2057,7 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
20402057 elif calendar == ' 366_day' :
20412058 return year* 366 + _cumdayspermonth_leap[month- 1 ] + day - 1
20422059
2043- # handle standard, julian, proleptic_gregorian calendars.
2060+ # handle standard, julian, tai, proleptic_gregorian calendars.
20442061 if year == 0 and not has_year_zero:
20452062 raise ValueError (' year zero does not exist in the %s calendar' % \
20462063 calendar)
@@ -2066,7 +2083,7 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
20662083 jday_greg -= 31739
20672084 if calendar == ' julian' :
20682085 return jday_jul
2069- elif calendar == ' proleptic_gregorian' :
2086+ elif calendar == ' proleptic_gregorian' or calendar == ' tai ' :
20702087 return jday_greg
20712088 elif calendar in [' standard' ,' gregorian' ]:
20722089 # check for invalid days in mixed calendar (there are 10 missing)
@@ -2080,7 +2097,7 @@ cdef _IntJulianDayFromDate(int year,int month,int day,calendar,skip_transition=F
20802097 else :
20812098 return jday_greg
20822099
2083- # legacy calendar specific sub-classes (will be removed in a future release).
2100+ # legacy calendar specific sub-classes (may be removed in a future release).
20842101
20852102@ cython.embedsignature (True )
20862103cdef class DatetimeNoLeap(datetime):
@@ -2141,3 +2158,13 @@ but allows for dates that don't exist in the proleptic gregorian calendar.
21412158 def __init__ (self , *args , **kwargs ):
21422159 kwargs[' calendar' ]= ' proleptic_gregorian'
21432160 super ().__init__( * args, ** kwargs)
2161+
2162+ @ cython.embedsignature (True )
2163+ cdef class DatetimeTAI(datetime):
2164+ """
2165+ Phony datetime object which mimics the python datetime object,
2166+ but allows for dates that don't exist in the proleptic gregorian calendar.
2167+ """
2168+ def __init__ (self , *args , **kwargs ):
2169+ kwargs[' calendar' ]= ' tai'
2170+ super ().__init__( * args, ** kwargs)
0 commit comments