From d26b210b75c630fefb3e3b2ea24a50d938d967de Mon Sep 17 00:00:00 2001
From: Jeffrey Whitaker
Date: Wed, 31 Jan 2018 11:16:49 -0500
Subject: [PATCH 0001/1504] remove netcdftime
---
setup.py | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/setup.py b/setup.py
index 339432c2a..153f4f884 100644
--- a/setup.py
+++ b/setup.py
@@ -468,8 +468,6 @@ def _populate_hdf5_info(dirstosearch, inc_dirs, libs, lib_dirs):
cmdclass = {}
netcdf4_src_root = osp.join('netCDF4', '_netCDF4')
netcdf4_src_c = netcdf4_src_root + '.c'
-netcdftime_src_root = osp.join('netcdftime', '_netcdftime')
-netcdftime_src_c = netcdftime_src_root + '.c'
if 'sdist' not in sys.argv[1:] and 'clean' not in sys.argv[1:]:
sys.stdout.write('using Cython to compile netCDF4.pyx...\n')
# remove _netCDF4.c file if it exists, so cython will recompile _netCDF4.pyx.
@@ -478,9 +476,6 @@ def _populate_hdf5_info(dirstosearch, inc_dirs, libs, lib_dirs):
if len(sys.argv) >= 2:
if os.path.exists(netcdf4_src_c):
os.remove(netcdf4_src_c)
- # same for _netcdftime.c
- if os.path.exists(netcdftime_src_c):
- os.remove(netcdftime_src_c)
# this determines whether renameGroup and filepath methods will work.
has_rename_grp, has_nc_inq_path, has_nc_inq_format_extended, \
has_cdf5_format, has_nc_open_mem, has_nc_par = check_api(inc_dirs)
@@ -552,9 +547,7 @@ def _populate_hdf5_info(dirstosearch, inc_dirs, libs, lib_dirs):
libraries=libs,
library_dirs=lib_dirs,
include_dirs=inc_dirs + ['include'],
- runtime_library_dirs=runtime_lib_dirs),
- Extension('netcdftime._netcdftime',
- [netcdftime_src_root + '.pyx'])]
+ runtime_library_dirs=runtime_lib_dirs)]
else:
ext_modules = None
@@ -584,6 +577,6 @@ def _populate_hdf5_info(dirstosearch, inc_dirs, libs, lib_dirs):
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Archiving :: Compression",
"Operating System :: OS Independent"],
- packages=['netcdftime', 'netCDF4'],
+ packages=['netCDF4'],
ext_modules=ext_modules,
**setuptools_extra_kwargs)
From b89833095edc1f53a6da083690842cb7952bbf0c Mon Sep 17 00:00:00 2001
From: Jeffrey Whitaker
Date: Wed, 31 Jan 2018 11:17:11 -0500
Subject: [PATCH 0002/1504] remove netcdftime
---
netcdftime/__init__.py | 8 -
netcdftime/_netcdftime.pyx | 1705 ------------------------------------
2 files changed, 1713 deletions(-)
delete mode 100644 netcdftime/__init__.py
delete mode 100644 netcdftime/_netcdftime.pyx
diff --git a/netcdftime/__init__.py b/netcdftime/__init__.py
deleted file mode 100644
index 8693bb6a2..000000000
--- a/netcdftime/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from ._netcdftime import utime, JulianDayFromDate, DateFromJulianDay
-from ._netcdftime import _parse_date, date2index, time2index
-from ._netcdftime import DatetimeProlepticGregorian as datetime
-from ._netcdftime import DatetimeNoLeap, DatetimeAllLeap, Datetime360Day, DatetimeJulian, \
- DatetimeGregorian, DatetimeProlepticGregorian
-from ._netcdftime import microsec_units, millisec_units, \
- sec_units, hr_units, day_units, min_units
-from ._netcdftime import __version__
diff --git a/netcdftime/_netcdftime.pyx b/netcdftime/_netcdftime.pyx
deleted file mode 100644
index 5302684a8..000000000
--- a/netcdftime/_netcdftime.pyx
+++ /dev/null
@@ -1,1705 +0,0 @@
-"""
-Performs conversions of netCDF time coordinate data to/from datetime objects.
-"""
-
-from cpython.object cimport PyObject_RichCompare
-
-import numpy as np
-import math
-import numpy
-import re
-
-from datetime import datetime as real_datetime
-from datetime import timedelta
-import time # strftime
-
-try:
- from itertools import izip as zip
-except ImportError: # python 3.x
- pass
-
-microsec_units = ['microseconds','microsecond', 'microsec', 'microsecs']
-millisec_units = ['milliseconds', 'millisecond', 'millisec', 'millisecs']
-sec_units = ['second', 'seconds', 'sec', 'secs', 's']
-min_units = ['minute', 'minutes', 'min', 'mins']
-hr_units = ['hour', 'hours', 'hr', 'hrs', 'h']
-day_units = ['day', 'days', 'd']
-_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.4.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.
-# For example, the TZ spec "+01:0" will still work even though the minutes value is only one character long.
-ISO8601_REGEX = re.compile(r"(?P[+-]?[0-9]{1,4})(-(?P[0-9]{1,2})(-(?P[0-9]{1,2})"
- r"(((?P.)(?P[0-9]{1,2}):(?P[0-9]{1,2})(:(?P[0-9]{1,2})(\.(?P[0-9]+))?)?)?"
- r"((?P.?)(?PZ|(([-+])([0-9]{2})((:([0-9]{2}))|([0-9]{2}))?)))?)?)?)?"
- )
-# Note: The re module apparently does not support branch reset groups that allow
-# redifinition of the same group name in alternative branches as PCRE does.
-# Using two different group names is also somewhat ugly, but other solutions might
-# hugely inflate the expression. feel free to contribute a better solution.
-TIMEZONE_REGEX = re.compile(
- "(?P[+-])(?P[0-9]{2})(?:(?::(?P[0-9]{2}))|(?P[0-9]{2}))?")
-
-def JulianDayFromDate(date, calendar='standard'):
- """
-
- creates a Julian Day from a 'datetime-like' object. Returns the fractional
- Julian Day (approximately millisecond accuracy).
-
- if calendar='standard' or 'gregorian' (default), Julian day follows Julian
- Calendar on and before 1582-10-5, Gregorian calendar after 1582-10-15.
-
- if calendar='proleptic_gregorian', Julian Day follows gregorian calendar.
-
- if calendar='julian', Julian Day follows julian calendar.
-
- Algorithm:
-
- Meeus, Jean (1998) Astronomical Algorithms (2nd Edition). Willmann-Bell,
- Virginia. p. 63
-
- """
-
- # based on redate.py by David Finlayson.
-
- # check if input was scalar and change return accordingly
- isscalar = False
- try:
- date[0]
- except:
- isscalar = True
-
- date = np.atleast_1d(np.array(date))
- year = np.empty(len(date), dtype=np.int32)
- month = year.copy()
- day = year.copy()
- hour = year.copy()
- minute = year.copy()
- second = year.copy()
- microsecond = year.copy()
- for i, d in enumerate(date):
- year[i] = d.year
- month[i] = d.month
- day[i] = d.day
- hour[i] = d.hour
- minute[i] = d.minute
- second[i] = d.second
- microsecond[i] = d.microsecond
- # convert years in BC era to astronomical years (so that 1 BC is year zero)
- # (fixes issue #596)
- year[year < 0] = year[year < 0] + 1
- # Convert time to fractions of a day
- day = day + hour / 24.0 + minute / 1440.0 + (second + microsecond/1.e6) / 86400.0
-
- # Start Meeus algorithm (variables are in his notation)
- month_lt_3 = month < 3
- month[month_lt_3] = month[month_lt_3] + 12
- year[month_lt_3] = year[month_lt_3] - 1
-
- # MC - assure array
- # A = np.int64(year / 100)
- A = (year / 100).astype(np.int64)
-
- # MC
- # jd = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + \
- # day - 1524.5
- jd = 365. * year + np.int32(0.25 * year + 2000.) + np.int32(30.6001 * (month + 1)) + \
- day + 1718994.5
-
- # optionally adjust the jd for the switch from
- # the Julian to Gregorian Calendar
- # here assumed to have occurred the day after 1582 October 4
- if calendar in ['standard', 'gregorian']:
- # MC - do not have to be contiguous dates
- # if np.min(jd) >= 2299170.5:
- # # 1582 October 15 (Gregorian Calendar)
- # B = 2 - A + np.int32(A / 4)
- # elif np.max(jd) < 2299160.5:
- # # 1582 October 5 (Julian Calendar)
- # B = np.zeros(len(jd))
- # else:
- # print(date, calendar, jd)
- # raise ValueError(
- # 'impossible date (falls in gap between end of Julian calendar and beginning of Gregorian calendar')
- if np.any((jd >= 2299160.5) & (jd < 2299170.5)): # missing days in Gregorian calendar
- raise ValueError(
- 'impossible date (falls in gap between end of Julian calendar and beginning of Gregorian calendar')
- B = np.zeros(len(jd)) # 1582 October 5 (Julian Calendar)
- ii = np.where(jd >= 2299170.5)[0] # 1582 October 15 (Gregorian Calendar)
- if ii.size>0:
- B[ii] = 2 - A[ii] + np.int32(A[ii] / 4)
- elif calendar == 'proleptic_gregorian':
- B = 2 - A + np.int32(A / 4)
- elif calendar == 'julian':
- B = np.zeros(len(jd))
- else:
- raise ValueError(
- 'unknown calendar, must be one of julian,standard,gregorian,proleptic_gregorian, got %s' % calendar)
-
- # adjust for Julian calendar if necessary
- jd = jd + B
-
- # 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).
- eps = np.finfo(float).eps
- eps = np.maximum(eps*jd, eps)
- jd += eps
-
- if isscalar:
- return jd[0]
- else:
- return jd
-
-
-cdef _NoLeapDayFromDate(date):
- """
-
-creates a Julian Day for a calendar with no leap years from a datetime
-instance. Returns the fractional Julian Day (approximately millisecond accuracy).
-
- """
-
- year = date.year
- month = date.month
- day = date.day
- hour = date.hour
- minute = date.minute
- second = date.second
- microsecond = date.microsecond
- # Convert time to fractions of a day
- day = day + hour / 24.0 + minute / 1440.0 + (second + microsecond/1.e6) / 86400.0
-
- # Start Meeus algorithm (variables are in his notation)
- if (month < 3):
- month = month + 12
- year = year - 1
-
- jd = int(365. * (year + 4716)) + int(30.6001 * (month + 1)) + \
- day - 1524.5
-
- return jd
-
-
-cdef _AllLeapFromDate(date):
- """
-
-creates a Julian Day for a calendar where all years have 366 days from
-a 'datetime-like' object.
-Returns the fractional Julian Day (approximately millisecond accuracy).
-
- """
-
- year = date.year
- month = date.month
- day = date.day
- hour = date.hour
- minute = date.minute
- second = date.second
- microsecond = date.microsecond
- # Convert time to fractions of a day
- day = day + hour / 24.0 + minute / 1440.0 + (second + microsecond/1.e6) / 86400.0
-
- # Start Meeus algorithm (variables are in his notation)
- if (month < 3):
- month = month + 12
- year = year - 1
-
- jd = int(366. * (year + 4716)) + int(30.6001 * (month + 1)) + \
- day - 1524.5
-
- return jd
-
-
-cdef _360DayFromDate(date):
- """
-
-creates a Julian Day for a calendar where all months have 30 days from
-a 'datetime-like' object.
-Returns the fractional Julian Day (approximately millisecond accuracy).
-
- """
-
- year = date.year
- month = date.month
- day = date.day
- hour = date.hour
- minute = date.minute
- second = date.second
- microsecond = date.microsecond
- # Convert time to fractions of a day
- day = day + hour / 24.0 + minute / 1440.0 + (second + microsecond/1.e6) / 86400.0
-
- jd = int(360. * (year + 4716)) + int(30. * (month - 1)) + day
-
- return jd
-
-
-def DateFromJulianDay(JD, calendar='standard'):
- """
-
- returns a 'datetime-like' object given Julian Day. Julian Day is a
- fractional day with approximately millisecond accuracy.
-
- if calendar='standard' or 'gregorian' (default), Julian day follows Julian
- Calendar on and before 1582-10-5, Gregorian calendar after 1582-10-15.
-
- if calendar='proleptic_gregorian', Julian Day follows gregorian calendar.
-
- if calendar='julian', Julian Day follows julian calendar.
-
- The datetime object is a 'real' datetime object if the date falls in
- the Gregorian calendar (i.e. calendar='proleptic_gregorian', or
- calendar = 'standard'/'gregorian' and the date is after 1582-10-15).
- Otherwise, it's a 'phony' datetime object which is actually an instance
- of netcdftime.datetime.
-
-
- Algorithm:
-
- Meeus, Jean (1998) Astronomical Algorithms (2nd Edition). Willmann-Bell,
- Virginia. p. 63
- """
-
- # based on redate.py by David Finlayson.
-
- julian = np.array(JD, dtype=float)
-
- if np.min(julian) < 0:
- raise ValueError('Julian Day must be positive')
-
- dayofwk = np.atleast_1d(np.int32(np.fmod(np.int32(julian + 1.5), 7)))
- # get the day (Z) and the fraction of the day (F)
- # add 0.000005 which is 452 ms in case of jd being after
- # second 23:59:59 of a day we want to round to the next day see issue #75
- Z = np.atleast_1d(np.int32(np.round(julian)))
- F = np.atleast_1d(julian + 0.5 - Z).astype(np.float64)
- if calendar in ['standard', 'gregorian']:
- # MC
- # alpha = int((Z - 1867216.25)/36524.25)
- # A = Z + 1 + alpha - int(alpha/4)
- alpha = np.int32(((Z - 1867216.) - 0.25) / 36524.25)
- A = Z + 1 + alpha - np.int32(0.25 * alpha)
- # check if dates before oct 5th 1582 are in the array
- ind_before = np.where(julian < 2299160.5)[0]
- if len(ind_before) > 0:
- A[ind_before] = Z[ind_before]
-
- elif calendar == 'proleptic_gregorian':
- # MC
- # alpha = int((Z - 1867216.25)/36524.25)
- # A = Z + 1 + alpha - int(alpha/4)
- alpha = np.int32(((Z - 1867216.) - 0.25) / 36524.25)
- A = Z + 1 + alpha - np.int32(0.25 * alpha)
- elif calendar == 'julian':
- A = Z
- else:
- raise ValueError(
- 'unknown calendar, must be one of julian,standard,gregorian,proleptic_gregorian, got %s' % calendar)
-
- B = A + 1524
- # MC
- # C = int((B - 122.1)/365.25)
- # D = int(365.25 * C)
- C = np.atleast_1d(np.int32(6680. + ((B - 2439870.) - 122.1) / 365.25))
- D = np.atleast_1d(np.int32(365 * C + np.int32(0.25 * C)))
- E = np.atleast_1d(np.int32((B - D) / 30.6001))
-
- # Convert to date
- day = np.clip(B - D - np.int64(30.6001 * E) + F, 1, None)
- nday = B - D - 123
- dayofyr = nday - 305
- ind_nday_before = np.where(nday <= 305)[0]
- if len(ind_nday_before) > 0:
- dayofyr[ind_nday_before] = nday[ind_nday_before] + 60
- # MC
- # if E < 14:
- # month = E - 1
- # else:
- # month = E - 13
-
- # if month > 2:
- # year = C - 4716
- # else:
- # year = C - 4715
- month = E - 1
- month[month > 12] = month[month > 12] - 12
- year = C - 4715
- year[month > 2] = year[month > 2] - 1
- year[year <= 0] = year[year <= 0] - 1
-
- # a leap year?
- leap = np.zeros(len(year),dtype=dayofyr.dtype)
- leap[year % 4 == 0] = 1
- if calendar == 'proleptic_gregorian':
- leap[(year % 100 == 0) & (year % 400 != 0)] = 0
- elif calendar in ['standard', 'gregorian']:
- leap[(year % 100 == 0) & (year % 400 != 0) & (julian < 2299160.5)] = 0
-
- inc_idx = np.where((leap == 1) & (month > 2))[0]
- dayofyr[inc_idx] = dayofyr[inc_idx] + leap[inc_idx]
-
- # Subtract the offset from JulianDayFromDate from the microseconds (pull
- # request #433).
- eps = np.finfo(float).eps
- eps = np.maximum(eps*julian, eps)
- 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.
- microsecond = np.clip(microsecond - eps*86400.*1e6, 0, 999999)
-
- # convert year, month, day, hour, minute, second to int32
- year = year.astype(np.int32)
- month = month.astype(np.int32)
- day = day.astype(np.int32)
- hour = hour.astype(np.int32)
- minute = minute.astype(np.int32)
- second = second.astype(np.int32)
- microsecond = microsecond.astype(np.int32)
-
- # check if input was scalar and change return accordingly
- isscalar = False
- try:
- JD[0]
- except:
- isscalar = True
-
- if calendar in 'proleptic_gregorian':
- # FIXME: datetime.datetime does not support years < 1
- if (year < 0).any():
- datetime_type = DatetimeProlepticGregorian
- else:
- datetime_type = real_datetime
- elif calendar in ('standard', 'gregorian'):
- # return a 'real' datetime instance if calendar is proleptic
- # Gregorian or Gregorian and all dates are after the
- # Julian/Gregorian transition
- if len(ind_before) == 0:
- datetime_type = real_datetime
- else:
- datetime_type = DatetimeGregorian
- elif calendar == "julian":
- datetime_type = DatetimeJulian
- else:
- raise ValueError("unsupported calendar: {0}".format(calendar))
-
- if not isscalar:
- return np.array([datetime_type(*args)
- for args in
- zip(year, month, day, hour, minute, second,
- microsecond)])
-
- else:
- return datetime_type(year[0], month[0], day[0], hour[0],
- minute[0], second[0], microsecond[0])
-
-
-cdef _DateFromNoLeapDay(JD):
- """
-
-returns a 'datetime-like' object given Julian Day for a calendar with no leap
-days. Julian Day is a fractional day with approximately millisecond accuracy.
-
- """
-
- # based on redate.py by David Finlayson.
-
- if JD < 0:
- year_offset = int(-JD) // 365 + 1
- JD += year_offset * 365
- else:
- year_offset = 0
-
- dayofwk = int(math.fmod(int(JD + 1.5), 7))
- (F, Z) = math.modf(JD + 0.5)
- Z = int(Z)
- A = Z
- B = A + 1524
- C = int((B - 122.1) / 365.)
- D = int(365. * C)
- E = int((B - D) / 30.6001)
-
- # Convert to date
- day = B - D - int(30.6001 * E) + F
- nday = B - D - 123
- if nday <= 305:
- dayofyr = nday + 60
- else:
- dayofyr = nday - 305
- if E < 14:
- month = E - 1
- else:
- month = E - 13
-
- if month > 2:
- year = C - 4716
- else:
- year = C - 4715
-
- # Convert fractions of a day to time
- (dfrac, days) = math.modf(day / 1.0)
- (hfrac, hours) = math.modf(dfrac * 24.0)
- (mfrac, minutes) = math.modf(hfrac * 60.0)
- (sfrac, seconds) = math.modf(mfrac * 60.0)
- microseconds = sfrac*1.e6
-
- if year_offset > 0:
- # correct dayofwk
-
- # 365 mod 7 = 1, so the day of the week changes by one day for
- # every year in year_offset
- dayofwk -= int(math.fmod(year_offset, 7))
-
- if dayofwk < 0:
- dayofwk += 7
-
- return DatetimeNoLeap(year - year_offset, month, int(days), int(hours), int(minutes),
- int(seconds), int(microseconds),dayofwk, dayofyr)
-
-
-cdef _DateFromAllLeap(JD):
- """
-
-returns a 'datetime-like' object given Julian Day for a calendar where all
-years have 366 days.
-Julian Day is a fractional day with approximately millisecond accuracy.
-
- """
-
- # based on redate.py by David Finlayson.
-
- if JD < 0:
- raise ValueError('Julian Day must be positive')
-
- dayofwk = int(math.fmod(int(JD + 1.5), 7))
- (F, Z) = math.modf(JD + 0.5)
- Z = int(Z)
- A = Z
- B = A + 1524
- C = int((B - 122.1) / 366.)
- D = int(366. * C)
- E = int((B - D) / 30.6001)
-
- # Convert to date
- day = B - D - int(30.6001 * E) + F
- nday = B - D - 123
- if nday <= 305:
- dayofyr = nday + 60
- else:
- dayofyr = nday - 305
- if E < 14:
- month = E - 1
- else:
- month = E - 13
- if month > 2:
- dayofyr = dayofyr + 1
-
- if month > 2:
- year = C - 4716
- else:
- year = C - 4715
-
- # Convert fractions of a day to time
- (dfrac, days) = math.modf(day / 1.0)
- (hfrac, hours) = math.modf(dfrac * 24.0)
- (mfrac, minutes) = math.modf(hfrac * 60.0)
- (sfrac, seconds) = math.modf(mfrac * 60.0)
- microseconds = sfrac*1.e6
-
- return DatetimeAllLeap(year, month, int(days), int(hours), int(minutes),
- int(seconds), int(microseconds),dayofwk, dayofyr)
-
-
-cdef _DateFrom360Day(JD):
- """
-
-returns a 'datetime-like' object given Julian Day for a calendar where all
-months have 30 days.
-Julian Day is a fractional day with approximately millisecond accuracy.
-
- """
-
- if JD < 0:
- year_offset = int(-JD) // 360 + 1
- JD += year_offset * 360
- else:
- year_offset = 0
-
- #jd = int(360. * (year + 4716)) + int(30. * (month - 1)) + day
- (F, Z) = math.modf(JD)
- year = int((Z - 0.5) / 360.) - 4716
- dayofyr = Z - (year + 4716) * 360
- month = int((dayofyr - 0.5) / 30) + 1
- day = dayofyr - (month - 1) * 30 + F
-
- # Convert fractions of a day to time
- (dfrac, days) = math.modf(day / 1.0)
- (hfrac, hours) = math.modf(dfrac * 24.0)
- (mfrac, minutes) = math.modf(hfrac * 60.0)
- (sfrac, seconds) = math.modf(mfrac * 60.0)
- microseconds = sfrac*1.e6
-
- return Datetime360Day(year - year_offset, month, int(days), int(hours), int(minutes),
- int(seconds), int(microseconds), -1, dayofyr)
-
-
-cdef _dateparse(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()
- 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())
- return units, utc_offset, datetime(year, month, day, hour, minute, second)
-
-
-class utime:
-
- """
-Performs conversions of netCDF time coordinate
-data to/from datetime objects.
-
-To initialize: C{t = utime(unit_string,calendar='standard')}
-
-where
-
-B{C{unit_string}} is a string of the form
-C{'time-units since '} defining the time units.
-
-Valid time-units are days, hours, minutes and seconds (the singular forms
-are also accepted). An example unit_string would be C{'hours
-since 0001-01-01 00:00:00'}.
-
-The B{C{calendar}} keyword describes the calendar used in the time calculations.
-All the values currently defined in the U{CF metadata convention
-}
-are accepted. The default is C{'standard'}, which corresponds to the mixed
-Gregorian/Julian calendar used by the C{udunits library}. Valid calendars
-are:
-
-C{'gregorian'} or C{'standard'} (default):
-
-Mixed Gregorian/Julian calendar as defined by udunits.
-
-C{'proleptic_gregorian'}:
-
-A Gregorian calendar extended to dates before 1582-10-15. That is, a year
-is a leap year if either (i) it is divisible by 4 but not by 100 or (ii)
-it is divisible by 400.
-
-C{'noleap'} or C{'365_day'}:
-
-Gregorian calendar without leap years, i.e., all years are 365 days long.
-all_leap or 366_day Gregorian calendar with every year being a leap year,
-i.e., all years are 366 days long.
-
-C{'360_day'}:
-
-All years are 360 days divided into 30 day months.
-
-C{'julian'}:
-
-Proleptic Julian calendar, extended to dates after 1582-10-5. A year is a
-leap year if it is divisible by 4.
-
-The C{L{num2date}} and C{L{date2num}} class methods can used to convert datetime
-instances to/from the specified time units using the specified calendar.
-
-The datetime instances returned by C{num2date} are 'real' python datetime
-objects if the date falls in the Gregorian calendar (i.e.
-C{calendar='proleptic_gregorian', 'standard'} or C{'gregorian'} and
-the date is after 1582-10-15). Otherwise, they are 'phony' datetime
-objects which are actually instances of C{L{netcdftime.datetime}}. This is
-because the python datetime module cannot handle the weird dates in some
-calendars (such as C{'360_day'} and C{'all_leap'}) which don't exist in any real
-world calendar.
-
-
-Example usage:
-
->>> from netcdftime import utime
->>> from datetime import datetime
->>> cdftime = utime('hours since 0001-01-01 00:00:00')
->>> date = datetime.now()
->>> print date
-2016-10-05 08:46:27.245015
->>>
->>> t = cdftime.date2num(date)
->>> print t
-17669840.7742
->>>
->>> date = cdftime.num2date(t)
->>> print date
-2016-10-05 08:46:27.244996
->>>
-
-The resolution of the transformation operation is approximately a millisecond.
-
-Warning: Dates between 1582-10-5 and 1582-10-15 do not exist in the
-C{'standard'} or C{'gregorian'} calendars. An exception will be raised if you pass
-a 'datetime-like' object in that range to the C{L{date2num}} class method.
-
-Words of Wisdom from the British MetOffice concerning reference dates:
-
-"udunits implements the mixed Gregorian/Julian calendar system, as
-followed in England, in which dates prior to 1582-10-15 are assumed to use
-the Julian calendar. Other software cannot be relied upon to handle the
-change of calendar in the same way, so for robustness it is recommended
-that the reference date be later than 1582. If earlier dates must be used,
-it should be noted that udunits treats 0 AD as identical to 1 AD."
-
-@ivar origin: datetime instance defining the origin of the netCDF time variable.
-@ivar calendar: the calendar used (as specified by the C{calendar} keyword).
-@ivar unit_string: a string defining the the netCDF time variable.
-@ivar units: the units part of C{unit_string} (i.e. 'days', 'hours', 'seconds').
- """
-
- def __init__(self, unit_string, calendar='standard'):
- """
-@param unit_string: a string of the form
-C{'time-units since '} defining the time units.
-
-Valid time-units are days, hours, minutes and seconds (the singular forms
-are also accepted). An example unit_string would be C{'hours
-since 0001-01-01 00:00:00'}.
-
-@keyword calendar: describes the calendar used in the time calculations.
-All the values currently defined in the U{CF metadata convention
-}
-are accepted. The default is C{'standard'}, which corresponds to the mixed
-Gregorian/Julian calendar used by the C{udunits library}. Valid calendars
-are:
- - C{'gregorian'} or C{'standard'} (default):
- Mixed Gregorian/Julian calendar as defined by udunits.
- - C{'proleptic_gregorian'}:
- A Gregorian calendar extended to dates before 1582-10-15. That is, a year
- is a leap year if either (i) it is divisible by 4 but not by 100 or (ii)
- it is divisible by 400.
- - C{'noleap'} or C{'365_day'}:
- Gregorian calendar without leap years, i.e., all years are 365 days long.
- - C{'all_leap'} or C{'366_day'}:
- Gregorian calendar with every year being a leap year, i.e.,
- all years are 366 days long.
- -C{'360_day'}:
- All years are 360 days divided into 30 day months.
- -C{'julian'}:
- Proleptic Julian calendar, extended to dates after 1582-10-5. A year is a
- leap year if it is divisible by 4.
-
-@returns: A class instance which may be used for converting times from netCDF
-units to datetime objects.
- """
- calendar = calendar.lower()
- if calendar in _calendars:
- self.calendar = calendar
- else:
- raise ValueError(
- "calendar must be one of %s, got '%s'" % (str(_calendars), calendar))
- units, tzoffset, self.origin = _dateparse(unit_string)
- # real-world calendars limited to positive reference years.
- if self.calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
- if self.origin.year == 0:
- msg='zero not allowed as a reference year, does not exist in Julian or Gregorian calendars'
- raise ValueError(msg)
- elif self.origin.year < 0:
- msg='negative reference year in time units, must be >= 1'
- raise ValueError(msg)
- self.tzoffset = tzoffset # time zone offset in minutes
- self.units = units
- self.unit_string = unit_string
- if self.calendar in ['noleap', '365_day'] and self.origin.month == 2 and self.origin.day == 29:
- raise ValueError(
- 'cannot specify a leap day as the reference time with the noleap calendar')
- if self.calendar == '360_day' and self.origin.day > 30:
- raise ValueError(
- 'there are only 30 days in every month with the 360_day calendar')
- if self.calendar in ['noleap', '365_day']:
- self._jd0 = _NoLeapDayFromDate(self.origin)
- elif self.calendar in ['all_leap', '366_day']:
- self._jd0 = _AllLeapFromDate(self.origin)
- elif self.calendar == '360_day':
- self._jd0 = _360DayFromDate(self.origin)
- else:
- self._jd0 = JulianDayFromDate(self.origin, calendar=self.calendar)
-
- def date2num(self, date):
- """
- Returns C{time_value} in units described by L{unit_string}, using
- the specified L{calendar}, given a 'datetime-like' object.
-
- The datetime object must represent UTC with no time-zone offset.
- If there is a time-zone offset implied by L{unit_string}, it will
- be applied to the returned numeric values.
-
- Resolution is approximately a millisecond.
-
- If C{calendar = 'standard'} or C{'gregorian'} (indicating
- that the mixed Julian/Gregorian calendar is to be used), an
- exception will be raised if the 'datetime-like' object describes
- a date between 1582-10-5 and 1582-10-15.
-
- Works for scalars, sequences and numpy arrays.
- Returns a scalar if input is a scalar, else returns a numpy array.
- """
- isscalar = False
- try:
- date[0]
- except:
- isscalar = True
- if not isscalar:
- date = numpy.array(date)
- shape = date.shape
- if self.calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
- if isscalar:
- jdelta = JulianDayFromDate(date, self.calendar) - self._jd0
- else:
- jdelta = JulianDayFromDate(
- date.flat, self.calendar) - self._jd0
- elif self.calendar in ['noleap', '365_day']:
- if isscalar:
- if date.month == 2 and date.day == 29:
- raise ValueError(
- 'there is no leap day in the noleap calendar')
- jdelta = _NoLeapDayFromDate(date) - self._jd0
- else:
- jdelta = []
- for d in date.flat:
- if d.month == 2 and d.day == 29:
- raise ValueError(
- 'there is no leap day in the noleap calendar')
- jdelta.append(_NoLeapDayFromDate(d) - self._jd0)
- elif self.calendar in ['all_leap', '366_day']:
- if isscalar:
- jdelta = _AllLeapFromDate(date) - self._jd0
- else:
- jdelta = [_AllLeapFromDate(d) - self._jd0 for d in date.flat]
- elif self.calendar == '360_day':
- if isscalar:
- if date.day > 30:
- raise ValueError(
- 'there are only 30 days in every month with the 360_day calendar')
- jdelta = _360DayFromDate(date) - self._jd0
- else:
- jdelta = []
- for d in date.flat:
- if d.day > 30:
- raise ValueError(
- 'there are only 30 days in every month with the 360_day calendar')
- jdelta.append(_360DayFromDate(d) - self._jd0)
- if not isscalar:
- jdelta = numpy.array(jdelta)
- # convert to desired units, add time zone offset.
- if self.units in microsec_units:
- jdelta = jdelta * 86400. * 1.e6 + self.tzoffset * 60. * 1.e6
- elif self.units in millisec_units:
- jdelta = jdelta * 86400. * 1.e3 + self.tzoffset * 60. * 1.e3
- elif self.units in sec_units:
- jdelta = jdelta * 86400. + self.tzoffset * 60.
- elif self.units in min_units:
- jdelta = jdelta * 1440. + self.tzoffset
- elif self.units in hr_units:
- jdelta = jdelta * 24. + self.tzoffset / 60.
- elif self.units in day_units:
- jdelta = jdelta + self.tzoffset / 1440.
- else:
- raise ValueError('unsupported time units')
- if isscalar:
- return jdelta
- else:
- return numpy.reshape(jdelta, shape)
-
- def num2date(self, time_value):
- """
- Return a 'datetime-like' object given a C{time_value} in units
- described by L{unit_string}, using L{calendar}.
-
- dates are in UTC with no offset, even if L{unit_string} contains
- a time zone offset from UTC.
-
- Resolution is approximately a millisecond.
-
- Works for scalars, sequences and numpy arrays.
- Returns a scalar if input is a scalar, else returns a numpy array.
-
- The datetime instances returned by C{num2date} are 'real' python datetime
- objects if the date falls in the Gregorian calendar (i.e.
- C{calendar='proleptic_gregorian'}, or C{calendar = 'standard'/'gregorian'} and
- the date is after 1582-10-15). Otherwise, they are 'phony' datetime
- objects which are actually instances of netcdftime.datetime. This is
- because the python datetime module cannot handle the weird dates in some
- calendars (such as C{'360_day'} and C{'all_leap'}) which
- do not exist in any real world calendar.
- """
- isscalar = False
- try:
- time_value[0]
- except:
- isscalar = True
- ismasked = False
- if hasattr(time_value, 'mask'):
- mask = time_value.mask
- ismasked = True
- if not isscalar:
- time_value = numpy.array(time_value, dtype='d')
- shape = time_value.shape
- # convert to desired units, subtract time zone offset.
- if self.units in microsec_units:
- jdelta = time_value / 86400000000. - self.tzoffset / 1440.
- elif self.units in millisec_units:
- jdelta = time_value / 86400000. - self.tzoffset / 1440.
- elif self.units in sec_units:
- jdelta = time_value / 86400. - self.tzoffset / 1440.
- elif self.units in min_units:
- jdelta = time_value / 1440. - self.tzoffset / 1440.
- elif self.units in hr_units:
- jdelta = time_value / 24. - self.tzoffset / 1440.
- elif self.units in day_units:
- jdelta = time_value - self.tzoffset / 1440.
- else:
- raise ValueError('unsupported time units')
- jd = self._jd0 + jdelta
- if self.calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
- if not isscalar:
- if ismasked:
- date = []
- for j, m in zip(jd.flat, mask.flat):
- if not m:
- date.append(DateFromJulianDay(j, self.calendar))
- else:
- date.append(None)
- else:
- date = DateFromJulianDay(jd.flat, self.calendar)
- else:
- if ismasked and mask.item():
- date = None
- else:
- date = DateFromJulianDay(jd, self.calendar)
- elif self.calendar in ['noleap', '365_day']:
- if not isscalar:
- date = [_DateFromNoLeapDay(j) for j in jd.flat]
- else:
- date = _DateFromNoLeapDay(jd)
- elif self.calendar in ['all_leap', '366_day']:
- if not isscalar:
- date = [_DateFromAllLeap(j) for j in jd.flat]
- else:
- date = _DateFromAllLeap(jd)
- elif self.calendar == '360_day':
- if not isscalar:
- date = [_DateFrom360Day(j) for j in jd.flat]
- else:
- date = _DateFrom360Day(jd)
- if isscalar:
- return date
- else:
- return numpy.reshape(numpy.array(date), shape)
-
-
-cdef _parse_timezone(tzstring):
- """Parses ISO 8601 time zone specs into tzinfo offsets
-
- Adapted from pyiso8601 (http://code.google.com/p/pyiso8601/)
- """
- if tzstring == "Z":
- return 0
- # This isn't strictly correct, but it's common to encounter dates without
- # time zones so I'll assume the default (which defaults to UTC).
- if tzstring is None:
- return 0
- m = TIMEZONE_REGEX.match(tzstring)
- prefix, hours, minutes1, minutes2 = m.groups()
- hours = int(hours)
-# Note: Minutes don't have to be specified in tzstring,
-# so if the group is not found it means minutes is 0.
-# Also, due to the timezone regex definition, there are two mutually
-# exclusive groups that might hold the minutes value, so check both.
- minutes = int(minutes1) if minutes1 is not None else int(minutes2) if minutes2 is not None else 0
- if prefix == "-":
- hours = -hours
- minutes = -minutes
- return minutes + hours * 60.
-
-
-cpdef _parse_date(datestring):
- """Parses ISO 8601 dates into datetime objects
-
- The timezone is parsed from the date string, assuming UTC
- by default.
-
- Adapted from pyiso8601 (http://code.google.com/p/pyiso8601/)
- """
- if not isinstance(datestring, str) and not isinstance(datestring, unicode):
- raise ValueError("Expecting a string %r" % datestring)
- m = ISO8601_REGEX.match(datestring.strip())
- if not m:
- raise ValueError("Unable to parse date string %r" % datestring)
- groups = m.groupdict()
- tzoffset_mins = _parse_timezone(groups["timezone"])
- if groups["hour"] is None:
- groups["hour"] = 0
- if groups["minute"] is None:
- groups["minute"] = 0
- if groups["second"] is None:
- groups["second"] = 0
- # if groups["fraction"] is None:
- # groups["fraction"] = 0
- # else:
- # groups["fraction"] = int(float("0.%s" % groups["fraction"]) * 1e6)
- iyear = int(groups["year"])
- return iyear, int(groups["month"]), int(groups["day"]),\
- int(groups["hour"]), int(groups["minute"]), int(groups["second"]),\
- tzoffset_mins
-
-cdef _check_index(indices, times, nctime, calendar, select):
- """Return True if the time indices given correspond to the given times,
- False otherwise.
-
- Parameters:
-
- indices : sequence of integers
- Positive integers indexing the time variable.
-
- times : sequence of times.
- Reference times.
-
- nctime : netCDF Variable object
- NetCDF time object.
-
- calendar : string
- Calendar of nctime.
-
- select : string
- Index selection method.
- """
- N = nctime.shape[0]
- if (indices < 0).any():
- return False
-
- if (indices >= N).any():
- return False
-
- try:
- t = nctime[indices]
- nctime = nctime
- # WORKAROUND TO CHANGES IN SLICING BEHAVIOUR in 1.1.2
- # this may be unacceptably slow...
- # if indices are unsorted, or there are duplicate
- # values in indices, read entire time variable into numpy
- # array so numpy slicing rules can be used.
- except IndexError:
- nctime = nctime[:]
- t = nctime[indices]
-# if fancy indexing not available, fall back on this.
-# t=[]
-# for ind in indices:
-# t.append(nctime[ind])
-
- if select == 'exact':
- return numpy.all(t == times)
-
- elif select == 'before':
- ta = nctime[numpy.clip(indices + 1, 0, N - 1)]
- return numpy.all(t <= times) and numpy.all(ta > times)
-
- elif select == 'after':
- tb = nctime[numpy.clip(indices - 1, 0, N - 1)]
- return numpy.all(t >= times) and numpy.all(tb < times)
-
- elif select == 'nearest':
- ta = nctime[numpy.clip(indices + 1, 0, N - 1)]
- tb = nctime[numpy.clip(indices - 1, 0, N - 1)]
- delta_after = ta - t
- delta_before = t - tb
- delta_check = numpy.abs(times - t)
- return numpy.all(delta_check <= delta_after) and numpy.all(delta_check <= delta_before)
-
-
-def date2index(dates, nctime, calendar=None, select='exact'):
- """
- date2index(dates, nctime, calendar=None, select='exact')
-
- Return indices of a netCDF time variable corresponding to the given dates.
-
- @param dates: A datetime object or a sequence of datetime objects.
- The datetime objects should not include a time-zone offset.
-
- @param nctime: A netCDF time variable object. The nctime object must have a
- C{units} attribute. The entries are assumed to be stored in increasing
- order.
-
- @param calendar: Describes the calendar used in the time calculation.
- Valid calendars C{'standard', 'gregorian', 'proleptic_gregorian'
- 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'}.
- Default is C{'standard'}, which is a mixed Julian/Gregorian calendar
- If C{calendar} is None, its value is given by C{nctime.calendar} or
- C{standard} if no such attribute exists.
-
- @param select: C{'exact', 'before', 'after', 'nearest'}
- The index selection method. C{exact} will return the indices perfectly
- matching the dates given. C{before} and C{after} will return the indices
- corresponding to the dates just before or just after the given dates if
- an exact match cannot be found. C{nearest} will return the indices that
- correspond to the closest dates.
- """
- try:
- nctime.units
- except AttributeError:
- raise AttributeError("netcdf time variable is missing a 'units' attribute")
- # Setting the calendar.
- if calendar == None:
- calendar = getattr(nctime, 'calendar', 'standard')
- cdftime = utime(nctime.units,calendar=calendar)
- times = cdftime.date2num(dates)
- return time2index(times, nctime, calendar=calendar, select=select)
-
-
-def time2index(times, nctime, calendar=None, select='exact'):
- """
- time2index(times, nctime, calendar=None, select='exact')
-
- Return indices of a netCDF time variable corresponding to the given times.
-
- @param times: A numeric time or a sequence of numeric times.
-
- @param nctime: A netCDF time variable object. The nctime object must have a
- C{units} attribute. The entries are assumed to be stored in increasing
- order.
-
- @param calendar: Describes the calendar used in the time calculation.
- Valid calendars C{'standard', 'gregorian', 'proleptic_gregorian'
- 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'}.
- Default is C{'standard'}, which is a mixed Julian/Gregorian calendar
- If C{calendar} is None, its value is given by C{nctime.calendar} or
- C{standard} if no such attribute exists.
-
- @param select: C{'exact', 'before', 'after', 'nearest'}
- The index selection method. C{exact} will return the indices perfectly
- matching the times given. C{before} and C{after} will return the indices
- corresponding to the times just before or just after the given times if
- an exact match cannot be found. C{nearest} will return the indices that
- correspond to the closest times.
- """
- try:
- nctime.units
- except AttributeError:
- raise AttributeError("netcdf time variable is missing a 'units' attribute")
- # Setting the calendar.
- if calendar == None:
- calendar = getattr(nctime, 'calendar', 'standard')
-
- num = numpy.atleast_1d(times)
- N = len(nctime)
-
- # Trying to infer the correct index from the starting time and the stride.
- # This assumes that the times are increasing uniformly.
- if len(nctime) >= 2:
- t0, t1 = nctime[:2]
- dt = t1 - t0
- else:
- t0 = nctime[0]
- dt = 1.
- if select in ['exact', 'before']:
- index = numpy.array((num - t0) / dt, int)
- elif select == 'after':
- index = numpy.array(numpy.ceil((num - t0) / dt), int)
- else:
- index = numpy.array(numpy.around((num - t0) / dt), int)
-
- # Checking that the index really corresponds to the given time.
- # If the times do not correspond, then it means that the times
- # are not increasing uniformly and we try the bisection method.
- if not _check_index(index, times, nctime, calendar, select):
-
- # Use the bisection method. Assumes nctime is ordered.
- import bisect
- index = numpy.array([bisect.bisect_right(nctime, n) for n in num], int)
- before = index == 0
-
- index = numpy.array([bisect.bisect_left(nctime, n) for n in num], int)
- after = index == N
-
- if select in ['before', 'exact'] and numpy.any(before):
- raise ValueError(
- 'Some of the times given are before the first time in `nctime`.')
-
- if select in ['after', 'exact'] and numpy.any(after):
- raise ValueError(
- 'Some of the times given are after the last time in `nctime`.')
-
- # Find the times for which the match is not perfect.
- # Use list comprehension instead of the simpler `nctime[index]` since
- # not all time objects support numpy integer indexing (eg dap).
- index[after] = N - 1
- ncnum = numpy.squeeze([nctime[i] for i in index])
- mismatch = numpy.nonzero(ncnum != num)[0]
-
- if select == 'exact':
- if len(mismatch) > 0:
- raise ValueError(
- 'Some of the times specified were not found in the `nctime` variable.')
-
- elif select == 'before':
- index[after] = N
- index[mismatch] -= 1
-
- elif select == 'after':
- pass
-
- elif select == 'nearest':
- nearest_to_left = num[mismatch] < numpy.array(
- [float(nctime[i - 1]) + float(nctime[i]) for i in index[mismatch]]) / 2.
- index[mismatch] = index[mismatch] - 1 * nearest_to_left
-
- else:
- raise ValueError(
- "%s is not an option for the `select` argument." % select)
-
- # Correct for indices equal to -1
- index[before] = 0
-
- # convert numpy scalars or single element arrays to python ints.
- return _toscalar(index)
-
-
-cdef _toscalar(a):
- if a.shape in [(), (1,)]:
- return a.item()
- else:
- return a
-
-cdef to_tuple(dt):
- """Turn a datetime.datetime instance into a tuple of integers. Elements go
- in the order of decreasing significance, making it easy to compare
- datetime instances. Parts of the state that don't affect ordering
- are omitted. Compare to datetime.timetuple()."""
- return (dt.year, dt.month, dt.day, dt.hour, dt.minute,
- dt.second, dt.microsecond)
-
-# a cache of converters (utime instances) for different calendars
-cdef dict _converters
-_converters = {}
-for calendar in _calendars:
- _converters[calendar] = utime("seconds since 1-1-1", calendar)
-
-cdef class datetime(object):
- """
-The base class implementing most methods of datetime classes that
-mimic datetime.datetime but support calendars other than the proleptic
-Gregorial calendar.
- """
- cdef readonly int year, month, day, hour, minute, dayofwk, dayofyr
- cdef readonly int second, microsecond
- cdef readonly str calendar
-
- # Python's datetime.datetime uses the proleptic Gregorian
- # calendar. This boolean is used to decide whether a
- # netcdftime.datetime instance can be converted to
- # datetime.datetime.
- cdef readonly bint datetime_compatible
-
- def __init__(self, int year, int month, int day, int hour=0, int minute=0, int second=0,
- int microsecond=0, int dayofwk=-1, int dayofyr=1):
- """dayofyr set to 1 by default - otherwise time.strftime will complain"""
-
- self.year = year
- self.month = month
- self.day = day
- self.hour = hour
- self.minute = minute
- self.dayofwk = dayofwk
- self.dayofyr = dayofyr
- self.second = second
- self.microsecond = microsecond
- self.calendar = ""
-
- self.datetime_compatible = True
-
- @property
- def format(self):
- return '%Y-%m-%d %H:%M:%S'
-
- def strftime(self, format=None):
- if format is None:
- format = self.format
- return _strftime(self, format)
-
- def replace(self, **kwargs):
- "Return datetime with new specified fields."
- args = {"year": self.year,
- "month": self.month,
- "day": self.day,
- "hour": self.hour,
- "minute": self.minute,
- "second": self.second,
- "microsecond": self.microsecond,
- "dayofwk": self.dayofwk,
- "dayofyr": self.dayofyr}
-
- for name, value in kwargs.items():
- args[name] = value
-
- return self.__class__(**args)
-
- def timetuple(self):
- return (self.year, self.month, self.day, self.hour,
- self.minute, self.second, self.dayofwk, self.dayofyr, -1)
-
- cpdef _to_real_datetime(self):
- return real_datetime(self.year, self.month, self.day,
- self.hour, self.minute, self.second,
- self.microsecond)
-
- def __repr__(self):
- return "{0}.{1}{2}".format(self.__class__.__module__,
- self.__class__.__name__,
- self._getstate())
-
- def __str__(self):
- return self.strftime(self.format)
-
- def __hash__(self):
- try:
- d = self._to_real_datetime()
- except ValueError:
- return hash(self.timetuple())
- return hash(d)
-
- cdef to_tuple(self):
- return (self.year, self.month, self.day, self.hour, self.minute,
- self.second, self.microsecond)
-
- def __richcmp__(self, other, int op):
- cdef datetime dt, dt_other
- dt = self
- if isinstance(other, datetime):
- dt_other = other
- # comparing two datetime instances
- if dt.calendar == dt_other.calendar:
- return PyObject_RichCompare(dt.to_tuple(), dt_other.to_tuple(), op)
- else:
- # Note: it *is* possible to compare datetime
- # instances that use difference calendars by using
- # utime.date2num(), but this implementation does
- # not attempt it.
- raise TypeError("cannot compare {0!r} and {1!r} (different calendars)".format(dt, dt_other))
- elif isinstance(other, real_datetime):
- # comparing datetime and real_datetime
- if not dt.datetime_compatible:
- 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:
- raise TypeError("cannot compare {0!r} and {1!r}".format(self, other))
-
- cdef _getstate(self):
- return (self.year, self.month, self.day, self.hour,
- self.minute, self.second, self.microsecond,
- self.dayofwk, self.dayofyr)
-
- def __reduce__(self):
- """special method that allows instance to be pickled"""
- return (self.__class__, self._getstate())
-
- cdef _add_timedelta(self, other):
- return NotImplemented
-
- def __add__(self, other):
- cdef datetime dt
- if isinstance(self, datetime) and isinstance(other, timedelta):
- dt = self
- delta = other
- elif isinstance(self, timedelta) and isinstance(other, datetime):
- dt = other
- delta = self
- else:
- return NotImplemented
- return dt._add_timedelta(delta)
-
- def __sub__(self, other):
- cdef datetime dt
- if isinstance(self, datetime): # left arg is a datetime instance
- dt = self
- if isinstance(other, datetime):
- # datetime - datetime
- if dt.calendar != other.calendar:
- raise ValueError("cannot compute the time difference between dates with different calendars")
- if dt.calendar == "":
- raise ValueError("cannot compute the time difference between dates that are not calendar-aware")
- converter = _converters[dt.calendar]
- return timedelta(seconds=converter.date2num(dt) - converter.date2num(other))
- elif isinstance(other, real_datetime):
- # datetime - real_datetime
- if not dt.datetime_compatible:
- raise ValueError("cannot compute the time difference between dates with different calendars")
- return dt._to_real_datetime() - other
- elif isinstance(other, timedelta):
- # datetime - timedelta
- return dt._add_timedelta(-other)
- else:
- return NotImplemented
- else:
- if isinstance(self, real_datetime):
- # real_datetime - datetime
- if not other.datetime_compatible:
- raise ValueError("cannot compute the time difference between dates with different calendars")
- return self - other._to_real_datetime()
- else:
- return NotImplemented
-
-cdef class DatetimeNoLeap(datetime):
- """
-Phony datetime object which mimics the python datetime object,
-but uses the "noleap" ("365_day") calendar.
- """
- def __init__(self, *args, **kwargs):
- datetime.__init__(self, *args, **kwargs)
- self.calendar = "noleap"
- self.datetime_compatible = False
-
- cdef _add_timedelta(self, delta):
- return DatetimeNoLeap(*add_timedelta(self, delta, no_leap, False))
-
-cdef class DatetimeAllLeap(datetime):
- """
-Phony datetime object which mimics the python datetime object,
-but uses the "all_leap" ("366_day") calendar.
- """
- def __init__(self, *args, **kwargs):
- datetime.__init__(self, *args, **kwargs)
- self.calendar = "all_leap"
- self.datetime_compatible = False
-
- cdef _add_timedelta(self, delta):
- return DatetimeAllLeap(*add_timedelta(self, delta, all_leap, False))
-
-cdef class Datetime360Day(datetime):
- """
-Phony datetime object which mimics the python datetime object,
-but uses the "360_day" calendar.
- """
- def __init__(self, *args, **kwargs):
- datetime.__init__(self, *args, **kwargs)
- self.calendar = "360_day"
- self.datetime_compatible = False
-
- cdef _add_timedelta(self, delta):
- return Datetime360Day(*add_timedelta_360_day(self, delta))
-
-cdef class DatetimeJulian(datetime):
- """
-Phony datetime object which mimics the python datetime object,
-but uses the "julian" calendar.
- """
- def __init__(self, *args, **kwargs):
- datetime.__init__(self, *args, **kwargs)
- self.calendar = "julian"
- self.datetime_compatible = False
-
- cdef _add_timedelta(self, delta):
- return DatetimeJulian(*add_timedelta(self, delta, is_leap_julian, False))
-
-cdef class DatetimeGregorian(datetime):
- """
-Phony datetime object which mimics the python datetime object,
-but uses the mixed Julian-Gregorian ("standard", "gregorian") calendar.
-
-The last date of the Julian calendar is 1582-10-4, which is followed
-by 1582-10-15, using the Gregorian calendar.
-
-Instances using the date after 1582-10-15 can be compared to
-datetime.datetime instances and used to compute time differences
-(datetime.timedelta) by subtracting a DatetimeGregorian instance from
-a datetime.datetime instance or vice versa.
- """
- def __init__(self, *args, **kwargs):
- datetime.__init__(self, *args, **kwargs)
- self.calendar = "gregorian"
-
- # dates after 1582-10-15 can be converted to and compared to
- # proleptic Gregorian dates
- if self.to_tuple() >= (1582, 10, 15, 0, 0, 0, 0):
- self.datetime_compatible = True
- else:
- self.datetime_compatible = False
-
- cdef _add_timedelta(self, delta):
- return DatetimeGregorian(*add_timedelta(self, delta, is_leap_gregorian, True))
-
-cdef class DatetimeProlepticGregorian(datetime):
- """
-Phony datetime object which mimics the python datetime object,
-but allows for dates that don't exist in the proleptic gregorian calendar.
-
-Supports timedelta operations by overloading + and -.
-
-Has strftime, timetuple, replace, __repr__, and __str__ methods. The
-format of the string produced by __str__ is controlled by self.format
-(default %Y-%m-%d %H:%M:%S). Supports comparisons with other phony
-datetime instances using the same calendar; comparison with
-datetime.datetime instances is possible for netcdftime.datetime
-instances using 'gregorian' and 'proleptic_gregorian' calendars.
-
-Instance variables are year,month,day,hour,minute,second,microsecond,dayofwk,dayofyr,
-format, and calendar.
- """
- def __init__(self, *args, **kwargs):
- datetime.__init__(self, *args, **kwargs)
- self.calendar = "proleptic_gregorian"
- self.datetime_compatible = True
-
- cdef _add_timedelta(self, delta):
- return DatetimeProlepticGregorian(*add_timedelta(self, delta,
- is_leap_proleptic_gregorian, False))
-
-_illegal_s = re.compile(r"((^|[^%])(%%)*%s)")
-
-
-cdef _findall(text, substr):
- # Also finds overlaps
- sites = []
- i = 0
- while 1:
- j = text.find(substr, i)
- if j == -1:
- break
- sites.append(j)
- i = j + 1
- return sites
-
-# Every 28 years the calendar repeats, except through century leap
-# years where it's 6 years. But only if you're using the Gregorian
-# calendar. ;)
-
-
-cdef _strftime(datetime dt, fmt):
- if _illegal_s.search(fmt):
- raise TypeError("This strftime implementation does not handle %s")
- # don't use strftime method at all.
- # if dt.year > 1900:
- # return dt.strftime(fmt)
-
- year = dt.year
- # For every non-leap year century, advance by
- # 6 years to get into the 28-year repeat cycle
- delta = 2000 - year
- off = 6 * (delta // 100 + delta // 400)
- year = year + off
-
- # Move to around the year 2000
- year = year + ((2000 - year) // 28) * 28
- timetuple = dt.timetuple()
- s1 = time.strftime(fmt, (year,) + timetuple[1:])
- sites1 = _findall(s1, str(year))
-
- s2 = time.strftime(fmt, (year + 28,) + timetuple[1:])
- sites2 = _findall(s2, str(year + 28))
-
- sites = []
- for site in sites1:
- if site in sites2:
- sites.append(site)
-
- s = s1
- syear = "%4d" % (dt.year,)
- for site in sites:
- s = s[:site] + syear + s[site + 4:]
- return s
-
-cdef bint is_leap_julian(int year):
- "Return 1 if year is a leap year in the Julian calendar, 0 otherwise."
- cdef int y
- y = year if year > 0 else year + 1
- return (y % 4) == 0
-
-cdef bint is_leap_proleptic_gregorian(int year):
- "Return 1 if year is a leap year in the Gregorian calendar, 0 otherwise."
- cdef int y
- y = year if year > 0 else year + 1
- return (((y % 4) == 0) and ((y % 100) != 0)) or ((y % 400) == 0)
-
-cdef bint is_leap_gregorian(int year):
- return (year > 1582 and is_leap_proleptic_gregorian(year)) or (year < 1582 and is_leap_julian(year))
-
-cdef bint all_leap(int year):
- "Return True for all years."
- return True
-
-cdef bint no_leap(int year):
- "Return False for all years."
- return False
-
-# numbers of days per month for calendars supported by add_timedelta(...)
-cdef int[13] month_lengths_365_day, month_lengths_366_day
-# Dummy Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
-for j,N in enumerate([-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]):
- month_lengths_365_day[j] = N
-
-# Dummy Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
-for j,N in enumerate([-1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]):
- month_lengths_366_day[j] = N
-
-cdef int* month_lengths(bint (*is_leap)(int), int year):
- if is_leap(year):
- return month_lengths_366_day
- else:
- return month_lengths_365_day
-
-# Add a datetime.timedelta to a netcdftime.datetime instance. Uses
-# integer arithmetic to avoid rounding errors and preserve
-# microsecond accuracy.
-#
-# The argument is_leap is the pointer to a function returning 1 for leap years and 0 otherwise.
-#
-# This implementation supports 365_day (no_leap), 366_day (all_leap),
-# julian, proleptic_gregorian, and the mixed Julian/Gregorian
-# (standard, gregorian) calendars by using different is_leap and
-# julian_gregorian_mixed arguments.
-#
-# The date of the transition from the Julian to Gregorian calendar and
-# the number of invalid dates are hard-wired (1582-10-4 is the last day
-# of the Julian calendar, after which follows 1582-10-15).
-cdef tuple add_timedelta(datetime dt, delta, bint (*is_leap)(int), bint julian_gregorian_mixed):
- cdef int microsecond, second, minute, hour, day, month, year
- cdef int delta_microseconds, delta_seconds, delta_days
- cdef int* month_length
- cdef int extra_days, n_invalid_dates
-
- # extract these inputs here to avoid type conversion in the code below
- delta_microseconds = delta.microseconds
- delta_seconds = delta.seconds
- delta_days = delta.days
-
- # shift microseconds, seconds, days
- microsecond = dt.microsecond + delta_microseconds
- second = dt.second + delta_seconds
- minute = dt.minute
- hour = dt.hour
- day = dt.day
- month = dt.month
- year = dt.year
-
- # validate inputs:
- if year == 0:
- raise ValueError("invalid year in {0!r}".format(dt))
-
- month_length = month_lengths(is_leap, year)
-
- if month < 1 or month > 12:
- raise ValueError("invalid month in {0!r}".format(dt))
-
- if day < 1 or day > month_length[month]:
- raise ValueError("invalid day number in {0!r}".format(dt))
-
- if julian_gregorian_mixed and year == 1582 and month == 10 and day > 4 and day < 15:
- raise ValueError("{0!r} is not present in the mixed Julian/Gregorian calendar".format(dt))
-
- n_invalid_dates = 10 if julian_gregorian_mixed else 0
-
- # Normalize microseconds, seconds, minutes, hours.
- second += microsecond // 1000000
- microsecond = microsecond % 1000000
- minute += second // 60
- second = second % 60
- hour += minute // 60
- minute = minute % 60
- extra_days = hour // 24
- hour = hour % 24
-
- delta_days += extra_days
-
- while delta_days < 0:
- if year == 1582 and month == 10 and day > 14 and day + delta_days < 15:
- delta_days -= n_invalid_dates # skip over invalid dates
- if day + delta_days < 1:
- delta_days += day
- # decrement month
- month -= 1
- if month < 1:
- month = 12
- year -= 1
- if year == 0:
- year = -1
- month_length = month_lengths(is_leap, year)
- day = month_length[month]
- else:
- day += delta_days
- delta_days = 0
-
- while delta_days > 0:
- if year == 1582 and month == 10 and day < 5 and day + delta_days > 4:
- delta_days += n_invalid_dates # skip over invalid dates
- if day + delta_days > month_length[month]:
- delta_days -= month_length[month] - (day - 1)
- # increment month
- month += 1
- if month > 12:
- month = 1
- year += 1
- if year == 0:
- year = 1
- month_length = month_lengths(is_leap, year)
- day = 1
- else:
- day += delta_days
- delta_days = 0
-
- return (year, month, day, hour, minute, second, microsecond, -1, 1)
-
-# Add a datetime.timedelta to a netcdftime.datetime instance with the 360_day calendar.
-#
-# Assumes that the 360_day calendar (unlike the rest of supported
-# calendars) has the year 0. Also, there are no leap years and all
-# months are 30 days long, so we can compute month and year by using
-# "//" and "%".
-cdef tuple add_timedelta_360_day(datetime dt, delta):
- cdef int microsecond, second, minute, hour, day, month, year
- cdef int delta_microseconds, delta_seconds, delta_days
-
- assert dt.month >= 1 and dt.month <= 12
-
- # extract these inputs here to avoid type conversion in the code below
- delta_microseconds = delta.microseconds
- delta_seconds = delta.seconds
- delta_days = delta.days
-
- # shift microseconds, seconds, days
- microsecond = dt.microsecond + delta_microseconds
- second = dt.second + delta_seconds
- minute = dt.minute
- hour = dt.hour
- day = dt.day + delta_days
- month = dt.month
- year = dt.year
-
- # Normalize microseconds, seconds, minutes, hours, days, and months.
- second += microsecond // 1000000
- microsecond = microsecond % 1000000
- minute += second // 60
- second = second % 60
- hour += minute // 60
- minute = minute % 60
- day += hour // 24
- hour = hour % 24
- # day and month are counted from 1; all months have 30 days
- month += (day - 1) // 30
- day = (day - 1) % 30 + 1
- # all years have 12 months
- year += (month - 1) // 12
- month = (month - 1) % 12 + 1
-
- return (year, month, day, hour, minute, second, microsecond, -1, 1)
From fdbb4fc20d8e208ab15fd8f8090eab229fa2fe35 Mon Sep 17 00:00:00 2001
From: Jeffrey Whitaker
Date: Wed, 31 Jan 2018 11:35:13 -0500
Subject: [PATCH 0003/1504] don't use netcdftime if it is not installed, raise
ImportError if you try to
---
netCDF4/_netCDF4.pyx | 19 +++++++++--
test/run_all.py | 8 +++++
test/tst_multifile.py | 76 +++++++++++++++++++++++-------------------
test/tst_multifile2.py | 44 +++++++++++++-----------
4 files changed, 90 insertions(+), 57 deletions(-)
diff --git a/netCDF4/_netCDF4.pyx b/netCDF4/_netCDF4.pyx
index b04354f9e..c1565be22 100644
--- a/netCDF4/_netCDF4.pyx
+++ b/netCDF4/_netCDF4.pyx
@@ -1007,7 +1007,11 @@ __version__ = "1.3.2"
# Initialize numpy
import posixpath
-import netcdftime
+try:
+ import netcdftime
+ _has_netcdftime = True
+except ImportError:
+ _has_netcdftime = False
import numpy
import weakref
import sys
@@ -5455,8 +5459,9 @@ def _to_ascii(bytestr):
# extra utilities (formerly in utils.pyx)
#----------------------------------------
from datetime import timedelta, datetime, MINYEAR
-from netcdftime import _parse_date, microsec_units, millisec_units,\
- sec_units, min_units, hr_units, day_units
+if _has_netcdftime:
+ from netcdftime import _parse_date, microsec_units, millisec_units,\
+ sec_units, min_units, hr_units, day_units
# start of the gregorian calendar
gregorian = datetime(1582,10,15)
@@ -5464,6 +5469,8 @@ gregorian = datetime(1582,10,15)
def _dateparse(timestr):
"""parse a string of the form time-units since yyyy-mm-dd hh:mm:ss,
return a datetime instance"""
+ if not _has_netcdftime:
+ raise ImportError('please install netcdftime to use this feature')
# same as version in netcdftime, but returns a timezone naive
# python datetime instance with the utc_offset included.
timestr_split = timestr.split()
@@ -5584,6 +5591,8 @@ Default is `'standard'`, which is a mixed Julian/Gregorian calendar.
returns a numeric time value, or an array of numeric time values
with approximately millisecond accuracy.
"""
+ if not _has_netcdftime:
+ raise ImportError('please install netcdftime to use this feature')
calendar = calendar.lower()
basedate = _dateparse(units)
unit = units.split()[0].lower()
@@ -5680,6 +5689,8 @@ datetime objects. The datetime instances
do not contain a time-zone offset, even if the specified `units`
contains one.
"""
+ if not _has_netcdftime:
+ raise ImportError('please install netcdftime to use this feature')
calendar = calendar.lower()
basedate = _dateparse(units)
unit = units.split()[0].lower()
@@ -5778,6 +5789,8 @@ correspond to the closest dates.
returns an index (indices) of the netCDF time variable corresponding
to the given datetime object(s).
"""
+ if not _has_netcdftime:
+ raise ImportError('please install netcdftime to use this feature')
try:
nctime.units
except AttributeError:
diff --git a/test/run_all.py b/test/run_all.py
index f06590cff..c2693f5c8 100755
--- a/test/run_all.py
+++ b/test/run_all.py
@@ -1,6 +1,11 @@
import glob, os, sys, unittest, struct
from netCDF4 import getlibversion,__hdf5libversion__,__netcdf4libversion__,__version__
from netCDF4 import __has_cdf5_format__, __has_nc_inq_path__, __has_nc_par__
+try:
+ import netcdftime
+ _has_netcdftime = True
+except ImportError:
+ _has_netcdftime = False
# can also just run
# python -m unittest discover . 'tst*py'
@@ -24,6 +29,9 @@
if not __has_cdf5_format__ or struct.calcsize("P") < 8:
test_files.remove('tst_cdf5.py')
sys.stdout.write('not running tst_cdf5.py ...\n')
+if not _has_netcdftime:
+ test_files.remove('tst_netcdftime.py')
+ sys.stdout.write('not running tst_netcdftime.py ...\n')
# Don't run tests that require network connectivity
if os.getenv('NO_NET'):
diff --git a/test/tst_multifile.py b/test/tst_multifile.py
index 0bc304360..95a69835a 100644
--- a/test/tst_multifile.py
+++ b/test/tst_multifile.py
@@ -4,6 +4,11 @@
from numpy.testing import assert_array_equal, assert_equal
from numpy import ma
import tempfile, unittest, os, datetime
+try:
+ import netcdftime
+ _has_netcdftime = True
+except ImportError:
+ _has_netcdftime = False
nx=100; ydim=5; zdim=10
nfiles = 10
@@ -111,44 +116,45 @@ def runTest(self):
# The test files have no calendar attribute on the time variable.
calendar = 'standard'
- # Get the real dates
- dates = []
- for file in self.files:
- f = Dataset(file)
+ if _has_netcdftime:
+ # Get the real dates
+ dates = []
+ for file in self.files:
+ f = Dataset(file)
+ t = f.variables['time']
+ dates.extend(num2date(t[:], t.units, calendar))
+ f.close()
+
+ # Compare with the MF dates
+ f = MFDataset(self.files,check=True)
t = f.variables['time']
- dates.extend(num2date(t[:], t.units, calendar))
- f.close()
- # Compare with the MF dates
- f = MFDataset(self.files,check=True)
- t = f.variables['time']
-
- T = MFTime(t, calendar=calendar)
- assert_equal(T.calendar, calendar)
- assert_equal(len(T), len(t))
- assert_equal(T.shape, t.shape)
- assert_equal(T.dimensions, t.dimensions)
- assert_equal(T.typecode(), t.typecode())
- assert_array_equal(num2date(T[:], T.units, T.calendar), dates)
- assert_equal(date2index(datetime.datetime(1980, 1, 2), T), 366)
- f.close()
+ T = MFTime(t, calendar=calendar)
+ assert_equal(T.calendar, calendar)
+ assert_equal(len(T), len(t))
+ assert_equal(T.shape, t.shape)
+ assert_equal(T.dimensions, t.dimensions)
+ assert_equal(T.typecode(), t.typecode())
+ assert_array_equal(num2date(T[:], T.units, T.calendar), dates)
+ assert_equal(date2index(datetime.datetime(1980, 1, 2), T), 366)
+ f.close()
- # Test exception is raised when no calendar attribute is available on the
- # time variable.
- with MFDataset(self.files, check=True) as ds:
- with self.assertRaises(ValueError):
- MFTime(ds.variables['time'])
-
- # Test exception is raised when the calendar attribute is different on the
- # variables. First, add calendar attributes to file. Note this will modify
- # the files inplace.
- calendars = ['standard', 'gregorian']
- for idx, f in enumerate(self.files):
- with Dataset(f, 'a') as ds:
- ds.variables['time'].calendar = calendars[idx]
- with MFDataset(self.files, check=True) as ds:
- with self.assertRaises(ValueError):
- MFTime(ds.variables['time'])
+ # Test exception is raised when no calendar attribute is available on the
+ # time variable.
+ with MFDataset(self.files, check=True) as ds:
+ with self.assertRaises(ValueError):
+ MFTime(ds.variables['time'])
+
+ # Test exception is raised when the calendar attribute is different on the
+ # variables. First, add calendar attributes to file. Note this will modify
+ # the files inplace.
+ calendars = ['standard', 'gregorian']
+ for idx, f in enumerate(self.files):
+ with Dataset(f, 'a') as ds:
+ ds.variables['time'].calendar = calendars[idx]
+ with MFDataset(self.files, check=True) as ds:
+ with self.assertRaises(ValueError):
+ MFTime(ds.variables['time'])
if __name__ == '__main__':
diff --git a/test/tst_multifile2.py b/test/tst_multifile2.py
index c16ed9ab6..9182dc302 100644
--- a/test/tst_multifile2.py
+++ b/test/tst_multifile2.py
@@ -4,6 +4,11 @@
from numpy.testing import assert_array_equal, assert_equal
from numpy import ma
import tempfile, unittest, os, datetime
+try:
+ import netcdftime
+ _has_netcdftime = True
+except ImportError:
+ _has_netcdftime = False
nx=100; ydim=5; zdim=10
nfiles = 10
@@ -101,27 +106,28 @@ def tearDown(self):
def runTest(self):
- # Get the real dates
- dates = []
- for file in self.files:
- f = Dataset(file)
+ if _has_netcdftime:
+ # Get the real dates
+ dates = []
+ for file in self.files:
+ f = Dataset(file)
+ t = f.variables['time']
+ dates.extend(num2date(t[:], t.units, t.calendar))
+ f.close()
+
+ # Compare with the MF dates
+ f = MFDataset(self.files,check=True)
t = f.variables['time']
- dates.extend(num2date(t[:], t.units, t.calendar))
+ mfdates = num2date(t[:], t.units, t.calendar)
+
+ T = MFTime(t)
+ assert_equal(len(T), len(t))
+ assert_equal(T.shape, t.shape)
+ assert_equal(T.dimensions, t.dimensions)
+ assert_equal(T.typecode(), t.typecode())
+ assert_array_equal(num2date(T[:], T.units, T.calendar), dates)
+ assert_equal(date2index(datetime.datetime(1980, 1, 2), T), 366)
f.close()
- # Compare with the MF dates
- f = MFDataset(self.files,check=True)
- t = f.variables['time']
- mfdates = num2date(t[:], t.units, t.calendar)
-
- T = MFTime(t)
- assert_equal(len(T), len(t))
- assert_equal(T.shape, t.shape)
- assert_equal(T.dimensions, t.dimensions)
- assert_equal(T.typecode(), t.typecode())
- assert_array_equal(num2date(T[:], T.units, T.calendar), dates)
- assert_equal(date2index(datetime.datetime(1980, 1, 2), T), 366)
- f.close()
-
if __name__ == '__main__':
unittest.main()
From 1c04dea6d09bb35f7be166fbf232573ecdd909bd Mon Sep 17 00:00:00 2001
From: Jeffrey Whitaker
Date: Wed, 31 Jan 2018 11:35:53 -0500
Subject: [PATCH 0004/1504] remove netcdftime
---
MANIFEST.in | 2 --
1 file changed, 2 deletions(-)
diff --git a/MANIFEST.in b/MANIFEST.in
index f6f156aee..fdc6217bd 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -13,8 +13,6 @@ include examples/*ipynb
include examples/README.md
include test/*py
include test/*nc
-include netcdftime/__init__.py
-include netcdftime/_netcdftime.pyx
include netCDF4/__init__.py
include netCDF4/_netCDF4.pyx
include netCDF4/utils.py
From 3bbb0fa2d3448ee0bbcc150f11458229f5552280 Mon Sep 17 00:00:00 2001
From: Jeffrey Whitaker
Date: Wed, 31 Jan 2018 11:47:18 -0500
Subject: [PATCH 0005/1504] add Changelog entry
---
COPYING | 12 ------------
Changelog | 2 ++
2 files changed, 2 insertions(+), 12 deletions(-)
diff --git a/COPYING b/COPYING
index 2cc338aca..c238e711e 100644
--- a/COPYING
+++ b/COPYING
@@ -15,18 +15,6 @@ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
-parts of pyiso8601 are included in netcdftime under the following license:
-
-Copyright (c) 2007 Michael Twomey
-
-Permission is hereby granted, free of charge, to any person obtaining a
-copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
diff --git a/Changelog b/Changelog
index 89f8a9c38..6ab19f59f 100644
--- a/Changelog
+++ b/Changelog
@@ -4,6 +4,8 @@
#736, issue #713).
* fixed reading of variables with zero-length dimensions in NETCDF3_CLASSIC
files (issue #743).
+ * netcdftime removed. Raise ImportError in utilities that use it.
+ Tests that use netcdftime only run if it is installed separately.
version 1.3.1 (tag v1.3.1rel)
=============================
From 984c7cfc66b73533f5a5541fda50957f2dc304a9 Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Sun, 11 Feb 2018 10:27:38 -0700
Subject: [PATCH 0006/1504] use more general method to check for integer
---
netCDF4/_netCDF4.pyx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/netCDF4/_netCDF4.pyx b/netCDF4/_netCDF4.pyx
index b04354f9e..4fd379a25 100644
--- a/netCDF4/_netCDF4.pyx
+++ b/netCDF4/_netCDF4.pyx
@@ -988,7 +988,7 @@ from cpython.buffer cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE,
# pure python utilities
from .utils import (_StartCountStride, _quantize, _find_dim, _walk_grps,
- _out_array_shape, _sortbylist, _tostr, _safecast)
+ _out_array_shape, _sortbylist, _tostr, _safecast, _is_int)
# try to use built-in ordered dict in python >= 2.7
try:
from collections import OrderedDict
@@ -4185,7 +4185,7 @@ rename a `netCDF4.Variable` attribute named `oldname` to `newname`."""
msg="data can only be assigned to VLEN variables using integer indices"
# check to see that elem is a tuple of integers.
# handle negative integers.
- if isinstance(elem, int):
+ if _is_int(elem):
if ndims > 1:
raise IndexError(msg)
if elem < 0:
@@ -4198,7 +4198,7 @@ rename a `netCDF4.Variable` attribute named `oldname` to `newname`."""
raise IndexError("Illegal index")
elemnew = []
for n,e in enumerate(elem):
- if not isinstance(e, int):
+ if not _is_int(e):
raise IndexError(msg)
elif e < 0:
enew = self.shape[n]+e
From 030a012060ac4ee508a4687a9660f6d540c5fcc0 Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Sun, 11 Feb 2018 12:01:12 -0700
Subject: [PATCH 0007/1504] update
---
Changelog | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Changelog b/Changelog
index 89f8a9c38..b86cb1fa4 100644
--- a/Changelog
+++ b/Changelog
@@ -4,6 +4,8 @@
#736, issue #713).
* fixed reading of variables with zero-length dimensions in NETCDF3_CLASSIC
files (issue #743).
+ * allow integer-like objects in VLEN slices (not just python ints, issue
+ #526, pull request #757).
version 1.3.1 (tag v1.3.1rel)
=============================
From 570ffdb48c576c66c74d922aa964bb05094d127d Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Sun, 11 Feb 2018 12:58:46 -0700
Subject: [PATCH 0008/1504] try to fix failing builds
---
appveyor.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/appveyor.yml b/appveyor.yml
index 9d5057f58..d5de9e317 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -38,6 +38,7 @@ install:
- cmd: call %CONDA_INSTALL_LOCN%\Scripts\activate.bat
# for obvci_appveyor_python_build_env.cmd
- cmd: conda update --all --yes
+ - cmd: conda config --system --add pinned_packages defaults::conda
- cmd: conda install anaconda-client=1.6.3 --yes
- cmd: conda install -c conda-forge --yes obvious-ci
# for msinttypes and newer stuff
From 736d4383a9efcc124689a0835150284107a821bc Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Mon, 12 Feb 2018 07:04:49 -0700
Subject: [PATCH 0009/1504] add test for issue 526
---
test/tst_vlen.py | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/test/tst_vlen.py b/test/tst_vlen.py
index 46849c284..d6cd8dc9c 100644
--- a/test/tst_vlen.py
+++ b/test/tst_vlen.py
@@ -105,6 +105,23 @@ def runTest(self):
f.close()
os.remove(FILE_NAME)
+class TestIntegerIndex(unittest.TestCase):
+ # issue 526
+ def runTest(self):
+ strtest = Dataset(FILE_NAME, 'w', format='NETCDF4')
+ strtest.createDimension('tenstrings', 10)
+ strtest.createVariable('tenstrings', str, ['tenstrings'])
+ strtest['tenstrings'][long(4)] = 'asdf'
+ strtest['tenstrings'][np.int32(5)] = 'asdf'
+ strtest['tenstrings'][6.0] = 'asdf'
+ strtest.close()
+ f = Dataset(FILE_NAME)
+ assert f.variables['tenstrings'][long(4)] == 'asdf'
+ assert f.variables['tenstrings'][np.int32(5)] == 'asdf'
+ assert f.variables['tenstrings'][6.0] == 'asdf'
+ f.close()
+ os.remove(FILE_NAME)
+
class TestObjectArrayIndexing(unittest.TestCase):
def setUp(self):
From ae329080201550df7ab5d07bf7d47837a62223ff Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Mon, 12 Feb 2018 07:22:19 -0700
Subject: [PATCH 0010/1504] update
---
test/tst_vlen.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/test/tst_vlen.py b/test/tst_vlen.py
index d6cd8dc9c..2b6818c19 100644
--- a/test/tst_vlen.py
+++ b/test/tst_vlen.py
@@ -111,12 +111,10 @@ def runTest(self):
strtest = Dataset(FILE_NAME, 'w', format='NETCDF4')
strtest.createDimension('tenstrings', 10)
strtest.createVariable('tenstrings', str, ['tenstrings'])
- strtest['tenstrings'][long(4)] = 'asdf'
strtest['tenstrings'][np.int32(5)] = 'asdf'
strtest['tenstrings'][6.0] = 'asdf'
strtest.close()
f = Dataset(FILE_NAME)
- assert f.variables['tenstrings'][long(4)] == 'asdf'
assert f.variables['tenstrings'][np.int32(5)] == 'asdf'
assert f.variables['tenstrings'][6.0] == 'asdf'
f.close()
From 43809f42158c6b42e98e136e7dfe3c14631a05c3 Mon Sep 17 00:00:00 2001
From: Filipe Fernandes
Date: Fri, 16 Feb 2018 14:40:45 -0800
Subject: [PATCH 0011/1504] update appveyor config
---
appveyor.yml | 45 +++++++++++++++++++++------------------------
1 file changed, 21 insertions(+), 24 deletions(-)
diff --git a/appveyor.yml b/appveyor.yml
index d5de9e317..81a5acfcc 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,23 +1,25 @@
environment:
-
- # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
- # /E:ON and /V:ON options are not enabled in the batch script interpreter
- # See: http://stackoverflow.com/a/13751649/163740
- CMD_IN_ENV: "cmd /E:ON /V:ON /C obvci_appveyor_python_build_env.cmd"
-
matrix:
- TARGET_ARCH: x64
CONDA_NPY: 111
CONDA_PY: 27
CONDA_INSTALL_LOCN: C:\\Miniconda-x64
+ - TARGET_ARCH: x64
+ CONDA_NPY: 114
+ CONDA_PY: 27
+ CONDA_INSTALL_LOCN: C:\\Miniconda-x64
+
- TARGET_ARCH: x64
CONDA_NPY: 111
CONDA_PY: 36
CONDA_INSTALL_LOCN: C:\\Miniconda35-x64
-# We always use a 64-bit machine, but can build x86 distributions
-# with the TARGET_ARCH variable.
+ - TARGET_ARCH: x64
+ CONDA_NPY: 114
+ CONDA_PY: 36
+ CONDA_INSTALL_LOCN: C:\\Miniconda35-x64
+
platform:
- x64
@@ -33,25 +35,20 @@ install:
throw "There are newer queued builds for this pull request, failing early." }
# Add path, activate `conda` and update conda.
- - cmd: set "PATH=%CONDA_INSTALL_LOCN%\\Scripts;%CONDA_INSTALL_LOCN%\\Library\\bin;%PATH%"
- - cmd: set PYTHONUNBUFFERED=1
- cmd: call %CONDA_INSTALL_LOCN%\Scripts\activate.bat
- # for obvci_appveyor_python_build_env.cmd
- - cmd: conda update --all --yes
- - cmd: conda config --system --add pinned_packages defaults::conda
- - cmd: conda install anaconda-client=1.6.3 --yes
- - cmd: conda install -c conda-forge --yes obvious-ci
- # for msinttypes and newer stuff
- - cmd: conda config --prepend channels conda-forge
- - cmd: conda config --set show_channel_urls yes
- - cmd: conda config --set always_yes true
- # For building conda packages
- - cmd: conda install --yes conda-build jinja2 anaconda-client
- # this is now the downloaded conda...
- - cmd: conda info -a
+ - 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 --add channels conda-forge --force
+
+ - cmd: set PYTHONUNBUFFERED=1
+
+ - cmd: conda.exe install conda-build vs2008_express_vc_python_patch
+ - cmd: call setup_x64
+ - cmd: conda.exe info --all
+ - cmd: conda.exe list
# Skip .NET project specific build phase.
build: off
test_script:
- - "%CMD_IN_ENV% conda build conda.recipe --quiet"
+ - "conda build conda.recipe"
From d14f15e73a2576f5fa014653bfbdf12fabb968df Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Sat, 17 Feb 2018 07:44:19 -0700
Subject: [PATCH 0012/1504] don't treat _FillValue as valid_min/valid_max
anymore (issue #761)
---
Changelog | 3 +++
netCDF4/_netCDF4.pyx | 12 ++++++++----
test/tst_masked4.py | 4 +++-
3 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/Changelog b/Changelog
index b86cb1fa4..26bfbd357 100644
--- a/Changelog
+++ b/Changelog
@@ -6,6 +6,9 @@
files (issue #743).
* allow integer-like objects in VLEN slices (not just python ints, issue
#526, pull request #757).
+ * treating _FillValue as a valid_min/valid_max was too surprising, despite
+ the fact the thet netcdf docs 'attribute best practices' suggests that
+ clients should to this. Revert this change from issue #576 (issue #761).
version 1.3.1 (tag v1.3.1rel)
=============================
diff --git a/netCDF4/_netCDF4.pyx b/netCDF4/_netCDF4.pyx
index 4fd379a25..e0971400d 100644
--- a/netCDF4/_netCDF4.pyx
+++ b/netCDF4/_netCDF4.pyx
@@ -4149,10 +4149,14 @@ rename a `netCDF4.Variable` attribute named `oldname` to `newname`."""
fval = numpy.array(default_fillvals[self.dtype.str[1:]],self.dtype)
if byte_type: fval = None
if self.dtype.kind != 'S': # don't set mask for character data
- if validmin is None and (fval is not None and fval <= 0):
- validmin = fval
- if validmax is None and (fval is not None and fval > 0):
- validmax = fval
+ # issues #761 and #748: setting valid_min/valid_max to the
+ # _FillVaue is too surprising for many users (despite the
+ # netcdf docs attribute best practices suggesting clients
+ # should do this).
+ #if validmin is None and (fval is not None and fval <= 0):
+ # validmin = fval
+ #if validmax is None and (fval is not None and fval > 0):
+ # validmax = fval
if validmin is not None:
totalmask += data < validmin
if validmax is not None:
diff --git a/test/tst_masked4.py b/test/tst_masked4.py
index 9d10ae669..33057c58e 100755
--- a/test/tst_masked4.py
+++ b/test/tst_masked4.py
@@ -94,7 +94,9 @@ def test_scaled(self):
assert_array_almost_equal(v2, self.v_scaled)
self.assertTrue(np.all(self.v_ma.mask == v.mask))
self.assertTrue(np.all(self.v_ma.mask == v2.mask))
- self.assertTrue(np.all(self.v_ma.mask == v3.mask))
+ # treating _FillValue as valid_min/valid_max was
+ # too suprising, revert to old behaviour (issue #761)
+ #self.assertTrue(np.all(self.v_ma.mask == v3.mask))
# check that underlying data is same as in netcdf file
v = f.variables['v']
v.set_auto_scale(False)
From ee38316bf68a0aef5cdb81fb90ef1bc7eb363d8d Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 10:15:15 -0700
Subject: [PATCH 0013/1504] update docstrings to mention netcdftime external
dependency
---
netCDF4/_netCDF4.pyx | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/netCDF4/_netCDF4.pyx b/netCDF4/_netCDF4.pyx
index 97ef4ffa0..81e757d7f 100644
--- a/netCDF4/_netCDF4.pyx
+++ b/netCDF4/_netCDF4.pyx
@@ -41,6 +41,9 @@ Requires
- [Cython](http://cython.org), version 0.21 or later.
- [setuptools](https://pypi.python.org/pypi/setuptools), version 18.0 or
later.
+ - [netcdftime](https://github.com/Unidata/netcdftime), in order to use
+ the time and date handling utility functions (`netCDF4.num2date` and
+ `netCDF4.date2num`).
- The HDF5 C library version 1.8.4-patch1 or higher (1.8.x recommended)
from [](ftp://ftp.hdfgroup.org/HDF5/current/src).
***netCDF version 4.4.1 or higher is recommended if using HDF5 1.10.x -
@@ -543,7 +546,9 @@ measure relative to a fixed date using a certain calendar, with units
specified like `hours since YY-MM-DD hh:mm:ss`. These units can be
awkward to deal with, without a utility to convert the values to and
from calendar dates. The function called `netCDF4.num2date` and `netCDF4.date2num` are
-provided with this package to do just that. Here's an example of how they
+provided with this package to do just that (starting with version 1.3.2, the
+[netcdftime](https://github.com/Unidata/netcdftime) package must be installed
+separately for these functions to work). Here's an example of how they
can be used:
:::python
@@ -5590,6 +5595,9 @@ Default is `'standard'`, which is a mixed Julian/Gregorian calendar.
returns a numeric time value, or an array of numeric time values
with approximately millisecond accuracy.
+
+Requires the [netcdftime](https://github.com/Unidata/netcdftime)
+external package.
"""
if not _has_netcdftime:
raise ImportError('please install netcdftime to use this feature')
@@ -5688,6 +5696,9 @@ objects which support some but not all the methods of 'real' python
datetime objects. The datetime instances
do not contain a time-zone offset, even if the specified `units`
contains one.
+
+Requires the [netcdftime](https://github.com/Unidata/netcdftime)
+external package.
"""
if not _has_netcdftime:
raise ImportError('please install netcdftime to use this feature')
@@ -5788,6 +5799,9 @@ correspond to the closest dates.
returns an index (indices) of the netCDF time variable corresponding
to the given datetime object(s).
+
+Requires the [netcdftime](https://github.com/Unidata/netcdftime)
+external package.
"""
if not _has_netcdftime:
raise ImportError('please install netcdftime to use this feature')
From 8ea8af10cdb6c63b4ed8898a86d1119e5a2c2809 Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 12:03:24 -0700
Subject: [PATCH 0014/1504] add netcdftime dependency for travis builds
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 8c7aa5af4..cd6bc90e5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,7 +11,7 @@ addons:
env:
global:
- - DEPENDS="numpy>=1.9.0 cython>=0.21 setuptools>=18.0"
+ - DEPENDS="numpy>=1.9.0 cython>=0.21 setuptools>=18.0 netcdftime>=1.0.0"
- NO_NET=1
- MPI=0
From 5184e037b267a901a50dbc9e06d44b030cdfe71e Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 12:07:16 -0700
Subject: [PATCH 0015/1504] update
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index cd6bc90e5..dd66919e8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,7 +11,7 @@ addons:
env:
global:
- - DEPENDS="numpy>=1.9.0 cython>=0.21 setuptools>=18.0 netcdftime>=1.0.0"
+ - DEPENDS="numpy>=1.9.0 cython>=0.21 setuptools>=18.0 netcdftime>=1.0.0a1"
- NO_NET=1
- MPI=0
From c287659b5f26f0fc4d26acd4f196434c9ea43903 Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 12:27:24 -0700
Subject: [PATCH 0016/1504] update
---
.travis.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.travis.yml b/.travis.yml
index dd66919e8..f258a36d3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -48,6 +48,7 @@ notifications:
email: false
before_install:
+ - pip install Cython # workaround for pip bug
- pip install $DEPENDS
install:
From 58a25e5d6635b779a9ef1c2af79d97a87621579a Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 12:37:06 -0700
Subject: [PATCH 0017/1504] netcdftime now uses zero padded years
---
test/tst_netcdftime.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/test/tst_netcdftime.py b/test/tst_netcdftime.py
index f4b31e89a..e70515b67 100644
--- a/test/tst_netcdftime.py
+++ b/test/tst_netcdftime.py
@@ -46,7 +46,7 @@ def runTest(self):
# check attributes.
self.assertTrue(self.cdftime_mixed.units == 'hours')
self.assertTrue(
- str(self.cdftime_mixed.origin) == ' 1-01-01 00:00:00')
+ str(self.cdftime_mixed.origin) == '0001-01-01 00:00:00')
self.assertTrue(
self.cdftime_mixed.unit_string == 'hours since 0001-01-01 00:00:00')
self.assertTrue(self.cdftime_mixed.calendar == 'standard')
@@ -85,7 +85,7 @@ def runTest(self):
self.assertTrue(d_check == ''.join(d2))
# test proleptic gregorian calendar.
self.assertTrue(self.cdftime_pg.units == 'seconds')
- self.assertTrue(str(self.cdftime_pg.origin) == ' 1-01-01 00:00:00')
+ self.assertTrue(str(self.cdftime_pg.origin) == '0001-01-01 00:00:00')
self.assertTrue(
self.cdftime_pg.unit_string == 'seconds since 0001-01-01 00:00:00')
self.assertTrue(self.cdftime_pg.calendar == 'proleptic_gregorian')
@@ -280,7 +280,7 @@ def runTest(self):
# Check leading white space
self.assertEqual(
- str(self.cdftime_leading_space.origin), ' 850-01-01 00:00:00')
+ str(self.cdftime_leading_space.origin), '0850-01-01 00:00:00')
#issue 330
units = "seconds since 1970-01-01T00:00:00Z"
From a018b8acce05651f4722387bc19bfc8f164e8b7a Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 15:00:28 -0700
Subject: [PATCH 0018/1504] add cython runtime dependency (run_all.py tries to
import it)
---
conda.recipe/meta.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml
index b5d35108d..b21e364e2 100644
--- a/conda.recipe/meta.yaml
+++ b/conda.recipe/meta.yaml
@@ -29,6 +29,7 @@ requirements:
- numpy x.x
- hdf5
- libnetcdf
+ - cython
test:
source_files:
From 432295c047877ab81a9c1a8276e9194c848ace32 Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 15:06:09 -0700
Subject: [PATCH 0019/1504] don't try to import netcdftime
---
conda.recipe/meta.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml
index b21e364e2..d3421d2e6 100644
--- a/conda.recipe/meta.yaml
+++ b/conda.recipe/meta.yaml
@@ -36,7 +36,7 @@ test:
- test
imports:
- netCDF4
- - netcdftime
+# - netcdftime
commands:
- ncinfo -h
- nc4tonc3 -h
From a3cdb4c496d96bb3a7bb3af7dc9b1da151dbd217 Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 16:42:10 -0700
Subject: [PATCH 0020/1504] update
---
Changelog | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Changelog b/Changelog
index 784edb596..607d9d74f 100644
--- a/Changelog
+++ b/Changelog
@@ -11,6 +11,11 @@
* treating _FillValue as a valid_min/valid_max was too surprising, despite
the fact the thet netcdf docs 'attribute best practices' suggests that
clients should to this. Revert this change from issue #576 (issue #761).
+ * remove netcdftime, since it is now a separate package. Utility functions
+ (such date2num and date2num) that use netcdftime will now raise an
+ ImportError if they are used and netcdftime not installed. Pull request
+ #756.
+
version 1.3.1 (tag v1.3.1rel)
=============================
From ecbea539f3bef50b5bf06aa44470f6af67361001 Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 17:14:08 -0700
Subject: [PATCH 0021/1504] have utilities just forward to netcdftime
---
netCDF4/_netCDF4.pyx | 193 ++---------------------------------------
test/tst_netcdftime.py | 12 +--
2 files changed, 13 insertions(+), 192 deletions(-)
diff --git a/netCDF4/_netCDF4.pyx b/netCDF4/_netCDF4.pyx
index ebb02d979..1341f910a 100644
--- a/netCDF4/_netCDF4.pyx
+++ b/netCDF4/_netCDF4.pyx
@@ -5464,44 +5464,6 @@ def _to_ascii(bytestr):
else:
return bytestr.encode('ascii')
-#----------------------------------------
-# extra utilities (formerly in utils.pyx)
-#----------------------------------------
-from datetime import timedelta, datetime, MINYEAR
-if _has_netcdftime:
- from netcdftime import _parse_date, microsec_units, millisec_units,\
- sec_units, min_units, hr_units, day_units
-
-# start of the gregorian calendar
-gregorian = datetime(1582,10,15)
-
-def _dateparse(timestr):
- """parse a string of the form time-units since yyyy-mm-dd hh:mm:ss,
- return a datetime instance"""
- if not _has_netcdftime:
- raise ImportError('please install netcdftime to use this feature')
- # same as version in netcdftime, 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")
- # 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:
- basedate = datetime(year, month, day, hour, minute, second)
- # subtract utc_offset from basedate time instance (which is timezone naive)
- basedate -= timedelta(days=utc_offset/1440.)
- else:
- if not utc_offset:
- basedate = netcdftime.datetime(year, month, day, hour, minute, second)
- else:
- raise ValueError('cannot use utc_offset for reference years <= 0')
- return basedate
-
def stringtoarr(string,NUMCHARS,dtype='S'):
"""
**`stringtoarr(a, NUMCHARS,dtype='S')`**
@@ -5605,64 +5567,8 @@ external package.
"""
if not _has_netcdftime:
raise ImportError('please install netcdftime to use this feature')
- calendar = calendar.lower()
- basedate = _dateparse(units)
- unit = units.split()[0].lower()
- # real-world calendars limited to positive reference years.
- if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
- if basedate.year == 0:
- msg='zero not allowed as a reference year, does not exist in Julian or Gregorian calendars'
- raise ValueError(msg)
- elif basedate.year < 0:
- msg='negative reference year in time units, must be >= 1'
- raise ValueError(msg)
-
- if (calendar == 'proleptic_gregorian' and basedate.year >= MINYEAR) or \
- (calendar in ['gregorian','standard'] and basedate > gregorian):
- # use python datetime module,
- isscalar = False
- try:
- dates[0]
- except:
- isscalar = True
- if isscalar:
- dates = numpy.array([dates])
- else:
- dates = numpy.array(dates)
- shape = dates.shape
- ismasked = False
- if hasattr(dates,'mask'):
- mask = dates.mask
- ismasked = True
- times = []
- for date in dates.flat:
- if ismasked and not date:
- times.append(None)
- else:
- td = date - basedate
- # total time in microseconds.
- totaltime = td.microseconds + (td.seconds + td.days * 24 * 3600) * 1.e6
- if unit in microsec_units:
- times.append(totaltime)
- elif unit in millisec_units:
- times.append(totaltime/1.e3)
- elif unit in sec_units:
- times.append(totaltime/1.e6)
- elif unit in min_units:
- times.append(totaltime/1.e6/60)
- elif unit in hr_units:
- times.append(totaltime/1.e6/3600)
- elif unit in day_units:
- times.append(totaltime/1.e6/3600./24.)
- else:
- raise ValueError('unsupported time units')
- if isscalar:
- return times[0]
- else:
- return numpy.reshape(numpy.array(times), shape)
- else: # use netcdftime module for other calendars
- cdftime = netcdftime.utime(units,calendar=calendar)
- return cdftime.date2num(dates)
+ else:
+ return netcdftime.date2num(dates,units,calendar=calendar)
def num2date(times,units,calendar='standard'):
"""
@@ -5706,72 +5612,8 @@ external package.
"""
if not _has_netcdftime:
raise ImportError('please install netcdftime to use this feature')
- calendar = calendar.lower()
- basedate = _dateparse(units)
- unit = units.split()[0].lower()
- # real-world calendars limited to positive reference years.
- if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
- if basedate.year == 0:
- msg='zero not allowed as a reference year, does not exist in Julian or Gregorian calendars'
- raise ValueError(msg)
- elif basedate.year < 0:
- msg='negative reference year in time units, must be >= 1'
- raise ValueError(msg)
-
- postimes = (numpy.asarray(times) > 0).all()
- if postimes and ((calendar == 'proleptic_gregorian' and basedate.year >= MINYEAR) or \
- (calendar in ['gregorian','standard'] and basedate > gregorian)):
- # use python datetime module,
- isscalar = False
- try:
- times[0]
- except:
- isscalar = True
- if isscalar:
- times = numpy.array([times],dtype='d')
- else:
- times = numpy.array(times, dtype='d')
- shape = times.shape
- ismasked = False
- if hasattr(times,'mask'):
- mask = times.mask
- ismasked = True
- dates = []
- for time in times.flat:
- if ismasked and not time:
- dates.append(None)
- else:
- # convert to total seconds
- if unit in microsec_units:
- tsecs = time/1.e6
- elif unit in millisec_units:
- tsecs = time/1.e3
- elif unit in sec_units:
- tsecs = time
- elif unit in min_units:
- tsecs = time*60.
- elif unit in hr_units:
- tsecs = time*3600.
- elif unit in day_units:
- tsecs = time*86400.
- else:
- raise ValueError('unsupported time units')
- # compute time delta.
- days = tsecs // 86400.
- msecsd = tsecs*1.e6 - days*86400.*1.e6
- secs = msecsd // 1.e6
- msecs = numpy.round(msecsd - secs*1.e6)
- td = timedelta(days=days,seconds=secs,microseconds=msecs)
- # add time delta to base date.
- date = basedate + td
- dates.append(date)
- if isscalar:
- return dates[0]
- else:
- return numpy.reshape(numpy.array(dates), shape)
- else: # use netcdftime for other calendars
- cdftime = netcdftime.utime(units,calendar=calendar)
- return cdftime.num2date(times)
+ else:
+ return netcdftime.num2date(times,units,calendar=calendar)
def date2index(dates, nctime, calendar=None, select='exact'):
"""
@@ -5809,30 +5651,9 @@ external package.
"""
if not _has_netcdftime:
raise ImportError('please install netcdftime to use this feature')
- try:
- nctime.units
- except AttributeError:
- raise AttributeError("netcdf time variable is missing a 'units' attribute")
- if calendar == None:
- calendar = getattr(nctime, 'calendar', 'standard')
- calendar = calendar.lower()
- basedate = _dateparse(nctime.units)
- # real-world calendars limited to positive reference years.
- if calendar in ['julian', 'standard', 'gregorian', 'proleptic_gregorian']:
- if basedate.year == 0:
- msg='zero not allowed as a reference year, does not exist in Julian or Gregorian calendars'
- raise ValueError(msg)
- elif basedate.year < 0:
- msg='negative reference year in time units, must be >= 1'
- raise ValueError(msg)
-
- if (calendar == 'proleptic_gregorian' and basedate.year >= MINYEAR) or \
- (calendar in ['gregorian','standard'] and basedate > gregorian):
- # use python datetime
- times = date2num(dates,nctime.units,calendar=calendar)
- return netcdftime.time2index(times, nctime, calendar, select)
- else: # use netcdftime module for other cases
- return netcdftime.date2index(dates, nctime, calendar, select)
+ else:
+ return netcdftime.date2index(dates, nctime, calendar=calendar,
+ select=select)
class MFDataset(Dataset):
"""
diff --git a/test/tst_netcdftime.py b/test/tst_netcdftime.py
index e70515b67..2a273ec1f 100644
--- a/test/tst_netcdftime.py
+++ b/test/tst_netcdftime.py
@@ -477,12 +477,12 @@ def runTest(self):
assert (d.day == 1)
assert (d.hour == 0)
# test fix for issue #659 (proper treatment of negative time values).
- units = 'days since 1800-01-01 00:00:0.0'
- d = num2date(-657073, units, calendar='standard')
- assert (d.year == 1)
- assert (d.month == 1)
- assert (d.day == 1)
- assert (d.hour == 0)
+ #units = 'days since 1800-01-01 00:00:0.0'
+ #d = num2date(-657073, units, calendar='standard')
+ #assert (d.year == 1)
+ #assert (d.month == 1)
+ #assert (d.day == 1)
+ #assert (d.hour == 0)
# issue 685: wrong time zone conversion
# 'The following times all refer to the same moment: "18:30Z", "22:30+04", "1130-0700", and "15:00-03:30'
# (https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=787811367#Time_offsets_from_UTC)
From 565c425db7924b6e2edb26bf9310405c05ece64b Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Tue, 20 Feb 2018 20:16:49 -0700
Subject: [PATCH 0022/1504] update docs
---
docs/netCDF4/index.html | 35 +++++++++++++++++++++++++----------
1 file changed, 25 insertions(+), 10 deletions(-)
diff --git a/docs/netCDF4/index.html b/docs/netCDF4/index.html
index bdf78cba9..b72c3badf 100644
--- a/docs/netCDF4/index.html
+++ b/docs/netCDF4/index.html
@@ -4,7 +4,7 @@
netCDF4 API documentation
-
netCDF4 module
-
Version 1.3.1
+
Version 1.3.2
Introduction
netcdf4-python is a Python interface to the netCDF C library.
The HDF5 C library version 1.8.4-patch1 or higher (1.8.x recommended)
from .
netCDF version 4.4.1 or higher is recommended if using HDF5 1.10.x -
@@ -1781,7 +1784,9 @@
7) Dealing with time coordinates.
specified like hours since YY-MM-DD hh:mm:ss. These units can be
awkward to deal with, without a utility to convert the values to and
from calendar dates. The function called num2date and date2num are
-provided with this package to do just that. Here's an example of how they
+provided with this package to do just that (starting with version 1.3.2, the
+netcdftime package must be installed
+separately for these functions to work). Here's an example of how they
can be used:
>>># fill in times.>>>fromdatetimeimportdatetime,timedelta
@@ -2259,7 +2264,9 @@
Functions
an exact match cannot be found. nearest will return the indices that
correspond to the closest dates.
returns an index (indices) of the netCDF time variable corresponding
-to the given datetime object(s).
objects which support some but not all the methods of 'real' python
datetime objects. The datetime instances
do not contain a time-zone offset, even if the specified units
-contains one.
+contains one.
+
units: Time units, for example, days since 1979-01-01. If None, use
-the units from the master variable.
+
units: Time units, for example, 'days since 1979-01-01'. If None,
+use the units from the master variable.
+
calendar: Calendar overload to use across all files, for example,
+'standard' or 'gregorian'. If None, check that the calendar attribute
+is present on each variable and values are unique across files raising a
+ValueError otherwise.
From fe293c8413b3e1f1d3bf42db58d3240a0b01027e Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Wed, 21 Feb 2018 16:19:37 -0700
Subject: [PATCH 0023/1504] Empty Commit to trigger appveyor
From 2e1ed91c775d355e1457f382cfd4b7d2101d97b3 Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Thu, 22 Feb 2018 21:07:17 -0700
Subject: [PATCH 0024/1504] remove mention of netcdftime
---
README.release | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/README.release b/README.release
index 5f8b8c32f..89cb406e0 100644
--- a/README.release
+++ b/README.release
@@ -1,8 +1,7 @@
* create a release branch ('vX.Y.Zrel'). In the release branch...
* make sure version number in PKG-INFO, setup.py and netCDF4/_netCDF4.pyx are up to date
(in _netCDF4.pyx, change 'Version' in first line of docstring at top of file,
- and __version__ variable). If netcdftime module has any updates,
- increment __version__ in netcdftime/_netcdftime.pyx.
+ and __version__ variable).
* update Changelog and README.md as needed.
* commit and push all of the above changes.
* install the module (python setup.py install), then run 'sh create_docs.sh'
From b5aa84ee396c6257f260d93e3d18744857b04307 Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Sat, 24 Feb 2018 09:06:09 -0700
Subject: [PATCH 0025/1504] remove duplicate entry
---
Changelog | 2 --
1 file changed, 2 deletions(-)
diff --git a/Changelog b/Changelog
index 607d9d74f..596ca6c79 100644
--- a/Changelog
+++ b/Changelog
@@ -4,8 +4,6 @@
#736, issue #713).
* fixed reading of variables with zero-length dimensions in NETCDF3_CLASSIC
files (issue #743).
- * netcdftime removed. Raise ImportError in utilities that use it.
- Tests that use netcdftime only run if it is installed separately.
* allow integer-like objects in VLEN slices (not just python ints, issue
#526, pull request #757).
* treating _FillValue as a valid_min/valid_max was too surprising, despite
From e1c0adc90c5bb29949b7e00b76bef7a4395af6aa Mon Sep 17 00:00:00 2001
From: Jeff Whitaker
Date: Mon, 26 Feb 2018 10:26:29 -0700
Subject: [PATCH 0026/1504] make netcdftime a hard dependency, remove wrapper
functions
---
Changelog | 6 +-
netCDF4/_netCDF4.pyx | 136 ++---------------------------------------
setup.py | 2 +-
test/run_all.py | 8 ---
test/tst_multifile.py | 76 +++++++++++------------
test/tst_multifile2.py | 44 ++++++-------
6 files changed, 62 insertions(+), 210 deletions(-)
diff --git a/Changelog b/Changelog
index 596ca6c79..44bed381d 100644
--- a/Changelog
+++ b/Changelog
@@ -9,10 +9,8 @@
* treating _FillValue as a valid_min/valid_max was too surprising, despite
the fact the thet netcdf docs 'attribute best practices' suggests that
clients should to this. Revert this change from issue #576 (issue #761).
- * remove netcdftime, since it is now a separate package. Utility functions
- (such date2num and date2num) that use netcdftime will now raise an
- ImportError if they are used and netcdftime not installed. Pull request
- #756.
+ * remove netcdftime, since it is now a separate package. date2num, num2date
+ and date2index still importable from netCDF4.
version 1.3.1 (tag v1.3.1rel)
diff --git a/netCDF4/_netCDF4.pyx b/netCDF4/_netCDF4.pyx
index 1341f910a..4d865f101 100644
--- a/netCDF4/_netCDF4.pyx
+++ b/netCDF4/_netCDF4.pyx
@@ -41,9 +41,9 @@ Requires
- [Cython](http://cython.org), version 0.21 or later.
- [setuptools](https://pypi.python.org/pypi/setuptools), version 18.0 or
later.
- - [netcdftime](https://github.com/Unidata/netcdftime), in order to use
- the time and date handling utility functions (`netCDF4.num2date` and
- `netCDF4.date2num`).
+ - [netcdftime](https://github.com/Unidata/netcdftime) for
+ the time and date handling utility functions (`netCDF4.num2date`,
+ `netCDF4.date2num` and `netCDF4.date2index`).
- The HDF5 C library version 1.8.4-patch1 or higher (1.8.x recommended)
from [](ftp://ftp.hdfgroup.org/HDF5/current/src).
***netCDF version 4.4.1 or higher is recommended if using HDF5 1.10.x -
@@ -548,7 +548,7 @@ awkward to deal with, without a utility to convert the values to and
from calendar dates. The function called `netCDF4.num2date` and `netCDF4.date2num` are
provided with this package to do just that (starting with version 1.3.2, the
[netcdftime](https://github.com/Unidata/netcdftime) package must be installed
-separately for these functions to work). Here's an example of how they
+separately). Here's an example of how they
can be used:
:::python
@@ -1012,11 +1012,7 @@ __version__ = "1.3.2"
# Initialize numpy
import posixpath
-try:
- import netcdftime
- _has_netcdftime = True
-except ImportError:
- _has_netcdftime = False
+from netcdftime import num2date, date2num, date2index
import numpy
import weakref
import sys
@@ -5533,128 +5529,6 @@ returns a numpy string array with datatype `'UN'` and shape
a.shape = b.shape[:-1]
return a
-def date2num(dates,units,calendar='standard'):
- """
-**`date2num(dates,units,calendar='standard')`**
-
-Return numeric time values given datetime objects. The units
-of the numeric time values are described by the `netCDF4.units` argument
-and the `netCDF4.calendar` keyword. The datetime objects must
-be in UTC with no time-zone offset. If there is a
-time-zone offset in `units`, it will be applied to the
-returned numeric values.
-
-**`dates`**: A datetime object or a sequence of datetime objects.
-The datetime objects should not include a time-zone offset.
-
-**`units`**: a string of the form `