Skip to content
Commits on Source (4)
......@@ -6,6 +6,8 @@ Time-handling functionality from netcdf4-python
[![PyPI package](https://badge.fury.io/py/cftime.svg)](http://python.org/pypi/cftime)
## News
08/15/2018: version 1.0.1 released.
11/8/2016: `cftime` was split out of the [netcdf4-python](https://github.com/Unidata/netcdf4-python) package.
## Quick Start
......
......@@ -2,20 +2,12 @@ environment:
CONDA_INSTALL_LOCN: C:\\Miniconda36-x64
matrix:
- TARGET_ARCH: x64
CONDA_NPY: 111
CONDA_PY: 27
NPY: 1.15
PY: 3.6
- TARGET_ARCH: x64
CONDA_NPY: 114
CONDA_PY: 27
- TARGET_ARCH: x64
CONDA_NPY: 111
CONDA_PY: 36
- TARGET_ARCH: x64
CONDA_NPY: 114
CONDA_PY: 36
NPY: 1.15
PY: 3.7
platform:
- x64
......@@ -35,6 +27,7 @@ install:
- cmd: call %CONDA_INSTALL_LOCN%\Scripts\activate.bat
- cmd: conda.exe config --set always_yes yes --set changeps1 no --set show_channel_urls true
- cmd: conda.exe update conda
- cmd: conda.exe config --remove channels defaults --force
- cmd: conda.exe config --add channels conda-forge --force
- cmd: set PYTHONUNBUFFERED=1
......@@ -48,4 +41,7 @@ install:
build: off
test_script:
- "conda build conda.recipe"
- conda.exe create --name TEST python=%PY% numpy=%NPY% cython pip pytest
- conda activate TEST
- python -m pip install . --no-deps --ignore-installed --no-cache-dir -vvv
- py.test -vv test
......@@ -27,7 +27,7 @@ _units = microsec_units+millisec_units+sec_units+min_units+hr_units+day_units
_calendars = ['standard', 'gregorian', 'proleptic_gregorian',
'noleap', 'julian', 'all_leap', '365_day', '366_day', '360_day']
__version__ = '1.0.0'
__version__ = '1.0.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.
......@@ -45,19 +45,29 @@ TIMEZONE_REGEX = re.compile(
# start of the gregorian calendar
gregorian = real_datetime(1582,10,15)
def _datesplit(timestr):
"""split a time string into two components, units and the remainder
after 'since'
"""
try:
(units, sincestring, remainder) = timestr.split(None,2)
except ValueError as e:
raise ValueError('Incorrectly formatted CF date-time unit_string')
if sincestring.lower() != 'since':
raise ValueError("no 'since' in unit_string")
return units.lower(), remainder
def _dateparse(timestr):
"""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.
timestr_split = timestr.split()
units = timestr_split[0].lower()
if timestr_split[1].lower() != 'since':
raise ValueError("no 'since' in unit_string")
(units, isostring) = _datesplit(timestr)
# parse the date string.
n = timestr.find('since')+6
isostring = timestr[n:]
year, month, day, hour, minute, second, utc_offset =\
_parse_date( isostring.strip() )
if year >= MINYEAR:
......@@ -75,17 +85,13 @@ def _dateparse(timestr):
cdef _parse_date_and_units(timestr):
"""parse a string of the form time-units since yyyy-mm-dd hh:mm:ss
return a tuple (units,utc_offset, datetimeinstance)"""
timestr_split = timestr.split()
units = timestr_split[0].lower()
(units, isostring) = _datesplit(timestr)
if units not in _units:
raise ValueError(
"units must be one of 'seconds', 'minutes', 'hours' or 'days' (or singular version of these), got '%s'" % units)
if timestr_split[1].lower() != 'since':
raise ValueError("no 'since' in unit_string")
# parse the date string.
n = timestr.find('since') + 6
year, month, day, hour, minute, second, utc_offset = _parse_date(
timestr[n:].strip())
isostring.strip())
return units, utc_offset, datetime(year, month, day, hour, minute, second)
......@@ -119,7 +125,7 @@ def date2num(dates,units,calendar='standard'):
"""
calendar = calendar.lower()
basedate = _dateparse(units)
unit = units.split()[0].lower()
(unit, ignore) = _datesplit(units)
# real-world calendars limited to positive reference years.
if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
if basedate.year == 0:
......@@ -143,7 +149,7 @@ def date2num(dates,units,calendar='standard'):
dates = numpy.array(dates)
shape = dates.shape
ismasked = False
if hasattr(dates,'mask'):
if numpy.ma.isMA(dates) and numpy.ma.is_masked(dates):
mask = dates.mask
ismasked = True
times = []
......@@ -219,7 +225,7 @@ def num2date(times,units,calendar='standard',only_use_cftime_datetimes=False):
"""
calendar = calendar.lower()
basedate = _dateparse(units)
unit = units.split()[0].lower()
(unit, ignore) = _datesplit(units)
# real-world calendars limited to positive reference years.
if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
if basedate.year == 0:
......@@ -248,7 +254,7 @@ def num2date(times,units,calendar='standard',only_use_cftime_datetimes=False):
times = numpy.array(times, dtype='d')
shape = times.shape
ismasked = False
if hasattr(times,'mask'):
if numpy.ma.isMA(times) and numpy.ma.is_masked(times):
mask = times.mask
ismasked = True
dates = []
......@@ -1148,7 +1154,7 @@ units to datetime objects.
except:
isscalar = True
ismasked = False
if hasattr(time_value, 'mask'):
if numpy.ma.isMA(time_value) and numpy.ma.is_masked(time_value):
mask = time_value.mask
ismasked = True
if not isscalar:
......@@ -2046,3 +2052,4 @@ cdef tuple add_timedelta_360_day(datetime dt, delta):
month = (month - 1) % 12 + 1
return (year, month, day, hour, minute, second, microsecond, -1, 1)
{% set version = "dev" %}
package:
name: cftime
version: {{ version }}
source:
path: ../
build:
number: 0
script: python setup.py install --single-version-externally-managed --record record.txt
requirements:
build:
- python
- setuptools
- cython
- numpy x.x
run:
- python
- setuptools
- numpy x.x
test:
source_files:
- test
requires:
- pytest
imports:
- cftime
commands:
- py.test -vv test
about:
home: https://github.com/Unidata/cftime
license: OSI Approved
summary: 'Provides an object-oriented python interface to the netCDF version 4 library..'
cftime (1.0.0-2) UNRELEASED; urgency=medium
cftime (1.0.1-1) unstable; urgency=medium
* New upstream release.
* Bump Standards-Version to 4.2.0, no changes.
* Drop autopkgtests to test installability & module import.
* Add lintian override for testsuite-autopkgtest-missing.
* Update watch file to limit matches to archive path.
-- Bas Couwenberg <sebastic@debian.org> Thu, 05 Jul 2018 09:39:49 +0200
-- Bas Couwenberg <sebastic@debian.org> Thu, 16 Aug 2018 07:05:28 +0200
cftime (1.0.0-1) unstable; urgency=medium
......
cython
pytest
coveralls
pytest-cov
......@@ -33,6 +33,6 @@ setup(
packages=['cftime'],
version=extract_version(),
ext_modules=[Extension('cftime._cftime', sources=['cftime/_cftime.pyx'])],
setup_requires=install_requires,
setup_requires=['setuptools>=18.0', 'cython>=0.19'],
install_requires=install_requires,
tests_require=tests_require)
......@@ -12,10 +12,9 @@ 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'))
......@@ -1042,6 +1041,26 @@ class issue17TestCase(unittest.TestCase):
assert_equal(d, expected_parsed_date)
class issue57TestCase(unittest.TestCase):
"""Regression tests for issue #57."""
# issue 57: cftime._cftime._dateparse returns quite opaque error messages that make it difficult to
# track down the source of problem
def setUp(self):
pass
def test_parse_incorrect_unitstring(self):
for datestr in ("days since2017-05-01 ", "dayssince 2017-05-01 00:00", "days snce 2017-05-01 00:00", "days_since_2017-05-01 00:00",
"days_since_2017-05-01_00:00"):
self.assertRaises(
ValueError, cftime._cftime._dateparse, datestr)
self.assertRaises(
ValueError, cftime._cftime.num2date, 1, datestr)
self.assertRaises(
ValueError, cftime._cftime.date2num, datetime(1900, 1, 1, 0), datestr)
_DATE_TYPES = [DatetimeNoLeap, DatetimeAllLeap, DatetimeJulian, Datetime360Day,
DatetimeGregorian, DatetimeProlepticGregorian]
......