[Git][debian-gis-team/cftime][upstream] New upstream version 1.2.1+ds

Bas Couwenberg gitlab at salsa.debian.org
Mon Jul 20 05:08:22 BST 2020



Bas Couwenberg pushed to branch upstream at Debian GIS Project / cftime


Commits:
e4ec2aaa by Bas Couwenberg at 2020-07-20T05:44:30+02:00
New upstream version 1.2.1+ds
- - - - -


4 changed files:

- Changelog
- README.md
- cftime/_cftime.pyx
- test/test_cftime.py


Changes:

=====================================
Changelog
=====================================
@@ -1,3 +1,13 @@
+version 1.2.1 (not yet released)
+=================================
+ * num2date uses 'proleptic_gregorian' scheme when basedate is post-Gregorian but date is pre-Gregorian
+   (issue #182).
+ * fix 1.2.0 regression (date2num no longer works with numpy scalar array inputs, issue #185).
+ * Fix for issue #187 (have date2num round to the nearest second when within 1
+   microsecond).
+ * Fix for issue #189 (leap years calculated incorrectly for negative years in
+   proleptic_gregorian calendar).
+
 version 1.2.0 (release tag v1.2.0rel)
 =====================================
  * Return the default values of dayofwk and dayofyr when calendar
@@ -24,7 +34,7 @@ version 1.1.2 (release tag v1.1.2rel)
 =====================================
  * change dayofwk and dayofyr attributes into properties (issue #158)
  * fix for issue #165 (python datetime should be returned when
-   only_use_cftime_datimes=False).
+   only_use_cftime_datetimes=False).
 
 version 1.1.1.2 (release tag v1.1.1.2rel)
 =========================================


