@@ -80,32 +80,7 @@ def _unpack_netcdf_time_units(units):
80
80
return delta_units , ref_date
81
81
82
82
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 ):
109
84
# Verify that at least the first and last date can be decoded
110
85
# successfully. Otherwise, tracebacks end up swallowed by
111
86
# Dataset.__repr__ when users try to view their lazily decoded array.
@@ -115,7 +90,8 @@ def _decode_cf_datetime_dtype(data, units, calendar):
115
90
last_item (values ) or [0 ]])
116
91
117
92
try :
118
- result = decode_cf_datetime (example_value , units , calendar )
93
+ result = decode_cf_datetime (example_value , units , calendar ,
94
+ use_cftime )
119
95
except Exception :
120
96
calendar_msg = ('the default calendar' if calendar is None
121
97
else 'calendar %r' % calendar )
@@ -129,7 +105,52 @@ def _decode_cf_datetime_dtype(data, units, calendar):
129
105
return dtype
130
106
131
107
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 ):
133
154
"""Given an array of numeric dates in netCDF format, convert it into a
134
155
numpy array of date time objects.
135
156
@@ -149,41 +170,30 @@ def decode_cf_datetime(num_dates, units, calendar=None):
149
170
if calendar is None :
150
171
calendar = 'standard'
151
172
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 :
159
174
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 :
185
193
dates = _decode_datetime_with_cftime (
186
194
flat_num_dates .astype (np .float ), units , calendar )
195
+ else :
196
+ dates = _decode_datetime_with_pandas (flat_num_dates , units , calendar )
187
197
188
198
return dates .reshape (num_dates .shape )
189
199
@@ -383,6 +393,8 @@ def encode_cf_timedelta(timedeltas, units=None):
383
393
384
394
385
395
class CFDatetimeCoder (VariableCoder ):
396
+ def __init__ (self , use_cftime = None ):
397
+ self .use_cftime = use_cftime
386
398
387
399
def encode (self , variable , name = None ):
388
400
dims , data , attrs , encoding = unpack_for_encoding (variable )
@@ -403,9 +415,11 @@ def decode(self, variable, name=None):
403
415
if 'units' in attrs and 'since' in attrs ['units' ]:
404
416
units = pop_to (attrs , encoding , 'units' )
405
417
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 )
407
420
transform = partial (
408
- decode_cf_datetime , units = units , calendar = calendar )
421
+ decode_cf_datetime , units = units , calendar = calendar ,
422
+ use_cftime = self .use_cftime )
409
423
data = lazy_elemwise_func (data , transform , dtype )
410
424
411
425
return Variable (dims , data , attrs , encoding )
0 commit comments