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

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Tue Oct 14 04:36:38 BST 2025



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


Commits:
3969742f by Bas Couwenberg at 2025-10-14T05:21:47+02:00
New upstream version 1.6.5
- - - - -


17 changed files:

- − .coveragerc
- .github/workflows/cibuildwheel.yml
- .github/workflows/deploy-docs.yml
- − .github/workflows/publish.yml
- .github/workflows/tests_conda.yml
- .github/workflows/tests_latest.yml
- Changelog
- MANIFEST.in
- README.md
- pyproject.toml
- − requirements-dev.txt
- − requirements.txt
- − setup.cfg
- setup.py
- src/cftime/__init__.py
- src/cftime/_cftime.pyx
- test/test_cftime.py


Changes:

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


=====================================
.github/workflows/cibuildwheel.yml
=====================================
@@ -29,7 +29,7 @@ jobs:
             arch: x86_64
 
     steps:
-    - uses: actions/checkout at v4
+    - uses: actions/checkout at v5
       with:
         fetch-depth: 0
 
@@ -42,34 +42,32 @@ jobs:
 
     - name: Build just oldest and newest on PRs, all on tags
       shell: bash
-      # - Always omit musl 3.8 b/c NumPy does not provide wheels for it
       # - Always omit musllinux_aarch64 because it's slow and niche
+      # - Always skip free-threading wheels for now
       # - On PPs, omit musllinux for speed
-      # - On PRs, run just oldest and newest Python versions (and omit 3.8 aarch64)
+      # - On PRs, run just oldest and newest Python versions
       run: |
         if [[ "${{ github.event_name }}" == "pull_request" ]]; then
-          CIBW_SKIP="pp* cp36-* cp37-* cp38-musllinux* cp39-* cp310-* cp311-* cp38-*_aarch64 *musllinux*"
+          CIBW_SKIP="cp311-* cp312-* cp313-* cp314t-* *musllinux*"
         else
-          CIBW_SKIP="pp* cp36-* cp37-* cp38-musllinux* *musllinux_aarch64"
+          CIBW_SKIP="cp314t-* *musllinux_aarch64"
         fi
         echo "CIBW_SKIP=$CIBW_SKIP" >> $GITHUB_ENV
         echo "Setting CIBW_SKIP=$CIBW_SKIP"
 
     - name: "Building ${{ matrix.os }} (${{ matrix.arch }}) wheels"
-      uses: pypa/cibuildwheel at v2.18.1
+      uses: pypa/cibuildwheel at v3.2.1
       env:
-        # Skips pypy py36,37
         CIBW_SKIP: ${{ env.CIBW_SKIP }}
         CIBW_ARCHS: ${{ matrix.arch }}
         CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
         CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014
-        # Emulated testing is slow, so trust that the Python 3.12 test is good enough on aarch64
+        # Emulated testing is slow, so trust that the Python 3.14 test is good enough on aarch64
         # (takes about 5 minutes per wheel to build, and 5 minutes to test)
-        CIBW_TEST_SKIP: "cp38-*_aarch64 cp39-*_aarch64 cp310-*_aarch64 cp311-*_aarch64"
-        CIBW_TEST_REQUIRES: pytest
+        CIBW_TEST_SKIP: "cp3{10,11,12,13}-*_aarch64"
+        CIBW_TEST_GROUPS: dev
         CIBW_TEST_COMMAND: >
           python -c "import cftime; print(f'cftime v{cftime.__version__}')" &&
-          python -m pip install check-manifest cython pytest pytest-cov &&
           python -m pytest -vv {package}/test
 
     - uses: actions/upload-artifact at v4
@@ -80,9 +78,11 @@ jobs:
 
   build_sdist:
     name: Build source distribution
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout at v4
+      - uses: actions/checkout at v5
+        with:
+          fetch-depth: 0
 
       - name: Build sdist
         run: >