=====================================
README.md
=====================================
@@ -12,12 +12,14 @@ Time-handling functionality from netcdf4-python
 ## News
 For details on the latest updates, see the [Changelog](https://github.com/Unidata/cftime/blob/master/Changelog).
 
-7/06/2020:  version 1.2.0 released. New microsecond accurate algorithm for date2num/num2date contributed by @spencerkclark. Bugs fixed in masked array handling.
+07/20/2020: Version 1.2.1 released.  Fixes a couple of regressions introduced in 1.2.0. See Changelog for details.
+
+7/06/2020:  version 1.2.0 released. New microsecond accurate algorithm for date2num/num2date contributed by [spencerkclark](https://github.com/spencerkclark). Bugs fixed in masked array handling.
 
 5/12/2020:  version 1.1.3 released.  Add isoformat method for compatibility with python datetime (issue #152).
  Make 'standard' default calendar for cftime.datetime so that dayofwk,dayofyr methods don't fail (issue #169).
 
-4/20/2020:  version 1.1.2 released.  Code optimization, fix logic so `only_use_cftime_datimes=False` works as 
+4/20/2020:  version 1.1.2 released.  Code optimization, fix logic so `only_use_cftime_datetimes=False` works as 
  expected (issues [#158](https://github.com/Unidata/cftime/issues/158) and [#165](https://github.com/Unidata/cftime/issues/165)).
 
 3/16/2020:  version 1.1.1 released.  Fix bug in microsecond formatting, ensure identical num2date results if input is an array of times, or a single scalar ([issue #143](https://github.com/Unidata/cftime/issues/143)).


=====================================
cftime/_cftime.pyx
=====================================
@@ -53,7 +53,7 @@ cdef int32_t* days_per_month_array = [
 _rop_lookup = {Py_LT: '__gt__', Py_LE: '__ge__', Py_EQ: '__eq__',
                Py_GT: '__lt__', Py_GE: '__le__', Py_NE: '__ne__'}
 
-__version__ = '1.2.0'
+__version__ = '1.2.1'
 
 # Adapted from http://delete.me.uk/2005/03/iso8601.html
 # Note: This regex ensures that all ISO8601 timezone formats are accepted - but, due to legacy support for other timestrings, not all incorrect formats can be rejected.
@@ -166,7 +166,7 @@ def _dateparse(timestr,calendar):
 
 def _can_use_python_datetime(date,calendar):
     return ((calendar == 'proleptic_gregorian' and date.year >= MINYEAR and date.year <= MAXYEAR) or \
-           (calendar in ['gregorian','standard'] and date > gregorian))
+           (calendar in ['gregorian','standard'] and date > gregorian and date.year <= MAXYEAR))
 
 @cython.embedsignature(True)
 def date2num(dates,units,calendar='standard'):
@@ -209,7 +209,7 @@ def date2num(dates,units,calendar='standard'):
     if unit in ["months", "month"] and calendar != "360_day":
         raise ValueError("Units of months only valid for 360_day calendar.")
     factor = UNIT_CONVERSION_FACTORS[unit]
-    use_python_datetime = _can_use_python_datetime(basedate,calendar)
+    can_use_python_basedatetime = _can_use_python_datetime(basedate,calendar)
 
     isscalar = False
     try:
@@ -220,28 +220,33 @@ def date2num(dates,units,calendar='standard'):
     if np.ma.isMA(dates) and np.ma.is_masked(dates):
         mask = dates.mask
         ismasked = True
-    if isscalar:
-        dates = np.array([dates])
+    dates = np.asanyarray(dates)
+    shape = dates.shape
+    # are all dates python datetime instances?
+    all_python_datetimes = True
+    for date in dates.flat:
+        if not isinstance(date,datetime_python):
+           all_python_datetimes = False
+           break
+    if can_use_python_basedatetime and all_python_datetimes:
+        use_python_datetime = True
+        if not isinstance(basedate, datetime_python):
+            basedate = real_datetime(basedate.year, basedate.month, basedate.day, 
+                       basedate.hour, basedate.minute, basedate.second,
+                       basedate.microsecond)
     else:
-        dates = np.array(dates)
-        shape = dates.shape
+        use_python_datetime = False
+        # convert basedate to specified calendar
+        if not isinstance(basedate, DATE_TYPES[calendar]):
+            basedate =  to_calendar_specific_datetime(basedate, calendar, False)
     times = []; n = 0
     for date in dates.flat:
         # use python datetime if possible.
-        if use_python_datetime and date.year >= MINYEAR and date.year <= MAXYEAR:
-            if not isinstance(basedate, datetime_python):
-                basedate = real_datetime(basedate.year, basedate.month, basedate.day, 
-                           basedate.hour, basedate.minute, basedate.second,
-                           basedate.microsecond)
-            if not isinstance(date, datetime_python):
-                date = real_datetime(date.year, date.month, date.day, date.hour,
-                                     date.minute, date.second, date.microsecond)
-            # adjust for time zone offset
+        if use_python_datetime:
+            # remove time zone offset
             if getattr(date, 'tzinfo',None) is not None:
                 date = date.replace(tzinfo=None) - date.utcoffset()
-        else: # convert basedate and date to same calendar specific cftime.datetime instance
-            if not isinstance(basedate, DATE_TYPES[calendar]):
-                basedate =  to_calendar_specific_datetime(basedate, calendar, False)
+        else: # convert date to same calendar specific cftime.datetime instance
             if not isinstance(date, DATE_TYPES[calendar]):
                 date = to_calendar_specific_datetime(date, calendar, False)
         if ismasked and mask.flat[n]:
@@ -348,7 +353,7 @@ _MAX_INT64 = np.iinfo("int64").max
 _MIN_INT64 = np.iinfo("int64").min
 
 
-def cast_to_int(num):
+def cast_to_int(num, units=None):
     if num.dtype.kind in "iu":
         return num
     else:
@@ -356,8 +361,20 @@ def cast_to_int(num):
             raise OverflowError('time values outside range of 64 bit signed integers')
         if isinstance(num, np.ma.core.MaskedArray):
             int_num = np.ma.masked_array(np.rint(num), dtype=np.int64)
+            # use ceil instead of rint if 1 microsec less than a second
+            # or floor if 1 microsec greater than a second (issue #187)
+            if units not in microsec_units and units not in millisec_units:
+                int_num = np.ma.where(int_num%1000000 == 1, \
+                          np.ma.masked_array(np.floor(num),dtype=np.int64), int_num)
+                int_num = np.ma.where(int_num%1000000 == 999999, \
+                          np.ma.masked_array(np.ceil(num),dtype=np.int64), int_num)
         else:
             int_num = np.array(np.rint(num), dtype=np.int64)
+            if units not in microsec_units and units not in millisec_units:
+                int_num = np.where(int_num%1000000 == 1, \
+                          np.array(np.floor(num),dtype=np.int64), int_num)
+                int_num = np.where(int_num%1000000 == 999999, \
+                          np.array(np.ceil(num),dtype=np.int64), int_num)
         return int_num
 
 
@@ -478,7 +495,7 @@ def num2date(
     times = np.asanyarray(times)  # Allow list as input
     times = upcast_times(times)
     scaled_times = scale_times(times, factor)
-    scaled_times = cast_to_int(scaled_times)
+    scaled_times = cast_to_int(scaled_times,units=unit)
 
     # Through np.timedelta64, convert integers scaled to have units of
     # microseconds to datetime.timedelta objects, the timedelta type compatible
@@ -1489,9 +1506,9 @@ cdef _is_leap(int year, calendar):
     if calendar == 'proleptic_gregorian' or (calendar == 'standard' and year > 1581):
         if tyear % 4: # not divisible by 4
             leap = False
-        elif year % 100: # not divisible by 100
+        elif tyear % 100: # not divisible by 100
             leap = True
-        elif year % 400: # not divisible by 400
+        elif tyear % 400: # not divisible by 400
             leap = False
         else:
             leap = True


=====================================
test/test_cftime.py
=====================================
@@ -805,6 +805,21 @@ class cftimeTestCase(unittest.TestCase):
             time2 = date2num(date,units,calendar=calendar)
             date2 = num2date(time2,units,calendar=calendar)
             assert(date2 == refdate)
+# issue #185: date2num should work the numpy scalar array of dates (1.2.0 regression)
+        dates = np.array(datetime(2010, 2, 2, 0, 0))
+        assert (date2num(dates, units="hours since 2010-02-01 00:00:00") == 24.)
+# issue #187 - roundtrip near second boundary
+        dt1 = datetime(1810, 4, 24, 16, 15, 10)
+        units = 'days since -4713-01-01 12:00'
+        dt2 = num2date(date2num(dt1, units), units)
+        assert(dt1 == dt2)
+# issue #189 - leap years calculated incorrectly for negative years in proleptic_gregorian calendar
+        dt1 = datetime(2020, 4, 24, 16, 15, 10)
+        units = 'days since -4713-01-01 12:00'
+        cal = 'proleptic_gregorian'
+        dt2 = num2date(date2num(dt1, units, cal), units, cal)
+        assert(dt1 == dt2)
+
 
 class TestDate2index(unittest.TestCase):
 



View it on GitLab: https://salsa.debian.org/debian-gis-team/cftime/-/commit/e4ec2aaa39cfd547546b1c5815e1f02466e437f2

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/cftime/-/commit/e4ec2aaa39cfd547546b1c5815e1f02466e437f2
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/20200720/c548a9a2/attachment-0001.html>


More information about the Pkg-grass-devel mailing list