@@ -80,32 +80,7 @@ def _unpack_netcdf_time_units(units):
8080 return delta_units , ref_date
8181
8282
83- def _decode_datetime_with_cftime (num_dates , units , calendar ):
84- cftime = _import_cftime ()
85-
86- if cftime .__name__ == 'cftime' :
87- dates = np .asarray (cftime .num2date (num_dates , units , calendar ,
88- only_use_cftime_datetimes = True ))
89- else :
90- # Must be using num2date from an old version of netCDF4 which
91- # does not have the only_use_cftime_datetimes option.
92- dates = np .asarray (cftime .num2date (num_dates , units , calendar ))
93-
94- if (dates [np .nanargmin (num_dates )].year < 1678 or
95- dates [np .nanargmax (num_dates )].year >= 2262 ):
96- if calendar in _STANDARD_CALENDARS :
97- warnings .warn (
98- 'Unable to decode time axis into full '
99- 'numpy.datetime64 objects, continuing using dummy '
100- 'cftime.datetime objects instead, reason: dates out '
101- 'of range' , SerializationWarning , stacklevel = 3 )
102- else :
103- if calendar in _STANDARD_CALENDARS :
104- dates = cftime_to_nptime (dates )
105- return dates
106-
107-
108- def _decode_cf_datetime_dtype (data , units , calendar ):
83+ def _decode_cf_datetime_dtype (data , units , calendar , use_cftime ):
10984 # Verify that at least the first and last date can be decoded
11085 # successfully. Otherwise, tracebacks end up swallowed by
11186 # Dataset.__repr__ when users try to view their lazily decoded array.
@@ -115,7 +90,8 @@ def _decode_cf_datetime_dtype(data, units, calendar):
11590 last_item (values ) or [0 ]])
11691
11792 try :
118- result = decode_cf_datetime (example_value , units , calendar )
93+ result = decode_cf_datetime (example_value , units , calendar ,
94+ use_cftime )
11995 except Exception :
12096 calendar_msg = ('the default calendar' if calendar is None
12197 else 'calendar %r' % calendar )
@@ -129,7 +105,52 @@ def _decode_cf_datetime_dtype(data, units, calendar):
129105 return dtype
130106
131107
132- def decode_cf_datetime (num_dates , units , calendar = None ):
108+ def _decode_datetime_with_cftime (num_dates , units , calendar ):
109+ cftime = _import_cftime ()
110+
111+ if cftime .__name__ == 'cftime' :
112+ return np .asarray (cftime .num2date (num_dates , units , calendar ,
113+ only_use_cftime_datetimes = True ))
114+ else :
115+ # Must be using num2date from an old version of netCDF4 which
116+ # does not have the only_use_cftime_datetimes option.
117+ return np .asarray (cftime .num2date (num_dates , units , calendar ))
118+
119+
120+ def _decode_datetime_with_pandas (flat_num_dates , units , calendar ):
121+ if calendar not in _STANDARD_CALENDARS :
122+ raise OutOfBoundsDatetime (
123+ 'Cannot decode times from a non-standard calendar, {!r}, using '
124+ 'pandas.' .format (calendar ))
125+
126+ delta , ref_date = _unpack_netcdf_time_units (units )
127+ delta = _netcdf_to_numpy_timeunit (delta )
128+ try :
129+ ref_date = pd .Timestamp (ref_date )
130+ except ValueError :
131+ # ValueError is raised by pd.Timestamp for non-ISO timestamp
132+ # strings, in which case we fall back to using cftime
133+ raise OutOfBoundsDatetime
134+
135+ # fixes: https://github.com/pydata/pandas/issues/14068
136+ # these lines check if the the lowest or the highest value in dates
137+ # cause an OutOfBoundsDatetime (Overflow) error
138+ with warnings .catch_warnings ():
139+ warnings .filterwarnings ('ignore' , 'invalid value encountered' ,
140+ RuntimeWarning )
141+ pd .to_timedelta (flat_num_dates .min (), delta ) + ref_date
142+ pd .to_timedelta (flat_num_dates .max (), delta ) + ref_date
143+
144+ # Cast input dates to integers of nanoseconds because `pd.to_datetime`
145+ # works much faster when dealing with integers
146+ # make _NS_PER_TIME_DELTA an array to ensure type upcasting
147+ flat_num_dates_ns_int = (flat_num_dates .astype (np .float64 ) *
148+ _NS_PER_TIME_DELTA [delta ]).astype (np .int64 )
149+
150+ return (pd .to_timedelta (flat_num_dates_ns_int , 'ns' ) + ref_date ).values
151+
152+
153+ def decode_cf_datetime (num_dates , units , calendar = None , use_cftime = None ):
133154 """Given an array of numeric dates in netCDF format, convert it into a
134155 numpy array of date time objects.
135156
@@ -149,41 +170,30 @@ def decode_cf_datetime(num_dates, units, calendar=None):
149170 if calendar is None :
150171 calendar = 'standard'
151172
152- delta , ref_date = _unpack_netcdf_time_units (units )
153-
154- try :
155- if calendar not in _STANDARD_CALENDARS :
156- raise OutOfBoundsDatetime
157-
158- delta = _netcdf_to_numpy_timeunit (delta )
173+ if use_cftime is None :
159174 try :
160- ref_date = pd .Timestamp (ref_date )
161- except ValueError :
162- # ValueError is raised by pd.Timestamp for non-ISO timestamp
163- # strings, in which case we fall back to using cftime
164- raise OutOfBoundsDatetime
165-
166- # fixes: https://github.com/pydata/pandas/issues/14068
167- # these lines check if the the lowest or the highest value in dates
168- # cause an OutOfBoundsDatetime (Overflow) error
169- with warnings .catch_warnings ():
170- warnings .filterwarnings ('ignore' , 'invalid value encountered' ,
171- RuntimeWarning )
172- pd .to_timedelta (flat_num_dates .min (), delta ) + ref_date
173- pd .to_timedelta (flat_num_dates .max (), delta ) + ref_date
174-
175- # Cast input dates to integers of nanoseconds because `pd.to_datetime`
176- # works much faster when dealing with integers
177- # make _NS_PER_TIME_DELTA an array to ensure type upcasting
178- flat_num_dates_ns_int = (flat_num_dates .astype (np .float64 ) *
179- _NS_PER_TIME_DELTA [delta ]).astype (np .int64 )
180-
181- dates = (pd .to_timedelta (flat_num_dates_ns_int , 'ns' ) +
182- ref_date ).values
183-
184- except (OutOfBoundsDatetime , OverflowError ):
175+ dates = _decode_datetime_with_pandas (flat_num_dates , units ,
176+ calendar )
177+ except (OutOfBoundsDatetime , OverflowError ):
178+ dates = _decode_datetime_with_cftime (
179+ flat_num_dates .astype (np .float ), units , calendar )
180+
181+ if (dates [np .nanargmin (num_dates )].year < 1678 or
182+ dates [np .nanargmax (num_dates )].year >= 2262 ):
183+ if calendar in _STANDARD_CALENDARS :
184+ warnings .warn (
185+ 'Unable to decode time axis into full '
186+ 'numpy.datetime64 objects, continuing using '
187+ 'cftime.datetime objects instead, reason: dates out '
188+ 'of range' , SerializationWarning , stacklevel = 3 )
189+ else :
190+ if calendar in _STANDARD_CALENDARS :
191+ dates = cftime_to_nptime (dates )
192+ elif use_cftime :
185193 dates = _decode_datetime_with_cftime (
186194 flat_num_dates .astype (np .float ), units , calendar )
195+ else :
196+ dates = _decode_datetime_with_pandas (flat_num_dates , units , calendar )
187197
188198 return dates .reshape (num_dates .shape )
189199
@@ -383,6 +393,8 @@ def encode_cf_timedelta(timedeltas, units=None):
383393
384394
385395class CFDatetimeCoder (VariableCoder ):
396+ def __init__ (self , use_cftime = None ):
397+ self .use_cftime = use_cftime
386398
387399 def encode (self , variable , name = None ):
388400 dims , data , attrs , encoding = unpack_for_encoding (variable )
@@ -403,9 +415,11 @@ def decode(self, variable, name=None):
403415 if 'units' in attrs and 'since' in attrs ['units' ]:
404416 units = pop_to (attrs , encoding , 'units' )
405417 calendar = pop_to (attrs , encoding , 'calendar' )
406- dtype = _decode_cf_datetime_dtype (data , units , calendar )
418+ dtype = _decode_cf_datetime_dtype (data , units , calendar ,
419+ self .use_cftime )
407420 transform = partial (
408- decode_cf_datetime , units = units , calendar = calendar )
421+ decode_cf_datetime , units = units , calendar = calendar ,
422+ use_cftime = self .use_cftime )
409423 data = lazy_elemwise_func (data , transform , dtype )
410424
411425 return Variable (dims , data , attrs , encoding )
0 commit comments