@@ -91,14 +91,15 @@ jobs:
 
       - uses: actions/upload-artifact at v4
         with:
-          path: dist/*.tar.gz
+          name: pypi-artifacts
+          path: ${{ github.workspace }}/dist/*.tar.gz
 
   show-artifacts:
     needs: [build_bdist, build_sdist]
     name: "Show artifacts"
     runs-on: ubuntu-22.04
     steps:
-    - uses: actions/download-artifact at v4
+    - uses: actions/download-artifact at v5
       with:
         pattern: pypi-artifacts*
         path: ${{ github.workspace }}/dist
@@ -116,7 +117,7 @@ jobs:
     # upload to PyPI for every tag starting with 'v'
     if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
     steps:
-    - uses: actions/download-artifact at v4
+    - uses: actions/download-artifact at v5
       with:
         pattern: pypi-artifacts*
         path: ${{ github.workspace }}/dist


=====================================
.github/workflows/deploy-docs.yml
=====================================
@@ -11,12 +11,12 @@ jobs:
 
     steps:
     - name: checkout
-      uses: actions/checkout at v4
+      uses: actions/checkout at v5
       with:
         fetch-depth: 0
 
     - name: Setup Mamba
-      uses: mamba-org/setup-micromamba at v1
+      uses: mamba-org/setup-micromamba at v2
       with:
         environment-name: TEST
         create-args: >-


=====================================
.github/workflows/publish.yml deleted
=====================================
@@ -1,48 +0,0 @@
-name: Publish to PyPI
-
-on: ["push", "pull_request"]
-
-jobs:
-  packages:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout at v4
-
-    - name: Set up Python
-      uses: actions/setup-python at v5
-      with:
-        python-version: 3.x
-
-    - name: Get tags
-      run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
-      shell: bash
-
-    - name: Install build tools
-      run: |
-        python -m pip install --upgrade pip wheel setuptools setuptools_scm build twine check-manifest numpy
-
-      shell: bash
-
-    - name: Build binary wheel
-      run: python -m build --sdist --wheel . --outdir dist
-
-    - name: CheckFiles
-      run: |
-        ls dist
-      shell: bash
-
-    - name: Test wheels and sdist
-      run: |
-        python setup.py --version
-        check-manifest --verbose
-        cd dist && python -m pip install cftime*.whl
-        python -m twine check *
-
-      shell: bash
-
-    - name: Publish a Python distribution to PyPI
-      if: ${{ github.event_name == 'release' }}
-      uses: pypa/gh-action-pypi-publish at master
-      with:
-        user: __token__
-        password: ${{ secrets.PYPI_PASSWORD }}


=====================================
.github/workflows/tests_conda.yml
=====================================
@@ -12,7 +12,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
+        python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ]
         os: [windows-latest, ubuntu-latest, macos-latest]
         platform: [x64, x32]
         experimental: [false]
@@ -25,10 +25,10 @@ jobs:
             experimental: true
 
     steps:
-    - uses: actions/checkout at v4
+    - uses: actions/checkout at v5
 
     - name: Setup micromamba Env
-      uses: mamba-org/setup-micromamba at v1
+      uses: mamba-org/setup-micromamba at v2
       with:
         environment-name: TEST
         create-args: >-
@@ -36,7 +36,7 @@ jobs:
           numpy>1.13.3
           cython>=0.29.20
           pytest
-          pytest-cov
+          coverage
     - name: Install unstable dependencies
       if: matrix.experimental == true
       shell: bash -l {0}
@@ -57,4 +57,5 @@ jobs:
     - name: Run Tests
       shell: bash -l {0}
       run: |
-        pytest -vv test
+        coverage run -m pytest -vv test
+        coverage report


=====================================
.github/workflows/tests_latest.yml
=====================================
@@ -6,13 +6,13 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.13.0-alpha.1"]
+        python-version: ["3.14.0b.1"]
     steps:
 
-    - uses: actions/checkout at v4
+    - uses: actions/checkout at v5
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python at v5
+      uses: actions/setup-python at v6
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -22,7 +22,7 @@ jobs:
 
     - name: Install unstable cftime dependencies via pip
       run: |
-        python -m pip install --pre -r requirements-dev.txt
+        python -m pip install --pre --group dev
         # get nightly wheels for numpy
         python -m pip install \
           --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/ \


=====================================
Changelog
=====================================
@@ -1,3 +1,12 @@
+version 1.6.5 (release tag v1.6.5rel)
+=====================================
+* python 3.14 wheels, 3.8/3.9 support dropped.
+* roundtrip not correct when dates are all python datetime
+  instances and calendar not proleptic_gregorian (issue #354).
+* fix cftime.datetime.strftime so it works with two digit year
+  formatting (%y, issue #362).
+* speed up comparisions (using __richcmp___) and update docs. Issue #365.
+
 version 1.6.4 (release tag v1.6.4rel)
 =====================================
  * build aarch64 linux wheels (issue #333).


=====================================
MANIFEST.in
=====================================
@@ -1,6 +1,4 @@
 include *.txt
-include README.md
-include LICENSE
 include pyproject.toml
 include Changelog
 exclude *.legacy


=====================================
README.md
=====================================
@@ -12,6 +12,8 @@ Time-handling functionality from netcdf4-python
 ## News
 For details on the latest updates, see the [Changelog](https://github.com/Unidata/cftime/blob/master/Changelog).
 
+10/13/2025:  Version 1.6.5 release.  Minor bugfixes/optimizations, wheels for python 3.14, no more wheels for 3.8/3.9.
+
 6/7/2024:  Version 1.6.4 release.  Wheels for muslinux and aarch64, numpy 2.0 compatibility. 
 
 10/20/2023:  Version 1.6.3 released.  Support for python 3.12, cython 3.0, strptime formats without separators.


=====================================
pyproject.toml
=====================================
@@ -1,9 +1,102 @@
+[project]
+name = "cftime"
+version = "1.6.5"
+description = "Time-handling functionality from netcdf4-python"
+readme = "README.md"
+authors = [
+    { name="Jeff Whitaker", email="whitaker.jeffrey at gmail.com"}
+]
+license = "MIT"
+license-files = ["LICENSE"]
+classifiers = [
+    'Development Status :: 5 - Production/Stable',
+    'Operating System :: MacOS :: MacOS X',
+    'Operating System :: Microsoft :: Windows',
+    'Operating System :: POSIX :: Linux',
+    'Programming Language :: Python',
+    'Programming Language :: Python :: 3',
+    'Programming Language :: Python :: 3.10',
+    'Programming Language :: Python :: 3.11',
+    'Programming Language :: Python :: 3.12',
+    'Programming Language :: Python :: 3.13',
+    'Programming Language :: Python :: 3.14',
+    'Topic :: Scientific/Engineering',
+]
+dependencies = [
+    "numpy>=1.21.2",
+]
+requires-python = ">=3.10"
+
+[dependency-groups]
+dev = [
+    "check-manifest",
+    "coverage>=7.10.0",
+    "coveralls",
+    "cython>=0.29.20",
+    "pytest",
+    "sphinx",
+    "twine",
+]
+
 [build-system]
 requires = [
-    "setuptools>=41.2",
+    "setuptools>=77.0.1",
     "cython>=0.29.20",
-    "wheel",
-    "oldest-supported-numpy ; python_version < '3.9'",
-    "numpy>=2.0.0rc1,<3 ; python_version >= '3.9'",
+    "numpy>=2.0.0,<3",
 ]
 build-backend = "setuptools.build_meta"
+
+[tool.setuptools]
+package-dir = {"" = "src"}
+
+[tool.pytest.ini_options]
+testpaths = "test"
+addopts = [
+    "-ra",
+    "-v",
+    "--doctest-modules",
+]
+doctest_optionflags = [
+    "NORMALIZE_WHITESPACE",
+    "ELLIPSIS",
+]
+
+[tool.coverage.run]
+source = ["src"]
+relative_files = true
+branch = true
+plugins = [
+    "Cython.Coverage",
+]
+include = [
+    "src/cftime/*",
+]
+omit = [
+    "setup.py",
+    "docs/*",
+    "ci/*",
+    "test/*",
+    ".eggs",
+]
+
+[tool.coverage.report]
+show_missing = true
+exclude_lines = [
+    "pragma: no cover",
+    "def __repr__",
+    "if __name__ == .__main__.:",
+]
+
+[tool.check-manifest]
+ignore = [
+    "*.yml",
+    ".coveragerc",
+    ".gitignore",
+    "README.release",
+    "ci",
+    "ci/*",
+    "docs",
+    "docs/*",
+    "test",
+    "test/*",
+]


=====================================
requirements-dev.txt deleted
=====================================
@@ -1,9 +0,0 @@
-check-manifest
-coverage
-coveralls
-cython>=0.29.20
-pytest
-pytest-cov
-sphinx
-twine
-wheel


=====================================
requirements.txt deleted
=====================================
@@ -1,2 +0,0 @@
-numpy>1.13.3; python_version<'3.12.0.rc1'
-numpy>=1.26.0b1; python_version>='3.12.0.rc1'


=====================================
setup.cfg deleted
=====================================
@@ -1,23 +0,0 @@
-[tool:pytest]
-testpaths = test
-addopts = 
-    -ra
-    -v
-    --doctest-modules
-    --cov-config .coveragerc
-    --cov=cftime
-    --cov-report term-missing
-doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS
-
-[check-manifest]
-ignore =
-    *.yml
-    .coveragerc
-    .gitignore
-    README.release
-    ci
-    ci/*
-    docs
-    docs/*
-    test
-    test/*


=====================================
setup.py
=====================================
@@ -2,14 +2,9 @@ import os
 import sys
 import numpy
 
+from Cython.Build import cythonize
 from setuptools import Command, Extension, setup
 
-# https://github.com/Unidata/cftime/issues/34
-try:
-    from Cython.Build import cythonize
-except ImportError:
-    cythonize = False
-
 
 BASEDIR = os.path.abspath(os.path.dirname(__file__))
 SRCDIR = os.path.join(BASEDIR,'src')
@@ -58,31 +53,6 @@ class CleanCython(Command):
                         print('clean: skipping file {!r}'.format(artifact))
 
 
-def extract_version():
-    version = None
-    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.
-                break
-    return version
-
-
-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))
     and cythonize):
     COMPILER_DIRECTIVES = {
@@ -99,43 +69,17 @@ if any([arg in CMDS_NOCYTHONIZE for arg in sys.argv]):
     ext_modules = []
 else:
     extension = Extension('{}._{}'.format(NAME, NAME),
-                          sources=[CYTHON_FNAME],
+                          sources=[os.path.relpath(CYTHON_FNAME, BASEDIR)],
                           define_macros=DEFINE_MACROS,
                           include_dirs=[numpy.get_include(),])
-    ext_modules = [extension]
-    if cythonize:
-        ext_modules = cythonize(extension,
-                                compiler_directives=COMPILER_DIRECTIVES,
-                                language_level=3)
+
+    ext_modules = cythonize(
+        extension,
+        compiler_directives=COMPILER_DIRECTIVES,
+        language_level=3,
+    )
 
 setup(
-    name=NAME,
-    author='Jeff Whitaker',
-    author_email='jeffrey.s.whitaker at noaa.gov',
-    description='Time-handling functionality from netcdf4-python',
-    long_description=description(),
-    long_description_content_type='text/markdown',
     cmdclass={'clean_cython': CleanCython},
-    packages=[NAME],
-    package_dir={'':'src'},
-    version=extract_version(),
     ext_modules=ext_modules,
-    install_requires=load('requirements.txt'),
-    tests_require=load('requirements-dev.txt'),
-    license='License :: OSI Approved :: MIT License',
-    python_requires=">=3.8",
-    classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'Operating System :: MacOS :: MacOS X',
-        'Operating System :: Microsoft :: Windows',
-        'Operating System :: POSIX :: Linux',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Programming Language :: Python :: 3.11',
-        'Programming Language :: Python :: 3.12',
-        'Topic :: Scientific/Engineering',
-        'License :: OSI Approved :: MIT License'],
-    )
+)


=====================================
src/cftime/__init__.py
=====================================
@@ -4,8 +4,16 @@ from ._cftime import num2date, date2num, date2index, time2index, num2pydate, to_
 from ._cftime import (microsec_units, millisec_units,
                      sec_units, hr_units, day_units, min_units,
                      UNIT_CONVERSION_FACTORS)
-from ._cftime import __version__, CFWarning
+from ._cftime import CFWarning
 # these will be removed in a future release
 from ._cftime import (DatetimeNoLeap, DatetimeAllLeap, Datetime360Day,
                      Datetime360Day, DatetimeJulian, 
                      DatetimeGregorian, DatetimeProlepticGregorian)
+
+
+def __getattr__(item: str):
+    if item == "__version__":
+        from importlib.metadata import version
+        return version("cftime")
+    
+    raise AttributeError(f"module 'cftime' has no attribute {item!r}")


=====================================
src/cftime/_cftime.pyx
=====================================
@@ -40,8 +40,6 @@ 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]
 
-__version__ = '1.6.4'
-
 # 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.
 #       For example, the TZ spec "+01:0" will still work even though the minutes value is only one character long.
@@ -261,7 +259,7 @@ def date2num(dates, units, calendar=None, has_year_zero=None, longdouble=False):
     if unit in ["months", "month"] and calendar != "360_day":
         raise ValueError("Units of months only valid for 360_day calendar.")
     unit_timedelta = timedelta(microseconds=UNIT_CONVERSION_FACTORS[unit])
-    can_use_python_basedatetime = _can_use_python_datetime(basedate,calendar)
+    can_use_python_basedatetime = calendar=='proleptic_gregorian' and _can_use_python_datetime(basedate,calendar)
 
     if can_use_python_basedatetime and all_python_datetimes:
         use_python_datetime = True
@@ -1062,6 +1060,11 @@ Gregorian calendar.
 
 Supports timedelta operations by overloading +/-, and
 comparisons with other instances (even if they use different calendars).
+When comparing instances with different calendars, the second instance in the comparison (RHS) is
+converted to the calendar of the LHS instance.  When comparing a list or array of instances (all using
+the same calendar) to a single scalar instance,
+it is much faster to convert the single instance to the calendar of the array before doing
+the comparison.
 
 Comparison with native python datetime instances is possible
 for cftime.datetime instances using
@@ -1410,7 +1413,7 @@ The default format of the string produced by strftime is controlled by self.form
             return hash(self.timetuple())
         return hash(d)
 
-    cdef to_tuple(self):
+    def to_tuple(self):
         return (self.year, self.month, self.day, self.hour, self.minute,
                 self.second, self.microsecond)
 
@@ -1423,19 +1426,12 @@ The default format of the string produced by strftime is controlled by self.form
             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:
-                # 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)
+                # raise an error if either is an idealized calendar (comparison not valid)
+                if dt.calendar in _idealized_calendars or other.calendar in _idealized_calendars:
+                    raise TypeError("cannot compare {0!r} and {1!r}".format(dt, other))
+                # convert one to match calendar of the other.
+                other2 = other.change_calendar(dt.calendar,has_year_zero=dt.has_year_zero)
+                return PyObject_RichCompare(dt.to_tuple(), other2.to_tuple(), op)
         elif isinstance(other, datetime_python):
             # comparing datetime and real_datetime
             if not dt.datetime_compatible:
@@ -1471,8 +1467,8 @@ The default format of the string produced by strftime is controlled by self.form
     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 
+        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()
@@ -1696,10 +1692,17 @@ cdef _strftime(datetime dt, fmt):
     year = year + ((2000 - year) // 28) * 28
     timetuple = dt.timetuple()
     s1 = time.strftime(fmt1, (year,) + timetuple[1:])
-    sites1 = _findall(s1, str(year))
+    twodigityear = 'y' in fmt1
+    if twodigityear:
+        sites1 = _findall(s1, str(year)[-2:])
+    else:
+        sites1 = _findall(s1, str(year))
 
     s2 = time.strftime(fmt1, (year + 28,) + timetuple[1:])
-    sites2 = _findall(s2, str(year + 28))
+    if twodigityear:
+       sites2 = _findall(s2, str(year + 28)[-2:])
+    else:
+       sites2 = _findall(s2, str(year + 28))
 
     sites = []
     for site in sites1:
@@ -1711,8 +1714,14 @@ cdef _strftime(datetime dt, fmt):
         syear = "%05d" % (dt.year,)
     else:
         syear = "%04d" % (dt.year,)
+    n=4
+    if twodigityear:
+        syear = syear[-2:]
+        if dt.year < 0:
+            syear = '-'+syear
+        n=2
     for site in sites:
-        s = s[:site] + syear + s[site + 4:]
+        s = s[:site] + syear + s[site + n:]
     if ihavems:
         s = s + '.{:06d}'.format(dt.microsecond)
     return s


=====================================
test/test_cftime.py
=====================================
@@ -64,6 +64,7 @@ est = timezone(timedelta(hours=-5), 'UTC')
 
 dtime = namedtuple('dtime', ('values', 'units', 'calendar'))
 dateformat =  '%Y-%m-%d %H:%M:%S'
+dateformat2 =  '%y-%m-%d %H:%M:%S'
 
 calendars=['standard', 'gregorian', 'proleptic_gregorian', 'noleap', 'julian',\
            'all_leap', '365_day', '366_day', '360_day']
@@ -201,6 +202,8 @@ class cftimeTestCase(unittest.TestCase):
         # check num2date method.
         d2 = self.cdftime_pg.num2date(t1)
         self.assertTrue(d.strftime(dateformat) == d2.strftime(dateformat))
+        # make sure two digit years work in strftime (issue #362)
+        self.assertTrue(d.strftime(dateformat2) == d2.strftime(dateformat2))
         # check day of year.
         ndayr = d.timetuple()[7]
         self.assertTrue(ndayr == 125)
@@ -943,6 +946,16 @@ class cftimeTestCase(unittest.TestCase):
         times = np.array([1,2,3,np.inf],dtype=np.float64)
         result = cftime.num2date(times, 'days since 2000-01-01', 'standard')
         np.testing.assert_equal(result, expected)
+        # issue #354: roundtrip not correct when dates are all python datetime
+        # instances and calendar not proleptic_gregorian.
+        datesin = np.array(["0002"],
+                  dtype="datetime64[s]").astype("M8[us]").astype(datetime)
+        datein = datesin.item()
+        num = cftime.date2num(datein, "seconds since 2000-01-01", calendar='standard')
+        dateout = cftime.num2date(num, "seconds since 2000-01-01", calendar='standard')
+        dateout2 = cftime.datetime(datein.year, datein.month, datein.day,
+                   calendar='standard')
+        assert(dateout==dateout2)
 
 
 class TestDate2index(unittest.TestCase):



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/cftime/-/commit/3969742f6c741b222116ae967537de5418fa6f95
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/20251014/9c9f5e32/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list