[Git][debian-gis-team/cftime][upstream] New upstream version 1.0.3

Bas Couwenberg gitlab at salsa.debian.org
Sun Dec 2 10:34:46 GMT 2018


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


Commits:
85241047 by Bas Couwenberg at 2018-12-02T09:47:49Z
New upstream version 1.0.3
- - - - -


11 changed files:

- appveyor.yml → .appveyor.yml
- + .coveragerc
- .gitignore
- .travis.yml
- README.md
- cftime/_cftime.pyx
- requirements-dev.txt
- + setup.cfg
- setup.py
- + setup.txt
- test/test_cftime.py


Changes:

=====================================
appveyor.yml → .appveyor.yml
=====================================
@@ -41,7 +41,7 @@ install:
 build: off
 
 test_script:
-    - conda.exe create --name TEST python=%PY% numpy=%NPY% cython pip pytest
+    - conda.exe create --name TEST python=%PY% numpy=%NPY% cython pip pytest pytest-cov
     - conda activate TEST
     - python -m pip install . --no-deps --ignore-installed --no-cache-dir -vvv
     - py.test -vv test


=====================================
.coveragerc
=====================================
@@ -0,0 +1,21 @@
+#
+# .coveragerc to control coverage.py
+#
+
+[run]
+branch = True
+plugins = Cython.Coverage
+include =
+    cftime/*
+omit = 
+    setup.py
+    docs/*
+    ci/*
+    test/*
+    .eggs
+
+[report]
+exclude_lines =
+    pragma: no cover
+    def __repr__
+    if __name__ == .__main__.:


=====================================
.gitignore
=====================================
@@ -1,6 +1,8 @@
 *.so
+*.py[co]
 *.c
 build/
+*.egg?
 *.egg-info
 _build/
 __pycache__


=====================================
.travis.yml
=====================================
@@ -1,40 +1,51 @@
 # Based on http://conda.pydata.org/docs/travis.html
-language: python
+language: minimal
+
 sudo: false # use container based build
+
 notifications:
   email: false
 
-python:
-  - 2.7
-  - 3.5
-  - 3.6
+env:
+  - PYTHON_VERSION=2.7
+  - PYTHON_VERSION=3.6
+  - PYTHON_VERSION=3.7
 
 matrix:
   include:
-    - python: 3.6
-      env: BUILD_DOCS="true"
+    - env: PYTHON_VERSION=3.7 BUILD_DOCS="true"
 
 before_install:
-  - wget http://bit.ly/miniconda -O miniconda.sh
-  - bash miniconda.sh -b -p $HOME/miniconda
-  - export PATH="$HOME/miniconda/bin:$PATH"
-  - conda update conda --yes --all
-  - conda config --add channels conda-forge --force
-  - conda create --yes -n TEST python=$TRAVIS_PYTHON_VERSION --file requirements.txt --file requirements-dev.txt
-  - source activate TEST
-  - if [[ $BUILD_DOCS == "true" ]]; then
+  # Build the conda testing environment.
+  - >
+    echo "Installing miniconda and cftime dependencies...";
+    wget http://bit.ly/miniconda -O miniconda.sh;
+    bash miniconda.sh -b -p ${HOME}/miniconda;
+    export PATH="${HOME}/miniconda/bin:${PATH}";
+    conda update conda --yes --all;
+    conda config --add channels conda-forge --force;
+    export ENV_NAME="test-environment";
+    conda create --yes -n ${ENV_NAME} python=${PYTHON_VERSION} --file requirements.txt --file requirements-dev.txt;
+    source activate ${ENV_NAME};
+    if [[ "${BUILD_DOCS}" == "true" ]]; then
       conda install -qy sphinx;
     fi;
 
-# Test source distribution.
+  # Log the conda environment and package details.
+  - conda list -n ${ENV_NAME}
+  - conda list -n ${ENV_NAME} --explicit
+  - conda info -a
+
 install:
-  - python setup.py sdist && version=$(python setup.py --version) && pushd dist  && pip install cftime-${version}.tar.gz && popd
+  # Install cftime.
+  - echo "Installing cftime..."
+  - CYTHON_COVERAGE=1 pip install -e .
 
 script:
-  - if [[ $BUILD_DOCS == "true" ]]; then
+  - if [[ "${BUILD_DOCS}" == "true" ]]; then
       pushd docs && make html linkcheck O=-W && popd;
     else
-      py.test -vv;
+      pytest -v;
     fi;
 
 after_success:
@@ -42,7 +53,7 @@ after_success:
 
 before_deploy:
   # Remove unused, unminified javascript from sphinx
-  - if [[ $BUILD_DOCS == "true" ]]; then
+  - if [[ "${BUILD_DOCS}" == "true" ]]; then
       rm -f docs/build/html/_static/jquery-*.js;
       rm -f docs/build/html/_static/underscore-*.js;
       rm -f docs/build/html/.buildinfo;
@@ -54,5 +65,4 @@ deploy:
     skip_cleanup: true
     on:
       branch: master
-      python: 3.6
-      condition: '$BUILD_DOCS == "true"'
+      condition: '${PYTHON_VERSION} == 3.7 AND ${BUILD_DOCS} == "true"'


=====================================
README.md
=====================================
@@ -4,8 +4,11 @@ Time-handling functionality from netcdf4-python
 [![Linux Build Status](https://travis-ci.org/Unidata/cftime.svg?branch=master)](https://travis-ci.org/Unidata/cftime)
 [![Windows Build Status](https://ci.appveyor.com/api/projects/status/fl9taa9je4e6wi7n/branch/master?svg=true)](https://ci.appveyor.com/project/jswhit/cftime/branch/master)
 [![PyPI package](https://badge.fury.io/py/cftime.svg)](http://python.org/pypi/cftime)
+[![Coverage Status](https://coveralls.io/repos/github/Unidata/cftime/badge.svg?branch=master)](https://coveralls.io/github/Unidata/cftime?branch=master)
 
 ## News
+12/01/2018:  version 1.0.3 released. Test coverage with coveralls.io, improved round-tripping accuracy for non-real world calendars (like `360_day`).
+
 10/27/2018:  version 1.0.2 released. Improved accuracy (from approximately 1000 microseconds to 10 microseconds on x86
 platforms). Refactored calendar calculations now allow for negative reference years. num2date function now more than an
 order of magnitude faster. `months since` units now allowed, but only for `360_day` calendar.


=====================================
cftime/_cftime.pyx
=====================================
@@ -2,7 +2,8 @@
 Performs conversions of netCDF time coordinate data to/from datetime objects.
 """
 
-from cpython.object cimport PyObject_RichCompare
+from cpython.object cimport (PyObject_RichCompare, Py_LT, Py_LE, Py_EQ,
+                             Py_NE, Py_GT, Py_GE)
 import cython
 import numpy as np
 import re
@@ -37,8 +38,11 @@ cdef int[12] _dpm_360  = [30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30]
 # Same as above, but SUM of previous months (no leap years).
 cdef int[13] _spm_365day = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]
 cdef int[13] _spm_366day = [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.0.2.1'
+__version__ = '1.0.3'
 
 # 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.
@@ -147,7 +151,7 @@ def date2num(dates,units,calendar='standard'):
     Default is `'standard'`, which is a mixed Julian/Gregorian calendar.
 
     returns a numeric time value, or an array of numeric time values
-    with approximately 10 microsecond accuracy.
+    with approximately 100 microsecond accuracy.
         """
         calendar = calendar.lower()
         basedate = _dateparse(units)
@@ -234,7 +238,7 @@ def num2date(times,units,calendar='standard',only_use_cftime_datetimes=False):
     subclass cftime.datetime are returned for all calendars.
 
     returns a datetime instance, or an array of datetime instances with
-    approximately 10 microsecond accuracy.
+    approximately 100 microsecond accuracy.
 
     ***Note***: The datetime instances returned are 'real' python datetime
     objects if `calendar='proleptic_gregorian'`, or
@@ -372,7 +376,7 @@ def JulianDayFromDate(date, calendar='standard'):
     """JulianDayFromDate(date, calendar='standard')
 
     creates a Julian Day from a 'datetime-like' object.  Returns the fractional
-    Julian Day (approximately 10 microsecond accuracy).
+    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.
@@ -418,14 +422,6 @@ def JulianDayFromDate(date, calendar='standard'):
     fracday = hour / 24.0 + minute / 1440.0 + (second + microsecond/1.e6) / 86400.0
     jd = jd - 0.5 + fracday
 
-    # Add a small offset (proportional to Julian date) for correct re-conversion.
-    # This is about 45 microseconds in 2000 for Julian date starting -4712.
-    # (pull request #433).
-    if calendar not in ['all_leap','no_leap','360_day','365_day','366_day']:
-        eps = np.array(np.finfo(np.float64).eps,np.longdouble)
-        eps = np.maximum(eps*jd, eps)
-        jd += eps
-
     if isscalar:
         return jd[0]
     else:
@@ -436,7 +432,7 @@ def DateFromJulianDay(JD, calendar='standard', only_use_cftime_datetimes=False,
     """
 
     returns a 'datetime-like' object given Julian Day. Julian Day is a
-    fractional day with approximately 10 microsecond accuracy.
+    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.
@@ -453,48 +449,64 @@ def DateFromJulianDay(JD, calendar='standard', only_use_cftime_datetimes=False,
     objects are used, which are actually instances of cftime.datetime.
     """
 
-    julian = np.array(JD, dtype=np.longdouble)
-
-    # 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 = np.atleast_1d(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)[0]
-
-    # Subtract the offset from JulianDayFromDate from the microseconds (pull
-    # request #433).
-    hour = np.clip((F * 24.).astype(np.int64), 0, 23)
-    F   -= hour / 24.
-    minute = np.clip((F * 1440.).astype(np.int64), 0, 59)
-    # this is an overestimation due to added offset in JulianDayFromDate
-    second = np.clip((F - minute / 1440.) * 86400., 0, None)
-    microsecond = (second % 1)*1.e6
-    # remove the offset from the microsecond calculation.
-    if calendar not in ['all_leap','no_leap','360_day','365_day','366_day']:
-        eps = np.array(np.finfo(np.float64).eps,np.longdouble)
-        eps = np.maximum(eps*julian, eps)
-        microsecond = np.clip(microsecond - eps*86400.*1e6, 0, 999999)
-
-    # convert hour, minute, second to int32
-    hour = hour.astype(np.int32)
-    minute = minute.astype(np.int32)
-    second = second.astype(np.int32)
-    microsecond = microsecond.astype(np.int32)
+    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.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)[0]
+        else:
+            ind_before = None
+
+        # 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.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,month,day,hour,minute,second,microsecond,dayofyr,dayofwk,ind_before =\
+        getdateinfo(julian)
+        microsecond[indxms] = 0
 
     # check if input was scalar and change return accordingly
     isscalar = False
@@ -1272,16 +1284,29 @@ Gregorial calendar.
                 raise TypeError("cannot compare {0!r} and {1!r} (different calendars)".format(self, other))
             return PyObject_RichCompare(dt.to_tuple(), to_tuple(other), op)
         else:
-            # With Python3 we can use "return NotImplemented". If the other
-            # object does not have rich comparison instructions for cftime
-            # then a TypeError is automatically raised. With Python2 in this
-            # scenario the default behaviour is to compare the object ids
-            # which will always have a result. Therefore there is no way to
-            # differentiate between objects that do or do not have legitimate
-            # comparisons, and so we cannot remove the TypeError below.
-            if sys.version_info[0] < 3:
-                raise TypeError("cannot compare {0!r} and {1!r}".format(self, other))
+            # With Python3 we can simply return NotImplemented. If the other
+            # object does not support rich comparison for cftime then a
+            # TypeError will be automatically raised. However, Python2 is not
+            # consistent with this Python3 behaviour. In Python2, we only
+            # delegate the comparison operation to the other object iff it has
+            # suitable rich comparison support available. This is deduced by
+            # introspection of the other object. Otherwise, we explicitly raise
+            # a TypeError to avoid Python2 defaulting to using either __cmp__
+            # comparision on the other object, or worst still, object ID
+            # comparison. Either way, at this point the comparision is deemed
+            # not valid from our perspective.
+            if sys.version_info.major == 2:
+                rop = _rop_lookup[op]
+                if (hasattr(other, '__richcmp__') or hasattr(other, rop)):
+                    # The other object potentially has the smarts to handle
+                    # the comparision, so allow the Python machinery to hand
+                    # the operation off to the other object.
+                    return NotImplemented
+                # Otherwise, the comparison is not valid.
+                emsg = "cannot compare {0!r} and {1!r}"
+                raise TypeError(emsg.format(self, other))
             else:
+                # Delegate responsibility of comparison to the other object.
                 return NotImplemented
 
     cdef _getstate(self):


=====================================
requirements-dev.txt
=====================================
@@ -1,4 +1,5 @@
+coverage
+coveralls
 cython
 pytest
-coveralls
 pytest-cov


=====================================
setup.cfg
=====================================
@@ -0,0 +1,9 @@
+[tool:pytest]
+testpaths = test
+addopts = 
+    -ra
+    --doctest-modules
+    --cov-config .coveragerc
+    --cov=cftime
+    --cov-report term-missing
+doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS


=====================================
setup.py
=====================================
@@ -1,15 +1,48 @@
+from __future__ import print_function
+
 import os
+import sys
+
+from Cython.Build import cythonize
+from setuptools import Command, Extension, setup
+
+
+COMPILER_DIRECTIVES = {}
+DEFINE_MACROS = None
+FLAG_COVERAGE = '--cython-coverage'  # custom flag enabling Cython line tracing
+BASEDIR = os.path.abspath(os.path.dirname(__file__))
+NAME = 'cftime'
+CFTIME_DIR = os.path.join(BASEDIR, NAME)
+CYTHON_FNAME = os.path.join(CFTIME_DIR, '_{}.pyx'.format(NAME))
+
+
+class CleanCython(Command):
+    description = 'Purge artifacts built by Cython'
+    user_options = []
 
-from setuptools import Extension, setup
+    def initialize_options(self):
+        pass
 
-rootpath = os.path.abspath(os.path.dirname(__file__))
+    def finalize_options(self):
+        pass
 
+    def run(self):
+        for rpath, _, fnames in os.walk(CFTIME_DIR):
+            for fname in fnames:
+                _, ext = os.path.splitext(fname)
+                if ext in ('.pyc', '.pyo', '.c', '.so'):
+                    artifact = os.path.join(rpath, fname)
+                    if os.path.exists(artifact):
+                        print('clean: removing file {!r}'.format(artifact))
+                        os.remove(artifact)
+                    else:
+                        print('clean: skipping file {!r}'.format(artifact))
 
-def extract_version(module='cftime'):
+
+def extract_version():
     version = None
-    fname = os.path.join(rootpath, module, '_cftime.pyx')
-    with open(fname) as f:
-        for line in f:
+    with open(CYTHON_FNAME) as fi:
+        for line in fi:
             if (line.startswith('__version__')):
                 _, version = line.split('=')
                 version = version.strip()[1:-1]  # Remove quotation characters.
@@ -17,22 +50,48 @@ def extract_version(module='cftime'):
     return version
 
 
-with open('requirements.txt') as f:
-    reqs = f.readlines()
-install_requires = [req.strip() for req in reqs]
+def load(fname):
+    result = []
+    with open(fname, 'r') as fi:
+        result = [package.strip() for package in fi.readlines()]
+    return result
+
+
+def description():
+    fname = os.path.join(BASEDIR, 'README.md')
+    with open(fname, 'r') as fi:
+        result = ''.join(fi.readlines())
+    return result
+
+
+if FLAG_COVERAGE in sys.argv or os.environ.get('CYTHON_COVERAGE', None):
+    COMPILER_DIRECTIVES = {'linetrace': True,
+                           'warn.maybe_uninitialized': False,
+                           'warn.unreachable': False,
+                           'warn.unused': False}
+    DEFINE_MACROS = [('CYTHON_TRACE', '1'),
+                     ('CYTHON_TRACE_NOGIL', '1')]
+    if FLAG_COVERAGE in sys.argv:
+        sys.argv.remove(FLAG_COVERAGE)
+    print('enable: "linetrace" Cython compiler directive')
 
-with open('requirements-dev.txt') as f:
-    reqs = f.readlines()
-tests_require = [req.strip() for req in reqs]
+extension = Extension('{}._{}'.format(NAME, NAME),
+                      sources=[CYTHON_FNAME],
+                      define_macros=DEFINE_MACROS)
 
 setup(
-    name='cftime',
+    name=NAME,
     author='Jeff Whitaker',
     author_email='jeffrey.s.whitaker at noaa.gov',
     description='Time-handling functionality from netcdf4-python',
-    packages=['cftime'],
+    long_description=description(),
+    long_description_content_type='text/markdown',
+    cmdclass={'clean_cython': CleanCython},
+    packages=[NAME],
     version=extract_version(),
-    ext_modules=[Extension('cftime._cftime', sources=['cftime/_cftime.pyx'])],
-    setup_requires=['setuptools>=18.0', 'cython>=0.19'],
-    install_requires=install_requires,
-    tests_require=tests_require)
+    ext_modules=cythonize(extension,
+                          compiler_directives=COMPILER_DIRECTIVES,
+                          language_level=2),
+    setup_requires=load('setup.txt'),
+    install_requires=load('requirements.txt'),
+    tests_require=load('requirements-dev.txt'))


=====================================
setup.txt
=====================================
@@ -0,0 +1,2 @@
+cython>=0.19
+setuptools>=18.0


=====================================
test/test_cftime.py
=====================================
@@ -1,5 +1,8 @@
 from __future__ import print_function
+
 import copy
+import operator
+import sys
 import unittest
 from collections import namedtuple
 from datetime import datetime, timedelta
@@ -8,17 +11,18 @@ import numpy as np
 import pytest
 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,
                     DatetimeGregorian, DatetimeJulian, DatetimeNoLeap,
                     DatetimeProlepticGregorian, JulianDayFromDate, _parse_date,
                     date2index, date2num, num2date, utime)
-import cftime
 
 # test cftime module for netCDF time <--> python datetime conversions.
 
 dtime = namedtuple('dtime', ('values', 'units', 'calendar'))
+dateformat =  '%Y-%m-%d %H:%M:%S'
 
 
 class CFTimeVariable(object):
@@ -128,11 +132,11 @@ class cftimeTestCase(unittest.TestCase):
         self.assertTrue(self.cdftime_pg.calendar == 'proleptic_gregorian')
         # check date2num method.
         d = datetime(1990, 5, 5, 2, 17)
-        t1 = np.around(self.cdftime_pg.date2num(d))
-        self.assertTrue(t1 == 62777470620.0)
+        t1 = self.cdftime_pg.date2num(d)
+        self.assertTrue(np.around(t1) == 62777470620.0)
         # check num2date method.
         d2 = self.cdftime_pg.num2date(t1)
-        self.assertTrue(str(d) == str(d2))
+        self.assertTrue(d.strftime(dateformat) == d2.strftime(dateformat))
         # check day of year.
         ndayr = d.timetuple()[7]
         self.assertTrue(ndayr == 125)
@@ -249,7 +253,6 @@ class cftimeTestCase(unittest.TestCase):
         # day goes out of range).
         t = 733499.0
         d = num2date(t, units='days since 0001-01-01 00:00:00')
-        dateformat =  '%Y-%m-%d %H:%M:%S'
         assert_equal(d.strftime(dateformat), '2009-04-01 00:00:00')
         # test edge case of issue 75 for numerical problems
         for t in (733498.999, 733498.9999, 733498.99999, 733498.999999, 733498.9999999):
@@ -333,17 +336,11 @@ class cftimeTestCase(unittest.TestCase):
         # also tests error found in issue #349
         calendars=['standard', 'gregorian', 'proleptic_gregorian', 'noleap', 'julian',\
                    'all_leap', '365_day', '366_day', '360_day']
-        dateformat =  '%Y-%m-%d %H:%M:%S'
         dateref = datetime(2015,2,28,12)
         ntimes = 1001
         verbose = True # print out max error diagnostics
-        precis = np.finfo(np.longdouble).precision
-        if precis < 18:
-            fact = 10
-        else:
-            fact = 1.
         for calendar in calendars:
-            eps = 10.*fact
+            eps = 100.
             units = 'microseconds since 2000-01-30 01:01:01'
             microsecs1 = date2num(dateref,units,calendar=calendar)
             maxerr = 0
@@ -360,7 +357,7 @@ class cftimeTestCase(unittest.TestCase):
                 print('calender = %s max abs err (microsecs) = %s eps = %s' % \
                      (calendar,maxerr,eps))
             units = 'milliseconds since 1800-01-30 01:01:01'
-            eps = 0.01*fact
+            eps = 0.1
             millisecs1 = date2num(dateref,units,calendar=calendar)
             maxerr = 0.
             for n in range(ntimes):
@@ -375,7 +372,7 @@ class cftimeTestCase(unittest.TestCase):
             if verbose:
                 print('calender = %s max abs err (millisecs) = %s eps = %s' % \
                      (calendar,maxerr,eps))
-            eps = 1.e-4*fact
+            eps = 1.e-3
             units = 'seconds since 0001-01-30 01:01:01'
             secs1 = date2num(dateref,units,calendar=calendar)
             maxerr = 0.
@@ -391,7 +388,7 @@ class cftimeTestCase(unittest.TestCase):
             if verbose:
                 print('calender = %s max abs err (secs) = %s eps = %s' % \
                      (calendar,maxerr,eps))
-            eps = 1.e-6*fact
+            eps = 1.e-5
             units = 'minutes since 0001-01-30 01:01:01'
             mins1 = date2num(dateref,units,calendar=calendar)
             maxerr = 0.
@@ -407,7 +404,7 @@ class cftimeTestCase(unittest.TestCase):
             if verbose:
                 print('calender = %s max abs err (mins) = %s eps = %s' % \
                      (calendar,maxerr,eps))
-            eps = 1.e-7*fact
+            eps = 1.e-6
             units = 'hours since 0001-01-30 01:01:01'
             hrs1 = date2num(dateref,units,calendar=calendar)
             maxerr = 0.
@@ -423,7 +420,7 @@ class cftimeTestCase(unittest.TestCase):
             if verbose:
                 print('calender = %s max abs err (hours) = %s eps = %s' % \
                      (calendar,maxerr,eps))
-            eps = 1.e-9*fact
+            eps = 1.e-8
             units = 'days since 0001-01-30 01:01:01'
             days1 = date2num(dateref,units,calendar=calendar)
             maxerr = 0.
@@ -670,6 +667,10 @@ class cftimeTestCase(unittest.TestCase):
              1, 'months since 01-01-01',calendar='standard')
         self.assertRaises(ValueError, utime, \
             'months since 01-01-01', calendar='standard')
+        # issue #78 - extra digits due to roundoff
+        assert(cftime.date2num(cftime.datetime(1, 12, 1, 0, 0, 0, 0, -1, 1), units='days since 01-01-01',calendar='noleap')  == 334.0)
+        assert(cftime.date2num(cftime.num2date(1.0,units='days since 01-01-01',calendar='noleap'),units='days since 01-01-01',calendar='noleap') == 1.0)
+        assert(cftime.date2num(cftime.DatetimeNoLeap(1980, 1, 1, 0, 0, 0, 0, 6, 1),'days since 1970-01-01','noleap') == 3650.0)
 
 
 class TestDate2index(unittest.TestCase):
@@ -686,10 +687,6 @@ class TestDate2index(unittest.TestCase):
             and `step` define the starting date, the length of the array and
             the distance between each date (in units).
 
-            :Example:
-            >>> t = TestTime(datetime(1989, 2, 18), 45, 6, 'hours since 1979-01-01')
-            >>> print num2date(t[1], t.units)
-            1989-02-18 06:00:00
             """
             self.units = units
             self.calendar = calendar
@@ -1106,12 +1103,75 @@ class DateTime(unittest.TestCase):
             self.datetime_date1 > self.date2_365_day
 
         def not_comparable_4():
-            "compare a datetime instance to something other than a datetime"
+            "compare a datetime instance to non-datetime"
             self.date1_365_day > 0
 
+        def not_comparable_5():
+            "compare non-datetime to a datetime instance"
+            0 < self.date_1_365_day
+
         for func in [not_comparable_1, not_comparable_2, not_comparable_3, not_comparable_4]:
             self.assertRaises(TypeError, func)
 
+    @pytest.mark.skipif(sys.version_info.major != 2,
+                        reason='python2 specific, non-comparable test')
+    def test_richcmp_py2(self):
+        class Rich(object):
+            """Dummy class with traditional rich comparison support."""
+            def __lt__(self, other):
+                raise NotImplementedError('__lt__')
+            def __le__(self, other):
+                raise NotImplementedError('__le__')
+            def __eq__(self, other):
+                raise NotImplementedError('__eq__')
+            def __ne__(self, other):
+                raise NotImplementedError('__ne__')
+            def __gt__(self, other):
+                raise NotImplementedError('__gt__')
+            def __ge__(self, other):
+                raise NotImplementedError('__ge__')
+
+        class CythonRich(object):
+            """Dummy class with spoof cython rich comparison support."""
+            def __richcmp__(self, other):
+                """
+                This method is never called. However it is introspected
+                by the cftime.datetime.__richcmp__ method, which will then
+                return NotImplemented, causing Python to call this classes
+                __cmp__ method as a back-stop, and hence spoofing the
+                cython specific rich comparison behaviour.
+                """
+                pass
+            def __cmp__(self, other):
+                raise NotImplementedError('__richcmp__')
+
+        class Pass(object):
+            """Dummy class with no rich comparison support whatsoever."""
+            pass
+
+        class Pass___cmp__(object):
+            """Dummy class that delegates all comparisons."""
+            def __cmp__(self, other):
+                return NotImplemented
+
+        # Test LHS operand comparison operator processing.
+        for op, expected in [(operator.gt, '__lt__'), (operator.ge, '__le__'),
+                             (operator.eq, '__eq__'), (operator.ne, '__ne__'),
+                             (operator.lt, '__gt__'), (operator.le, '__ge__')]:
+            with self.assertRaisesRegexp(NotImplementedError, expected):
+                op(self.date1_365_day, Rich())
+
+            with self.assertRaisesRegexp(NotImplementedError, '__richcmp__'):
+                op(self.date1_365_day, CythonRich())
+
+        # Test RHS operand comparison operator processing.
+        for op in [operator.gt, operator.ge, operator.eq, operator.ne,
+                   operator.lt, operator.le]:
+            with self.assertRaisesRegexp(TypeError, 'cannot compare'):
+                op(Pass(), self.date1_365_day)
+
+            with self.assertRaisesRegexp(TypeError, 'cannot compare'):
+                op(Pass___cmp__(), self.date1_365_day)
 
 
 class issue17TestCase(unittest.TestCase):



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/cftime/commit/852410474bcf4617ceef81299cbad89ff41df4a8
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/20181202/2c9848f8/attachment-0001.html>


More information about the Pkg-grass-devel mailing list