Coverage for /home/david/NCAS-CMS/cfunits/cfunits/units.py : 72%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import ctypes
2import ctypes.util
3import operator
4import sys
6from copy import deepcopy
8from numpy import array as numpy_array
9from numpy import asanyarray as numpy_asanyarray
10from numpy import dtype as numpy_dtype
11from numpy import generic as numpy_generic
12from numpy import ndarray as numpy_ndarray
13from numpy import shape as numpy_shape
14from numpy import size as numpy_size
17# --------------------------------------------------------------------
18# Aliases for ctypes
19# --------------------------------------------------------------------
20_sizeof_buffer = 257
21_string_buffer = ctypes.create_string_buffer(_sizeof_buffer)
22_c_char_p = ctypes.c_char_p
23_c_int = ctypes.c_int
24_c_uint = ctypes.c_uint
25_c_float = ctypes.c_float
26_c_double = ctypes.c_double
27_c_size_t = ctypes.c_size_t
28_c_void_p = ctypes.c_void_p
29_pointer = ctypes.pointer
30_POINTER = ctypes.POINTER
32_ctypes_POINTER = {4: _POINTER(_c_float),
33 8: _POINTER(_c_double)}
35# --------------------------------------------------------------------
36# Load the Udunits-2 library and read the database
37# --------------------------------------------------------------------
38#if sys.platform == 'darwin':
39# # This has been tested on Mac OSX 10.5.8 and 10.6.8
40# _udunits = ctypes.CDLL('libudunits2.0.dylib')
41#else:
42# # Linux
43# _udunits = ctypes.CDLL('libudunits2.so.0')
44_libpath = ctypes.util.find_library('udunits2')
45if _libpath is None:
46 raise FileNotFoundError(
47 "cfunits requires UNIDATA UDUNITS-2. Can't find the 'udunits2' library.")
49_udunits = ctypes.CDLL(_libpath)
51# Suppress "overrides prefixed-unit" messages. This also suppresses
52# all other error messages - so watch out!
53#
54# Messages may be turned back on by calling the module function
55# udunits_error_messages.
56# ut_error_message_handler ut_set_error_message_handler(
57# ut_error_message_handler handler);
58_ut_set_error_message_handler = _udunits.ut_set_error_message_handler
59_ut_set_error_message_handler.argtypes = (_c_void_p, )
60_ut_set_error_message_handler.restype = _c_void_p
62_ut_set_error_message_handler(_udunits.ut_ignore)
64# Read the data base
65# ut_system* ut_read_xml(const char* path);
66_ut_read_xml = _udunits.ut_read_xml
67_ut_read_xml.argtypes = (_c_char_p, )
68_ut_read_xml.restype = _c_void_p
70#print('units: before _udunits.ut_read_xml(',_unit_database,')')
71_ut_system = _ut_read_xml(None)
72#print('units: after _udunits.ut_read_xml(',_unit_database,')')
74# Reinstate the reporting of error messages
75#_ut_set_error_message_handler(_udunits.ut_write_to_stderr)
77# --------------------------------------------------------------------
78# Aliases for the UDUNITS-2 C API. See
79# http://www.unidata.ucar.edu/software/udunits/udunits-2.0.4/udunits2lib.html
80# for documentation.
81# --------------------------------------------------------------------
82# int ut_format(const ut_unit* const unit, char* buf, size_t size, unsigned opts);
83_ut_format = _udunits.ut_format
84_ut_format.argtypes = (_c_void_p, _c_char_p, _c_size_t, _c_uint)
85_ut_format.restype = _c_int
87# char* ut_trim(char* const string, const ut_encoding encoding);
88_ut_trim = _udunits.ut_trim
89_ut_trim.argtypes = (_c_char_p, _c_int) # ut_encoding assumed to be int!
90_ut_trim.restype = _c_char_p
92# ut_unit* ut_parse(const ut_system* const system,
93# const char* const string, const ut_encoding encoding);
94_ut_parse = _udunits.ut_parse
95_ut_parse.argtypes = (_c_void_p, _c_char_p, _c_int) # ut_encoding assumed to be int!
96_ut_parse.restype = _c_void_p
98# int ut_compare(const ut_unit* const unit1, const ut_unit* const
99# unit2);
100_ut_compare = _udunits.ut_compare
101_ut_compare.argtypes = (_c_void_p, _c_void_p)
102_ut_compare.restype = _c_int
104# int ut_are_convertible(const ut_unit* const unit1, const ut_unit*
105# const unit2);
106_ut_are_convertible = _udunits.ut_are_convertible
107_ut_are_convertible.argtypes = (_c_void_p, _c_void_p)
108_ut_are_convertible.restype = _c_int
110# cv_converter* ut_get_converter(ut_unit* const from, ut_unit* const
111# to);
112_ut_get_converter = _udunits.ut_get_converter
113_ut_get_converter.argtypes = (_c_void_p, _c_void_p)
114_ut_get_converter.restype = _c_void_p
116# ut_unit* ut_divide(const ut_unit* const numer, const ut_unit* const
117# denom);
118_ut_divide = _udunits.ut_divide
119_ut_divide.argtypes = (_c_void_p, _c_void_p)
120_ut_divide.restype = _c_void_p
122# ut_unit* ut_offset(const ut_unit* const unit, const double offset);
123_ut_offset = _udunits.ut_offset
124_ut_offset.argtypes = (_c_void_p, _c_double)
125_ut_offset.restype = _c_void_p
127# ut_unit* ut_raise(const ut_unit* const unit, const int power);
128_ut_raise = _udunits.ut_raise
129_ut_raise.argtypes = (_c_void_p, _c_int)
130_ut_raise.restype = _c_void_p
132# ut_unit* ut_scale(const double factor, const ut_unit* const unit);
133_ut_scale = _udunits.ut_scale
134_ut_scale.argtypes = (_c_double, _c_void_p)
135_ut_scale.restype = _c_void_p
137# ut_unit* ut_multiply(const ut_unit* const unit1, const ut_unit*
138# const unit2);
139_ut_multiply = _udunits.ut_multiply
140_ut_multiply.argtypes = (_c_void_p, _c_void_p)
141_ut_multiply.restype = _c_void_p
143# ut_unit* ut_log(const double base, const ut_unit* const reference);
144_ut_log = _udunits.ut_log
145_ut_log.argtypes = (_c_double, _c_void_p)
146_ut_log.restype = _c_void_p
148# ut_unit* ut_root(const ut_unit* const unit, const int root);
149_ut_root = _udunits.ut_root
150_ut_root.argtypes = (_c_void_p, _c_int)
151_ut_root.restype = _c_void_p
153# void ut_free_system(ut_system* system);
154_ut_free = _udunits.ut_free
155_ut_free.argypes = (_c_void_p, )
156_ut_free.restype = None
158# float* cv_convert_floats(const cv_converter* converter, const float*
159# const in, const size_t count, float* out);
160_cv_convert_floats = _udunits.cv_convert_floats
161_cv_convert_floats.argtypes = (_c_void_p, _c_void_p, _c_size_t, _c_void_p)
162_cv_convert_floats.restype = _c_void_p
164# double* cv_convert_doubles(const cv_converter* converter, const
165# double* const in, const size_t count,
166# double* out);
167_cv_convert_doubles = _udunits.cv_convert_doubles
168_cv_convert_doubles.argtypes = (_c_void_p, _c_void_p, _c_size_t, _c_void_p)
169_cv_convert_doubles.restype = _c_void_p
171# double cv_convert_double(const cv_converter* converter, const double
172# value);
173_cv_convert_double = _udunits.cv_convert_double
174_cv_convert_double.argtypes = (_c_void_p, _c_double)
175_cv_convert_double.restype = _c_double
177# void cv_free(cv_converter* const conv);
178_cv_free = _udunits.cv_free
179_cv_free.argtypes = (_c_void_p, )
180_cv_free.restype = None
182_UT_ASCII = 0
183_UT_NAMES = 4
184_UT_DEFINITION = 8
186_cv_convert_array = {4: _cv_convert_floats,
187 8: _cv_convert_doubles}
189# Some function definitions necessary for the following
190# changes to the unit system.
191_ut_get_unit_by_name = _udunits.ut_get_unit_by_name
192_ut_get_unit_by_name.argtypes = (_c_void_p, _c_char_p)
193_ut_get_unit_by_name.restype = _c_void_p
194_ut_get_status = _udunits.ut_get_status
195_ut_get_status.restype = _c_int
196_ut_unmap_symbol_to_unit = _udunits.ut_unmap_symbol_to_unit
197_ut_unmap_symbol_to_unit.argtypes = (_c_void_p, _c_char_p, _c_int)
198_ut_unmap_symbol_to_unit.restype = _c_int
199_ut_map_symbol_to_unit = _udunits.ut_map_symbol_to_unit
200_ut_map_symbol_to_unit.argtypes = (_c_char_p, _c_int, _c_void_p)
201_ut_map_symbol_to_unit.restype = _c_int
202_ut_map_unit_to_symbol = _udunits.ut_map_unit_to_symbol
203_ut_map_unit_to_symbol.argtypes = (_c_void_p, _c_char_p, _c_int)
204_ut_map_unit_to_symbol.restype = _c_int
205_ut_map_name_to_unit = _udunits.ut_map_name_to_unit
206_ut_map_name_to_unit.argtypes = (_c_char_p, _c_int, _c_void_p)
207_ut_map_name_to_unit.restype = _c_int
208_ut_map_unit_to_name = _udunits.ut_map_unit_to_name
209_ut_map_unit_to_name.argtypes = (_c_void_p, _c_char_p, _c_int)
210_ut_map_unit_to_name.restype = _c_int
211_ut_new_base_unit = _udunits.ut_new_base_unit
212_ut_new_base_unit.argtypes = (_c_void_p, )
213_ut_new_base_unit.restype = _c_void_p
215# Change Sv mapping. Both sievert and sverdrup are just aliases,
216# so no unit to symbol mapping needs to be changed.
217# We don't need to remove rem, since it was constructed with
218# the correct sievert mapping in place; because that mapping
219# was only an alias, the unit now doesn't depend on the mapping
220# persisting.
221assert(0 == _ut_unmap_symbol_to_unit(_ut_system, _c_char_p(b'Sv'), _UT_ASCII))
222assert(0 == _ut_map_symbol_to_unit(_c_char_p(b'Sv'), _UT_ASCII,
223 _ut_get_unit_by_name(_ut_system, _c_char_p(b'sverdrup'))))
225# Add new base unit calendar_year
226calendar_year_unit = _ut_new_base_unit(_ut_system)
227assert(0 == _ut_map_symbol_to_unit(_c_char_p(b'cY'), _UT_ASCII, calendar_year_unit))
228assert(0 == _ut_map_unit_to_symbol(calendar_year_unit, _c_char_p(b'cY'), _UT_ASCII))
229assert(0 == _ut_map_name_to_unit(_c_char_p(b'calendar_year'), _UT_ASCII, calendar_year_unit))
230assert(0 == _ut_map_unit_to_name(calendar_year_unit, _c_char_p(b'calendar_year'), _UT_ASCII))
231assert(0 == _ut_map_name_to_unit(_c_char_p(b'calendar_years'), _UT_ASCII, calendar_year_unit))
233# Add various aliases useful for CF
234def add_unit_alias(definition, symbol, singular, plural):
235 unit = _ut_parse(_ut_system, _c_char_p(definition.encode('utf-8')), _UT_ASCII)
236 if symbol is not None:
237 assert(0 == _ut_map_symbol_to_unit(_c_char_p(symbol.encode('utf-8')), _UT_ASCII, unit))
238 if singular is not None:
239 assert(0 == _ut_map_name_to_unit(_c_char_p(singular.encode('utf-8')), _UT_ASCII, unit))
240 if plural is not None:
241 assert(0 == _ut_map_name_to_unit(_c_char_p(plural.encode('utf-8')), _UT_ASCII, unit))
243add_unit_alias("1.e-3", "psu", "practical_salinity_unit", "practical_salinity_units")
244add_unit_alias("calendar_year/12", "cM", "calendar_month", "calendar_months")
245add_unit_alias("1", None, "level", "levels")
246add_unit_alias("1", None, "layer", "layers")
247add_unit_alias("1", None, "sigma_level", "sigma_levels")
248add_unit_alias("1", "dB", "decibel", "debicels")
249add_unit_alias("10 dB", None, "bel", "bels")
251#_udunits.ut_get_unit_by_name(_udunits.ut_new_base_unit(_ut_system),
252# _ut_system, 'calendar_year')
254# --------------------------------------------------------------------
255# Create a calendar year unit
256# --------------------------------------------------------------------
257#_udunits.ut_map_name_to_unit('calendar_year', _UT_ASCII,
258# _udunits.ut_new_base_unit(_ut_system))
260# --------------------------------------------------------------------
261# Aliases for netCDF4.netcdftime classes
262# --------------------------------------------------------------------
263import cftime
264#_netCDF4_netcdftime_utime = cftime.utime
265_datetime = cftime.datetime
267# --------------------------------------------------------------------
268# Aliases for netCDF4.netcdftime functions
269# --------------------------------------------------------------------
270_num2date = cftime.num2date
271_date2num = cftime.date2num
273_cached_ut_unit = {}
274_cached_utime = {}
276# --------------------------------------------------------------------
277# Save some useful units
278# --------------------------------------------------------------------
279# A time ut_unit (equivalent to 'day', 'second', etc.)
280_day_ut_unit = _ut_parse(_ut_system, _c_char_p(b'day'), _UT_ASCII)
281_cached_ut_unit['days'] = _day_ut_unit
282# A pressure ut_unit (equivalent to 'Pa', 'hPa', etc.)
283_pressure_ut_unit = _ut_parse(_ut_system, _c_char_p(b'pascal'), _UT_ASCII)
284_cached_ut_unit['pascal'] = _pressure_ut_unit
285# A calendar time ut_unit (equivalent to 'cY', 'cM')
286_calendartime_ut_unit = _ut_parse(_ut_system, _c_char_p(b'calendar_year'), _UT_ASCII)
287_cached_ut_unit['calendar_year'] = _calendartime_ut_unit
288# A dimensionless unit one (equivalent to '', '1', '2', etc.)
289#_dimensionless_unit_one = _udunits.ut_get_dimensionless_unit_one(_ut_system)
290#_cached_ut_unit[''] = _dimensionless_unit_one
291#_cached_ut_unit['1'] = _dimensionless_unit_one
293_dimensionless_unit_one = _ut_parse(_ut_system, _c_char_p(b'1'), _UT_ASCII)
294_cached_ut_unit[''] = _dimensionless_unit_one
295_cached_ut_unit['1'] = _dimensionless_unit_one
297# --------------------------------------------------------------------
298# Set the default calendar type according to the CF conventions
299# --------------------------------------------------------------------
300_default_calendar = 'gregorian'
301_canonical_calendar = {'gregorian' : 'gregorian' ,
302 'standard' : 'gregorian' ,
303 'none' : 'gregorian' ,
304 'proleptic_gregorian': 'proleptic_gregorian',
305 '360_day' : '360_day' ,
306 'noleap' : '365_day' ,
307 '365_day' : '365_day' ,
308 'all_leap' : '366_day' ,
309 '366_day' : '366_day' ,
310 'julian' : 'julian' ,
311 }
313_months_or_years = ('month', 'months', 'year', 'years', 'yr')
315## --------------------------------------------------------------------
316## Set month lengths in days for non-leap years (_days_in_month[0,1:])
317## and leap years (_days_in_month[1,1:])
318## --------------------------------------------------------------------
319#_days_in_month = numpy_array(
320# [[-99, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
321# [-99, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]])
323# --------------------------------------------------------------------
324# Function to control Udunits error messages
325# --------------------------------------------------------------------
326def udunits_error_messages(flag):
327 '''Control the printing of error messages from Udunits, which are
328 turned off by default.
330 :Parameters:
332 flag: `bool`
333 Set to True to print Udunits error messages and False to
334 not print Udunits error messages.
336 :Returns:
338 `None`
340 **Examples:**
342 >>> udunits_error_messages(True)
343 >>> udunits_error_messages(False)
345 '''
346 if flag:
347 _ut_set_error_message_handler(_udunits.ut_write_to_stderr)
348 else:
349 _ut_set_error_message_handler(_udunits.ut_ignore)
351#def _month_length(year, month, calendar, _days_in_month=_days_in_month):
352# '''
353#
354#Find month lengths in days for each year/month pairing in the input
355#numpy arrays 'year' and 'month', both of which must have the same
356#shape. 'calendar' must be one of the standard CF calendar types.
357#
358#:Parameters:
359#
360#
361#'''
362# shape = month.shape
363# if calendar in ('standard', 'gregorian'):
364# leap = numpy_where(year % 4 == 0, 1, 0)
365# leap = numpy_where((year > 1582) &
366# (year % 100 == 0) & (year % 400 != 0),
367# 0, leap)
368# elif calendar == '360_day':
369# days_in_month = numpy_empty(shape)
370# days_in_month.fill(30)
371# return days_in_month
372#
373# elif calendar in ('all_leap', '366_day'):
374# leap = numpy_zeros(shape)
375#
376# elif calendar in ('no_leap', '365_day'):
377# leap = numpy_ones(shape)
378#
379# elif calendar == 'proleptic_gregorian':
380# leap = numpy_where(year % 4 == 0, 1, 0)
381# leap = numpy_where((year % 100 == 0) & (year % 400 != 0),
382# 0, leap)
383#
384# days_in_month = numpy_array([_days_in_month[l, m]
385# for l, m in zip(leap.flat, month.flat)])
386# days_in_month.resize(shape)
387#
388# return days_in_month
389#
390#def _proper_date(year, month, day, calendar, fix=False,
391# _days_in_month=_days_in_month):
392# '''
393#
394#Given equally shaped numpy arrays of 'year', 'month', 'day' adjust
395#them *in place* to be proper dates. 'calendar' must be one of the
396#standard CF calendar types.
397#
398#Excessive number of months are converted to years but excessive days
399#are not converted to months nor years. If a day is illegal in the
400#proper date then a ValueError is raised, unless 'fix' is True, in
401#which case the day is lowered to the nearest legal date:
402#
403# 2000/26/1 -> 2002/3/1
404#
405# 2001/2/31 -> ValueError if 'fix' is False
406# 2001/2/31 -> 2001/2/28 if 'fix' is True
407# 2001/2/99 -> 2001/2/28 if 'fix' is True
408#
409#:Parameters:
410#
411#'''
412## y, month = divmod(month, 12)
413## year += y
414#
415# year += month // 12
416# month[...] = month % 12
417#
418# mask = (month == 0)
419# year[...] = numpy_where(mask, year-1, year)
420# month[...] = numpy_where(mask, 12, month)
421# del mask
422#
423# days_in_month = _month_length(year, month, calendar,
424# _days_in_month=_days_in_month)
425#
426# if fix:
427# day[...] = numpy_where(day > days_in_month, days_in_month, day)
428# elif (day > days_in_month).any():
429# raise ValueError("Illegal date(s) in %s calendar" % calendar)
430#
431# return year, month, day
434# --------------------------------------------------------------------
435# Constants, as defined by UDUNITS
436# --------------------------------------------------------------------
437_year_length = 365.242198781
438_month_length = _year_length / 12
441class Units():
442 '''Store, combine and compare physical units and convert numeric
443 values to different units.
445 Units are as defined in UNIDATA's Udunits-2 package, with a few
446 exceptions for greater consistency with the CF conventions namely
447 support for CF calendars and new units definitions.
450 **Modifications to the standard Udunits database**
452 Whilst a standard Udunits-2 database may be used, greater
453 consistency with CF is achieved by using a modified database. The
454 following units are either new to, modified from, or removed from
455 the standard Udunits-2 database (version 2.1.24):
457 ======================= ====== ============ ==============
458 Unit name Symbol Definition Status
459 ======================= ====== ============ ==============
460 practical_salinity_unit psu 1e-3 New unit
461 level 1 New unit
462 sigma_level 1 New unit
463 layer 1 New unit
464 decibel dB 1 New unit
465 bel 10 dB New unit
466 sverdrup Sv 1e6 m3 s-1 Added symbol
467 sievert J kg-1 Removed symbol
468 ======================= ====== ============ ==============
470 Plural forms of the new units' names are allowed, such as
471 ``practical_salinity_units``.
473 The modified database is in the *udunits* subdirectory of the
474 *etc* directory found in the same location as this module.
477 **Accessing units**
479 Units may be set, retrieved and deleted via the `units`
480 attribute. Its value is a string that can be recognized by
481 UNIDATA's Udunits-2 package, with the few exceptions given in the
482 CF conventions.
484 >>> u = Units('m s-1')
485 >>> u
486 <Cf Units: 'm s-1'>
487 >>> u.units = 'days since 2004-3-1'
488 >>> u
489 <Units: days since 2004-3-1>
492 **Equality and equivalence of units**
494 There are methods for assessing whether two units are equivalent
495 or equal. Two units are equivalent if numeric values in one unit
496 are convertible to numeric values in the other unit (such as
497 ``kilometres`` and ``metres``). Two units are equal if they are
498 equivalent and their conversion is a scale factor of 1 and an
499 offset of 0 (such as ``kilometres`` and ``1000 metres``). Note
500 that equivalence and equality are based on internally stored
501 binary representations of the units, rather than their string
502 representations.
504 >>> u = Units('m/s')
505 >>> v = Units('m s-1')
506 >>> w = Units('km.s-1')
507 >>> x = Units('0.001 kilometer.second-1')
508 >>> y = Units('gram')
510 >>> u.equivalent(v), u.equals(v), u == v
511 (True, True, True)
512 >>> u.equivalent(w), u.equals(w)
513 (True, False)
514 >>> u.equivalent(x), u.equals(x)
515 (True, True)
516 >>> u.equivalent(y), u.equals(y)
517 (False, False)
520 **Time and reference time units**
522 Time units may be given as durations of time (*time units*) or as
523 an amount of time since a reference time (*reference time units*):
525 >>> v = Units()
526 >>> v.units = 's'
527 >>> v.units = 'day'
528 >>> v.units = 'days since 1970-01-01'
529 >>> v.units = 'seconds since 1992-10-8 15:15:42.5 -6:00'
531 .. note:: It is recommended that the units ``year`` and ``month``
532 be used with caution, as explained in the following
533 excerpt from the CF conventions: "The Udunits package
534 defines a year to be exactly 365.242198781 days (the
535 interval between 2 successive passages of the sun
536 through vernal equinox). It is not a calendar
537 year. Udunits includes the following definitions for
538 years: a common_year is 365 days, a leap_year is 366
539 days, a Julian_year is 365.25 days, and a Gregorian_year
540 is 365.2425 days. For similar reasons the unit
541 ``month``, which is defined to be exactly year/12,
542 should also be used with caution."
544 **Calendar**
546 The date given in reference time units is associated with one of
547 the calendars recognized by the CF conventions and may be set with
548 the `calendar` attribute. However, as in the CF conventions, if
549 the calendar is not set then, for the purposes of calculation and
550 comparison, it defaults to the mixed Gregorian/Julian calendar as
551 defined by Udunits:
553 >>> u = Units('days since 2000-1-1')
554 >>> u.calendar
555 AttributeError: Can't get 'Units' attribute 'calendar'
556 >>> v = Units('days since 2000-1-1')
557 >>> v.calendar = 'gregorian'
558 >>> v.equals(u)
559 True
562 **Arithmetic with units**
564 The following operators, operations and assignments are
565 overloaded:
567 Comparison operators:
569 ``==, !=``
571 Binary arithmetic operations:
573 ``+, -, *, /, pow(), **``
575 Unary arithmetic operations:
577 ``-, +``
579 Augmented arithmetic assignments:
581 ``+=, -=, *=, /=, **=``
583 The comparison operations return a boolean and all other
584 operations return a new units object or modify the units object in
585 place.
587 >>> u = Units('m')
588 <Units: m>
590 >>> v = u * 1000
591 >>> v
592 <Units: 1000 m>
594 >>> u == v
595 False
596 >>> u != v
597 True
599 >>> u **= 2
600 >>> u
601 <Units: m2>
603 It is also possible to create the logarithm of a unit
604 corresponding to the given logarithmic base:
606 >>> u = Units('seconds')
607 >>> u.log(10)
608 <Units: lg(re 1 s)>
611 **Modifying data for equivalent units**
613 Any numpy array or python numeric type may be modified for
614 equivalent units using the `conform` static method.
616 >>> Units.conform(2, Units('km'), Units('m'))
617 2000.0
619 >>> import numpy
620 >>> a = numpy.arange(5.0)
621 >>> Units.conform(a, Units('minute'), Units('second'))
622 array([ 0., 60., 120., 180., 240.])
623 >>> a
624 array([ 0., 1., 2., 3., 4.])
626 If the *inplace* keyword is True, then a numpy array is modified
627 in place, without any copying overheads:
629 >>> Units.conform(a,
630 Units('days since 2000-12-1'),
631 Units('days since 2001-1-1'), inplace=True)
632 array([-31., -30., -29., -28., -27.])
633 >>> a
634 array([-31., -30., -29., -28., -27.])
636 '''
637 def __init__(self, units=None, calendar=None, formatted=False,
638 names=False, definition=False, _ut_unit=None):
639 '''**Initialization**
641 :Parameters:
643 units: `str` or `Units`, optional
644 Set the new units from this string.
646 calendar: `str`, optional
647 Set the calendar for reference time units.
649 formatted: `bool`, optional
650 Format the string representation of the units in a
651 standardized manner. See the `formatted` method.
653 names: `bool`, optional
654 Format the string representation of the units using names
655 instead of symbols. See the `format` method.
657 definition: `bool`, optional
658 Format the string representation of the units using basic
659 units. See the `format` method.
661 _ut_unit: `int`, optional
662 Set the new units from this Udunits binary unit
663 representation. This should be an integer returned by a
664 call to `ut_parse` function of Udunits. Ignored if `units`
665 is set.
667 '''
669 if isinstance(units, self.__class__):
670 self.__dict__ = units.__dict__
671 return
673 self._isvalid = True
674 self._reason_notvalid = ''
675 self._units = units
676 self._ut_unit = None
677 self._isreftime = False
678 self._calendar = calendar
679 self._canonical_calendar = None
680 self._utime = None
681 self._units_since_reftime = None
683 # Set the calendar
684 _calendar = None
685 if calendar is not None:
686 _calendar = _canonical_calendar.get(calendar.lower())
687 if _calendar is None:
688 self._new_reason_notvalid(
689 "Invalid calendar={!r}".format(calendar))
690 self._isvalid = False
691 _calendar = calendar
692 # --- End: if
694 if units is not None:
695 try:
696 units = units.strip()
697 except AttributeError:
698 self._isvalid = False
699 self._new_reason_notvalid(
700 "Bad units type: {}".format(type(units)))
701 return
703 unit = None
705 if isinstance(units, str) and ' since ' in units:
706 # ----------------------------------------------------
707 # Set a reference time unit
708 # ----------------------------------------------------
709 # Set the calendar
710 if calendar is None:
711 _calendar = _default_calendar
712 else:
713 _calendar = _canonical_calendar.get(calendar.lower())
714 if _calendar is None:
715 _calendar = calendar
716 # --- End: if
718 units_split = units.split(' since ')
719 unit = units_split[0].strip()
721 _units_since_reftime = unit
723 ut_unit = _cached_ut_unit.get(unit, None)
724 if ut_unit is None:
725 ut_unit = _ut_parse(_ut_system, _c_char_p(
726 unit.encode('utf-8')), _UT_ASCII)
727 if not ut_unit or not _ut_are_convertible(
728 ut_unit, _day_ut_unit):
729 ut_unit = None
730 self._isvalid = False
731 else:
732 _cached_ut_unit[unit] = ut_unit
733 # --- End: if
735 utime = None
737 if (_calendar, units) in _cached_utime:
738 utime = _cached_utime[(_calendar, units)]
739 else:
740 # Create a new Utime object
741 unit_string = '{} since {}'.format(
742 unit, units_split[1].strip())
744 if (_calendar, unit_string) in _cached_utime:
745 utime = _cached_utime[(_calendar, unit_string)]
746 else:
747 try:
748 utime = Utime(_calendar, unit_string)
749 except Exception as error:
750 utime = None
751 if unit in _months_or_years:
752 temp_unit_string = 'days since {}'.format(
753 units_split[1].strip())
754 try:
755 _ = Utime(_calendar, temp_unit_string)
756 except Exception as error:
757 self._new_reason_notvalid(str(error))
758 self._isvalid = False
759 else:
760 self._new_reason_notvalid(str(error))
761 self._isvalid = False
762 # --- End: try
764 _cached_utime[(_calendar, unit_string)] = utime
765 # --- End: if
767 self._isreftime = True
768 self._calendar = calendar
769 self._canonical_calendar = _calendar
770 self._utime = utime
772 else:
773 # ----------------------------------------------------
774 # Set a unit other than a reference time unit
775 # ----------------------------------------------------
776 ut_unit = _cached_ut_unit.get(units, None)
777 if ut_unit is None:
778 ut_unit = _ut_parse(_ut_system,
779 _c_char_p(units.encode('utf-8')),
780 _UT_ASCII)
781 if not ut_unit:
782 ut_unit = None
783 self._isvalid = False
784 self._new_reason_notvalid(
785 "Invalid units: {!r}; "
786 "Not recognised by UDUNITS".format(units))
787 else:
788 _cached_ut_unit[units] = ut_unit
789 # --- End: if
791# if ut_unit is None:
792# ut_unit = _ut_parse(_ut_system, _c_char_p(units.encode('utf-8')), _UT_ASCII)
793# if not ut_unit:
794# raise ValueError(
795# "Can't set unsupported unit: %r" % units)
796# _cached_ut_unit[units] = ut_unit
798 self._isreftime = False
799 self._calendar = None
800 self._canonial_calendar = None
801 self._utime = None
803 self._ut_unit = ut_unit
804 self._units = units
805 self._units_since_reftime = unit
807 if formatted or names or definition:
808 self._units = self.formatted(names, definition)
810 return
812 elif calendar:
813 # ---------------------------------------------------------
814 # Calendar is set, but units are not.
815 # ---------------------------------------------------------
816 self._units = None
817 self._ut_unit = None
818 self._isreftime = True
819 self._calendar = calendar
820 self._canonical_calendar = _canonical_calendar[calendar.lower()]
821 self._units_since_reftime = None
822 try:
823 self._utime = Utime(_canonical_calendar[calendar.lower()])
824 except Exception as error:
825 self._new_reason_notvalid(
826 'Invalid calendar={!r}'.format(calendar))
827 self._isvalid = True
829 return
831 if _ut_unit is not None:
832 # ---------------------------------------------------------
833 # _ut_unit is set
834 # ---------------------------------------------------------
835 self._ut_unit = _ut_unit
836 self._isreftime = False
838 units = self.formatted(names, definition)
839 _cached_ut_unit[units] = _ut_unit
840 self._units = units
842 self._units_since_reftime = None
844 self._calendar = None
845 self._utime = None
847 return
849 # -------------------------------------------------------------
850 # Nothing has been set
851 # -------------------------------------------------------------
852 self._units = None
853 self._ut_unit = None
854 self._isreftime = False
855 self._calendar = None
856 self._canonical_calendar = None
857 self._utime = None
858 self._units_since_reftime = None
860 def __getstate__(self):
861 '''Called when pickling.
863 :Returns:
865 `dict`
866 A dictionary of the instance's attributes
868 **Examples:**
870 >>> u = Units('days since 3-4-5', calendar='gregorian')
871 >>> u.__getstate__()
872 {'calendar': 'gregorian',
873 'units': 'days since 3-4-5'}
875 '''
876 return dict([(attr, getattr(self, attr))
877 for attr in ('_units', '_calendar')
878 if hasattr(self, attr)])
880 def __setstate__(self, odict):
881 '''Called when unpickling.
883 :Parameters:
885 odict: `dict`
886 The output from the instance's `__getstate__` method.
888 :Returns:
890 `None`
892 '''
893 units = None
894 if '_units' in odict:
895 units = odict['_units']
897 calendar = None
898 if '_calendar' in odict:
899 calendar = odict['_calendar']
901 self.__init__(units=units, calendar=calendar)
903 def __hash__(self):
904 '''x.__hash__() <==> hash(x)
906 '''
907 if not self._isreftime:
908 return hash(('Units', self._ut_unit))
910 return hash(('Units',
911 self._ut_unit, self._rtime_jd0, self._rtime_calendar,
912 self._rtime_tzoffset))
914 def __repr__(self):
915 '''x.__repr__() <==> repr(x)
917 '''
918 return '<{0}: {1}>'.format(self.__class__.__name__, self)
920 def __str__(self):
921 '''x.__str__() <==> str(x)
923 '''
924 string = []
925 if self._units is not None:
926 if self._units == '':
927 string.append("\'\'")
928 else:
929 string.append(str(self._units))
930 # --- End: if
932 if self._calendar is not None:
933 string.append('{0}'.format(self._calendar))
935 return ' '.join(string)
937 def __deepcopy__(self, memo):
938 '''Used if copy.deepcopy is called on the variable.
940 '''
941 return self
943 def __bool__(self):
944 '''Truth value testing and the built-in operation ``bool``
946 x.__bool__() <==> x!=0
948 '''
949 return self._ut_unit is not None
951 def __eq__(self, other):
952 '''The rich comparison operator ``==``
954 x.__eq__(y) <==> x==y
956 '''
957 return self.equals(other)
959 def __ne__(self, other):
960 '''The rich comparison operator ``!=``
962 x.__ne__(y) <==> x!=y
964 '''
965 return not self.equals(other)
967 def __gt__(self, other):
968 '''The rich comparison operator ``>``
970 x.__gt__(y) <==> x>y
972 '''
973 return self._comparison(other, '__gt__')
975 def __ge__(self, other):
976 '''The rich comparison operator ````
978 x.__ge__(y) <==> x>y
980 '''
981 return self._comparison(other, '__ge__')
983 def __lt__(self, other):
984 '''The rich comparison operator ````
986 x.__lt__(y) <==> x<y
988 '''
989 return self._comparison(other, '__lt__')
991 def __le__(self, other):
992 '''The rich comparison operator ````
994 x.__le__(y) <==> x<=y
996 '''
997 return self._comparison(other, '__le__')
999 def __sub__(self, other):
1000 '''The binary arithmetic operation ``-``
1002 x.__sub__(y) <==> x-y
1004 '''
1005 if (self._isreftime
1006 or (isinstance(other, self.__class__) and other._isreftime)):
1007 raise ValueError("Can't do {!r} - {!r}".format(self, other))
1009 try:
1010 _ut_unit = _ut_offset(self._ut_unit, _c_double(other))
1011 return type(self)(_ut_unit=_ut_unit)
1012 except:
1013 raise ValueError("Can't do {!r} - {!r}".format(self, other))
1015 def __add__(self, other):
1016 '''The binary arithmetic operation ``+``
1018 x.__add__(y) <==> x+y
1020 '''
1021 if (self._isreftime or
1022 (isinstance(other, self.__class__) and other._isreftime)):
1023 raise ValueError("Can't do {!r} + {!r}".format(self, other))
1025 try:
1026 _ut_unit = _ut_offset(self._ut_unit, _c_double(-other))
1027 return type(self)(_ut_unit=_ut_unit)
1028 except:
1029 raise ValueError("Can't do {!r} + {!r}".format(self, other))
1031 def __mul__(self, other):
1032 '''The binary arithmetic operation ``*``
1034 x.__mul__(y) <==> x*y
1036 '''
1037 if isinstance(other, self.__class__):
1038 if self._isreftime or other._isreftime:
1039 raise ValueError("Can't do {!r} * {!r}".format(self, other))
1041 try:
1042 ut_unit=_ut_multiply(self._ut_unit, other._ut_unit)
1043 except:
1044 raise ValueError("Can't do {!r} * {!r}".format(self, other))
1045 else:
1046 if self._isreftime:
1047 raise ValueError("Can't do {!r} * {!r}".format(self, other))
1049 try:
1050 ut_unit=_ut_scale(_c_double(other), self._ut_unit)
1051 except:
1052 raise ValueError("Can't do {!r} * {!r}".format(self, other))
1053 # --- End: if
1055 return type(self)(_ut_unit=ut_unit)
1057 def __div__(self, other):
1058 '''x.__div__(y) <==> x/y
1060 '''
1061 if isinstance(other, self.__class__):
1062 if self._isreftime or other._isreftime:
1063 raise ValueError("Can't do {!r} / {!r}".format(self, other))
1065 try:
1066 ut_unit=_ut_divide(self._ut_unit, other._ut_unit)
1067 except:
1068 raise ValueError("Can't do {!r} / {!r}".format(self, other))
1069 else:
1070 if self._isreftime:
1071 raise ValueError("Can't do {!r} / {!r}".format(self, other))
1073 try:
1074 ut_unit=_ut_scale(_c_double(1.0/other), self._ut_unit)
1075 except:
1076 raise ValueError("Can't do {!r} / {!r}".format(self, other))
1077 # --- End: if
1079 return type(self)(_ut_unit=ut_unit)
1081 def __pow__(self, other, modulo=None):
1082 '''The binary arithmetic operations ``**`` and ``pow``
1084 x.__pow__(y) <==> x**y
1086 '''
1087 # ------------------------------------------------------------
1088 # y must be either an integer or the reciprocal of a positive
1089 # integer.
1090 # ------------------------------------------------------------
1092 if modulo is not None:
1093 raise NotImplementedError(
1094 "3-argument power not supported for {!r}".format(
1095 self.__class__.__name__))
1097 if self and not self._isreftime:
1098 ut_unit = self._ut_unit
1099 try:
1100 return type(self)(_ut_unit=_ut_raise(ut_unit, _c_int(other)))
1101 except:
1102 pass
1104 if 0 < other <= 1:
1105 # If other is a float and (1/other) is a positive
1106 # integer then take the (1/other)-th root. E.g. if
1107 # other is 0.125 then we take the 8-th root.
1108 try:
1109 recip_other = 1/other
1110 root = int(recip_other)
1111 if recip_other == root:
1112 ut_unit = _ut_root(ut_unit, _c_int(root))
1113 if ut_unit is not None:
1114 return type(self)(_ut_unit=ut_unit)
1115 except:
1116 pass
1117 else:
1118 # If other is a float equal to its integer then raise
1119 # to the integer part. E.g. if other is 3.0 then we
1120 # raise to the power of 3; if other is -2.0 then we
1121 # raise to the power of -2
1122 try:
1123 root = int(other)
1124 if other == root:
1125 ut_unit = _ut_raise(ut_unit, _c_int(root))
1126 if ut_unit is not None:
1127 return type(self)(_ut_unit=ut_unit)
1128 except:
1129 pass
1130 # --- End: if
1132 raise ValueError("Can't do {!r} ** {!r}".format(self, other))
1134 def __isub__(self, other):
1135 '''x.__isub__(y) <==> x-=y
1137 '''
1138 return self - other
1140 def __iadd__(self, other):
1141 '''x.__iadd__(y) <==> x+=y
1143 '''
1144 return self + other
1146 def __imul__(self, other):
1147 '''The augmented arithmetic assignment ``*=``
1149 x.__imul__(y) <==> x*=y
1151 '''
1152 return self * other
1154 def __idiv__(self, other):
1155 '''The augmented arithmetic assignment ``/=``
1157 x.__idiv__(y) <==> x/=y
1159 '''
1160 return self / other
1162 def __ipow__(self, other):
1163 '''The augmented arithmetic assignment ``**=``
1165 x.__ipow__(y) <==> x**=y
1167 '''
1168 return self ** other
1170 def __rsub__(self, other):
1171 '''The binary arithmetic operation ``-`` with reflected operands
1173 x.__rsub__(y) <==> y-x
1175 '''
1176 try:
1177 return -self + other
1178 except:
1179 raise ValueError("Can't do {!r} - {!r}".format(other, self))
1181 def __radd__(self, other):
1182 '''The binary arithmetic operation ``+`` with reflected operands
1184 x.__radd__(y) <==> y+x
1186 '''
1187 return self + other
1189 def __rmul__(self, other):
1190 '''The binary arithmetic operation ``*`` with reflected operands
1192 x.__rmul__(y) <==> y*x
1194 '''
1195 return self * other
1197 def __rdiv__(self, other):
1198 '''x.__rdiv__(y) <==> y/x
1200 '''
1201 try:
1202 return (self ** -1) * other
1203 except:
1204 raise ValueError("Can't do {!r} / {!r}".format(other, self))
1206 def __floordiv__(self, other):
1207 '''x.__floordiv__(y) <==> x//y <==> x/y
1209 '''
1210 return self / other
1212 def __ifloordiv__(self, other):
1213 '''x.__ifloordiv__(y) <==> x//=y <==> x/=y
1215 '''
1216 return self / other
1218 def __rfloordiv__(self, other):
1219 '''x.__rfloordiv__(y) <==> y//x <==> y/x
1221 '''
1222 try:
1223 return (self ** -1) * other
1224 except:
1225 raise ValueError("Can't do {!r} // {!r}".format(other, self))
1227 def __truediv__(self, other):
1228 '''x.__truediv__(y) <==> x/y
1230 '''
1231 return self.__div__(other)
1233 def __itruediv__(self, other):
1234 '''x.__itruediv__(y) <==> x/=y
1236 '''
1237 return self.__idiv__(other)
1239 def __rtruediv__(self, other):
1240 '''x.__rtruediv__(y) <==> y/x
1242 '''
1243 return self.__rdiv__(other)
1245 def __mod__(self, other):
1246 '''TODO
1248 '''
1249 raise ValueError("Can't do {!r} % {!r}".format(other, self))
1251 def __neg__(self):
1252 '''The unary arithmetic operation ``-``
1254 x.__neg__() <==> -x
1256 '''
1257 return self * -1
1259 def __pos__(self):
1260 '''The unary arithmetic operation ``+``
1262 x.__pos__() <==> +x
1264 '''
1265 return self
1267 # ----------------------------------------------------------------
1268 # Private methods
1269 # ----------------------------------------------------------------
1270 def _comparison(self, other, method):
1271 '''
1272 '''
1273 try:
1274 cv_converter = _ut_get_converter(self._ut_unit, other._ut_unit)
1275 except:
1276 raise ValueError(
1277 "Units are not compatible: {!r}, {!r}".format(self, other))
1279 if not cv_converter:
1280 _cv_free(cv_converter)
1281 raise ValueError(
1282 "Units are not compatible: {!r}, {!r}".format(self, other))
1284 y = _c_double(1.0)
1285 pointer = ctypes.pointer(y)
1286 _cv_convert_doubles(cv_converter,
1287 pointer,
1288 _c_size_t(1),
1289 pointer)
1290 _cv_free(cv_converter)
1292 return getattr(operator, method)(y.value, 1)
1294 def _new_reason_notvalid(self, reason):
1295 '''TODO
1297 '''
1298 _reason_notvalid = self._reason_notvalid
1299 if _reason_notvalid:
1300 self._reason_notvalid = _reason_notvalid+'; '+reason
1301 else:
1302 self._reason_notvalid = reason
1304 # ----------------------------------------------------------------
1305 # Attributes
1306 # ----------------------------------------------------------------
1307 @property
1308 def isreftime(self):
1309 '''True if the units are reference time units, False otherwise.
1311 Note that time units (such as ``'days'``) are not reference time
1312 units.
1314 .. seealso:: `isdimensionless`, `islongitude`, `islatitude`,
1315 `ispressure`, `istime`
1317 **Examples:**
1319 >>> Units('days since 2000-12-1 03:00').isreftime
1320 True
1321 >>> Units('hours since 2100-1-1', calendar='noleap').isreftime
1322 True
1323 >>> Units(calendar='360_day').isreftime
1324 True
1325 >>> Units('days').isreftime
1326 False
1327 >>> Units('kg').isreftime
1328 False
1329 >>> Units().isreftime
1330 False
1332 '''
1333 return self._isreftime
1335 @property
1336 def iscalendartime(self):
1337 '''True if the units are calendar time units, False otherwise.
1339 Note that regular time units (such as ``'days'``) are not calendar
1340 time units.
1342 .. seealso:: `isdimensionless`, `islongitude`, `islatitude`,
1343 `ispressure`, `isreftime`, `istime`
1345 **Examples:**
1347 >>> Units('calendar_months').iscalendartime
1348 True
1349 >>> Units('calendar_years').iscalendartime
1350 True
1351 >>> Units('days').iscalendartime
1352 False
1353 >>> Units('km s-1').iscalendartime
1354 False
1355 >>> Units('kg').isreftime
1356 False
1357 >>> Units('').isreftime
1358 False
1359 >>> Units().isreftime
1360 False
1362 '''
1363 return bool(_ut_are_convertible(self._ut_unit, _calendartime_ut_unit))
1365 @property
1366 def isdimensionless(self):
1367 '''True if the units are dimensionless, false otherwise.
1369 .. seealso:: `islongitude`, `islatitude`, `ispressure`, `isreftime`,
1370 `istime`
1372 **Examples:**
1374 >>> Units('').isdimensionless
1375 True
1376 >>> Units('1').isdimensionless
1377 True
1378 >>> Units('100').isdimensionless
1379 True
1380 >>> Units('m/m').isdimensionless
1381 True
1382 >>> Units('m km-1').isdimensionless
1383 True
1384 >>> Units().isdimensionless
1385 False
1386 >>> Units('m').isdimensionless
1387 False
1388 >>> Units('m/s').isdimensionless
1389 False
1390 >>> Units('days since 2000-1-1', calendar='noleap').isdimensionless
1391 False
1393 '''
1394 return bool(_ut_are_convertible(self._ut_unit, _dimensionless_unit_one))
1396 @property
1397 def ispressure(self):
1398 '''True if the units are pressure units, false otherwise.
1400 .. seealso:: `isdimensionless`, `islongitude`, `islatitude`,
1401 `isreftime`, `istime`
1403 **Examples:**
1405 >>> Units('bar').ispressure
1406 True
1407 >>> Units('hPa').ispressure
1408 True
1409 >>> Units('meter^-1-kilogram-second^-2').ispressure
1410 True
1411 >>> Units('hours since 2100-1-1', calendar='noleap').ispressure
1412 False
1414 '''
1415 ut_unit = self._ut_unit
1416 if ut_unit is None:
1417 return False
1419 return bool(_ut_are_convertible(ut_unit, _pressure_ut_unit))
1421 @property
1422 def islatitude(self):
1423 '''True if and only if the units are latitude units.
1425 This is the case if and only if the `units` attribute is one of
1426 ``'degrees_north'``, ``'degree_north'``, ``'degree_N'``,
1427 ``'degrees_N'``, ``'degreeN'``, and ``'degreesN'``.
1429 Note that units of ``'degrees'`` are not latitude units.
1431 .. seealso:: `isdimensionless`, `islongitude`, `ispressure`,
1432 `isreftime`, `istime`
1434 **Examples:**
1436 >>> Units('degrees_north').islatitude
1437 True
1438 >>> Units('degrees').islatitude
1439 False
1440 >>> Units('degrees_east').islatitude
1441 False
1442 >>> Units('kg').islatitude
1443 False
1444 >>> Units().islatitude
1445 False
1447 '''
1448 return self._units in ('degrees_north', 'degree_north', 'degree_N',
1449 'degrees_N', 'degreeN', 'degreesN')
1451 @property
1452 def islongitude(self):
1453 '''True if and only if the units are longitude units.
1455 This is the case if and only if the `units` attribute is one of
1456 ``'degrees_east'``, ``'degree_east'``, ``'degree_E'``,
1457 ``'degrees_E'``, ``'degreeE'``, and ``'degreesE'``.
1459 Note that units of ``'degrees'`` are not longitude units.
1461 .. seealso:: `isdimensionless`, `islatitude`, `ispressure`,
1462 `isreftime`, `istime`
1464 **Examples:**
1466 >>> Units('degrees_east').islongitude
1467 True
1468 >>> Units('degrees').islongitude
1469 False
1470 >>> Units('degrees_north').islongitude
1471 False
1472 >>> Units('kg').islongitude
1473 False
1474 >>> Units().islongitude
1475 False
1477 '''
1478 return self._units in ('degrees_east', 'degree_east', 'degree_E',
1479 'degrees_E', 'degreeE', 'degreesE')
1481 @property
1482 def istime(self):
1483 '''True if the units are time units, False otherwise.
1485 Note that reference time units (such as ``'days since
1486 2000-12-1'``) are not time units, nor are calendar years and
1487 calendar months.
1489 .. seealso:: `iscalendartime`, `isdimensionless`, `islongitude`,
1490 `islatitude`, `ispressure`, `isreftime`
1492 **Examples:**
1494 >>> Units('days').istime
1495 True
1496 >>> Units('seconds').istime
1497 True
1498 >>> Units('kg').istime
1499 False
1500 >>> Units().istime
1501 False
1502 >>> Units('hours since 2100-1-1', calendar='noleap').istime
1503 False
1504 >>> Units(calendar='360_day').istime
1505 False
1506 >>> Units('calendar_years').istime
1507 False
1508 >>> Units('calendar_months').istime
1509 False
1511 '''
1512 if self._isreftime:
1513 return False
1515 ut_unit = self._ut_unit
1516 if ut_unit is None:
1517 return False
1519 return bool(_ut_are_convertible(ut_unit, _day_ut_unit))
1521 @property
1522 def isvalid(self):
1523 '''Whether the units are valid.
1525 .. seealso:: `reason_notvalid`
1527 **Examples:**
1529 >>> u = Units('km')
1530 >>> u.isvalid
1531 True
1532 >>> u.reason_notvalid
1533 ''
1535 >>> u = Units('Bad Units')
1536 >>> u.isvalid
1537 False
1538 >>> u.reason_notvalid
1539 "Invalid units: 'Bad Units'; Not recognised by UDUNITS"
1540 >>> u = Units(days since 2000-1-1', calendar='Bad Calendar')
1541 >>> u.isvalid
1542 False
1543 >>> u.reason_notvalid
1544 "Invalid calendar='Bad Calendar'; calendar must be one of ['standard', 'gregorian', 'proleptic_gregorian', 'noleap', 'julian', 'all_leap', '365_day', '366_day', '360_day'], got 'bad calendar'"
1546 '''
1547 return getattr(self, '_isvalid', False)
1549 @property
1550 def reason_notvalid(self):
1551 '''The reason for invalid units.
1553 If the units are valid then the reason is an empty string.
1555 .. seealso:: `isvalid`
1557 **Examples:**
1559 >>> u = Units('km')
1560 >>> u.isvalid
1561 True
1562 >>> u.reason_notvalid
1563 ''
1565 >>> u = Units('Bad Units')
1566 >>> u.isvalid
1567 False
1568 >>> u.reason_notvalid
1569 "Invalid units: 'Bad Units'; Not recognised by UDUNITS"
1571 >>> u = Units(days since 2000-1-1', calendar='Bad Calendar')
1572 >>> u.isvalid
1573 False
1574 >>> u.reason_notvalid
1575 "Invalid calendar='Bad Calendar'; calendar must be one of ['standard', 'gregorian', 'proleptic_gregorian', 'noleap', 'julian', 'all_leap', '365_day', '366_day', '360_day'], got 'bad calendar'"
1577 '''
1578 return getattr(self, '_reason_notvalid', '')
1580 @property
1581 def reftime(self):
1582 '''The reference date-time of reference time units.
1584 .. seealso:: `calendar`, `isreftime`, `units`
1586 **Examples:**
1588 >>> Units('days since 1900-1-1').reftime
1589 <Datetime: 1900-01-01 00:00:00>
1590 >>> str(Units('days since 1900-1-1 03:00').reftime)
1591 '1900-01-01 03:00:00'
1593 TODO
1594 '''
1595 if self.isreftime:
1596 utime = self._utime
1597 if utime:
1598 origin = utime.origin
1599 if origin:
1600 return origin
1601 else:
1602 # Some refrence date-times do not have a utime, such
1603 # as those defined by monts or years
1604 calendar = self._canonical_calendar
1605 return cftime.datetime(
1606 *cftime._parse_date(self.units.split(' since ')[1]),
1607 calendar=calendar
1608 )
1609 # --- End: if
1611 raise AttributeError(
1612 "{!r} has no attribute 'reftime'".format(self))
1614 @property
1615 def calendar(self):
1616 '''The calendar for reference time units.
1618 May be any string allowed by the calendar CF property.
1620 If it is unset then the default CF calendar is assumed when
1621 required.
1623 .. seealso:: `units`
1625 **Examples:**
1627 >>> Units(calendar='365_day').calendar
1628 '365_day'
1629 >>> Units('days since 2001-1-1', calendar='noleap').calendar
1630 'noleap'
1631 >>> Units('days since 2001-1-1').calendar
1632 AttributeError: Units has no attribute 'calendar'
1634 '''
1635 value = self._calendar
1636 if value is not None:
1637 return value
1639 raise AttributeError("%s has no attribute 'calendar'" %
1640 self.__class__.__name__)
1642 @property
1643 def units(self):
1644 '''The units.
1646 May be any string allowed by the units CF property.
1648 .. seealso:: `calendar`
1650 **Examples:**
1652 >>> Units('kg').units
1653 'kg'
1654 >>> Units('seconds').units
1655 'seconds'
1656 >>> Units('days since 2000-1-1', calendar='366_day').units
1657 'days since 2000-1-1'
1659 '''
1660 value = self._units
1661 if value is not None:
1662 return value
1664 raise AttributeError("'%s' object has no attribute 'units'" %
1665 self.__class__.__name__)
1667 # ----------------------------------------------------------------
1668 # Methods
1669 # ----------------------------------------------------------------
1670 def equivalent(self, other, verbose=False):
1671 '''Returns True if numeric values in one unit are convertible to
1672 numeric values in the other unit.
1674 .. seealso:: `equals`
1676 :Parameters:
1678 other: `Units`
1679 The other units.
1681 :Returns:
1683 `bool`
1684 True if the units are equivalent, False otherwise.
1686 **Examples:**
1688 >>> u = Units('m')
1689 >>> v = Units('km')
1690 >>> w = Units('s')
1692 >>> u.equivalent(v)
1693 True
1694 >>> u.equivalent(w)
1695 False
1697 >>> u = Units('days since 2000-1-1')
1698 >>> v = Units('days since 2000-1-1', calendar='366_day')
1699 >>> w = Units('seconds since 1978-3-12', calendar='gregorian)
1701 >>> u.equivalent(v)
1702 False
1703 >>> u.equivalent(w)
1704 True
1706 '''
1707# if not self.isvalid or not other.isvalid:
1708# return False
1710 isreftime1 = self._isreftime
1711 isreftime2 = other._isreftime
1713# if isreftime1 and isreftime2:
1714# # Both units are reference-time units
1715# if self._canonical_calendar != other._canonical_calendar:
1716# if verbose:
1717# print("{}: Incompatible calendars: {!r}, {!r}".format(
1718# self.__class__.__name__,
1719# self._calendar, other._calendar)) # pragma: no cover
1720# return False
1721#
1722# reftime0 = getattr(self, 'reftime', None)
1723# reftime1 = getattr(other, 'reftime', None)
1724# if reftime0 != reftime1:
1725# if verbose:
1726# print("{}: Different reference date-times: {!r}, {!r}".format(
1727# self.__class__.__name__,
1728# reftime0, reftime1)) # pragma: no cover
1729# return False
1730#
1731# elif isreftime1 or isreftime2:
1732# if verbose:
1733# print("{}: Only one is reference time".format(
1734# self.__class__.__name__)) # pragma: no cover
1735# return False
1738 if isreftime1 and isreftime2:
1739 # Both units are reference-time units
1740 units0 = self._units
1741 units1 = other._units
1742 if units0 and units1 or (not units0 and not units1):
1743 out = (self._canonical_calendar == other._canonical_calendar)
1744 if verbose and not out:
1745 print("{}: Incompatible calendars: {!r}, {!r}".format(
1746 self.__class__.__name__,
1747 self._calendar, other._calendar)) # pragma: no cover
1749 return out
1750 else:
1751 return False
1753 elif isreftime1 or isreftime2:
1754 if verbose:
1755 print("{}: Only one is reference time".format(
1756 self.__class__.__name__)) # pragma: no cover
1757 return False
1759# if not self.isvalid:
1760# if verbose:
1761# print("{}: {!r} is not valid".format(self.__class__.__name__, self)) # pragma: no cover
1762# return False
1763#
1764# if not other.isvalid:
1765# if verbose:
1766# print("{}: {!r} is not valid".format(self.__class__.__name__, other)) # pragma: no cover
1767# return False
1769# if isreftime1 and isreftime2:
1770# # Both units are reference-time units
1771# units0 = self._units
1772# units1 = other._units
1773# if units0 and units1 or (not units0 and not units1):
1774# return self._calendar == other._calendar
1775# else:
1776# return False
1777# # --- End: if
1779 # Still here?
1780 if not self and not other:
1781 # Both units are null and therefore equivalent
1782 return True
1784# if not isreftime1 and not isreftime2:
1785 # Both units are not reference-time units
1786 return bool(_ut_are_convertible(self._ut_unit, other._ut_unit))
1788 # Still here? Then units are not equivalent.
1789# return False
1791 def formatted(self, names=None, definition=None):
1792 '''Formats the string stored in the `units` attribute in a
1793 standardized manner. The `units` attribute is modified in place
1794 and its new value is returned.
1796 :Parameters:
1798 names: `bool`, optional
1799 Use unit names instead of symbols.
1801 definition: `bool`, optional
1802 The formatted string is given in terms of basic units
1803 instead of stopping any expansion at the highest level
1804 possible.
1806 :Returns:
1808 `str` or `None`
1809 The formatted string. If the units have not yet been set,
1810 then `None` is returned.
1812 **Examples:**
1814 >>> u = Units('W')
1815 >>> u.units
1816 'W'
1817 >>> u.units = u.format(names=True)
1818 >>> u.units
1819 'watt'
1820 >>> u.units = u.format(definition=True)
1821 >>> u.units
1822 'm2.kg.s-3'
1823 >>> u.units = u.format(names=True, definition=True)
1824 'meter^2-kilogram-second^-3'
1825 >>> u.units = u.format()
1826 >>> u.units
1827 'W'
1829 >>> u.units = 'dram'
1830 >>> u.format(names=True)
1831 '1.848345703125e-06 meter^3'
1833 Formatting is also available during object initialization:
1835 >>> u = Units('m/s', format=True)
1836 >>> u.units
1837 'm.s-1'
1839 >>> u = Units('dram', names=True)
1840 >>> u.units
1841 '1.848345703125e-06 m3'
1843 >>> u = Units('Watt')
1844 >>> u.units
1845 'Watt'
1847 >>> u = Units('Watt', formatted=True)
1848 >>> u.units
1849 'W'
1851 >>> u = Units('Watt', names=True)
1852 >>> u.units
1853 'watt'
1855 >>> u = Units('Watt', definition=True)
1856 >>> u.units
1857 'm2.kg.s-3'
1859 >>> u = Units('Watt', names=True, definition=True)
1860 >>> u.units
1861 'meter^2-kilogram-second^-3'
1863 '''
1864 ut_unit = self._ut_unit
1866 if ut_unit is None:
1867 return None
1869 opts = _UT_ASCII
1870 if names:
1871 opts |= _UT_NAMES
1872 if definition:
1873 opts |= _UT_DEFINITION
1875 if _ut_format(ut_unit, _string_buffer, _sizeof_buffer, opts) != -1:
1876 out = _string_buffer.value
1877 else:
1878 raise ValueError("Can't format unit {!r}".format(self))
1880 if self.isreftime:
1881 out = str(out)
1882 out += ' since ' + self.reftime.strftime()
1883 return out
1885 return out.decode('utf-8')
1887 @classmethod
1888 def conform(cls, x, from_units, to_units, inplace=False):
1889 '''Conform values in one unit to equivalent values in another,
1890 compatible unit.
1892 Returns the conformed values.
1894 The values may either be a `numpy` array, a python numeric type,
1895 or a `list` or `tuple`. The returned value is of the same type,
1896 except that input integers are converted to floats and python
1897 sequences are converted to `numpy` arrays (see the *inplace*
1898 keyword).
1900 .. warning:: Do not change the calendar of reference time units in
1901 the current version. Whilst this is possible, it will
1902 almost certainly result in an incorrect
1903 interpretation of the data or an error.
1905 :Parameters:
1907 x: `numpy.ndarray` or python numeric object
1909 from_units: `Units`
1910 The original units of *x*
1912 to_units: `Units`
1913 The units to which *x* should be conformed to.
1915 inplace: `bool`, optional
1916 If True and *x* is a `numpy` array then change it in place,
1917 creating no temporary copies, with one exception: If *x*
1918 is of integer type and the conversion is not null, then it
1919 will not be changed inplace and the returned conformed
1920 array will be of float type.
1922 If *x* is a `list` or `tuple` then the *inplace* parameter
1923 is ignored and a `numpy` array is returned.
1925 :Returns:
1927 `numpy.ndarray` or python numeric
1928 The modified numeric values.
1930 **Examples:**
1932 >>> Units.conform(2, Units('km'), Units('m'))
1933 2000.0
1935 >>> import numpy
1936 >>> a = numpy.arange(5.0)
1937 >>> Units.conform(a, Units('minute'), Units('second'))
1938 array([ 0., 60., 120., 180., 240.])
1939 >>> print(a)
1940 [ 0. 1. 2. 3. 4.]
1942 >>> Units.conform(a,
1943 Units('days since 2000-12-1'),
1944 Units('days since 2001-1-1'), inplace=True)
1945 array([-31., -30., -29., -28., -27.])
1946 >>> print(a)
1947 [-31. -30. -29. -28. -27.]
1949 '''
1950 if from_units.equals(to_units):
1951 if not isinstance(x, (int, float)):
1952 x = numpy_asanyarray(x)
1954 if inplace:
1955 return x
1956 else:
1957 try:
1958 return x.copy()
1959 except AttributeError:
1960 x
1961 # --- End: if
1963 if not from_units.equivalent(to_units):
1964 raise ValueError("Units are not convertible: {!r}, {!r}".format(
1965 from_units, to_units))
1967 ut_unit1 = from_units._ut_unit
1968 ut_unit2 = to_units._ut_unit
1970 if ut_unit1 is None or ut_unit2 is None:
1971 raise ValueError("Units are not convertible: {!r}, {!r}".format(
1972 from_units, to_units))
1974 convert = _ut_compare(ut_unit1, ut_unit2)
1976 if from_units._isreftime and to_units._isreftime:
1977 # --------------------------------------------------------
1978 # Both units are time-reference units, so calculate the
1979 # non-zero offset in units of days.
1980 # --------------------------------------------------------
1981 units0, reftime0 = from_units.units.split(' since ')
1982 units1, reftime1 = to_units.units.split(' since ')
1983 if units0 in _months_or_years:
1984 from_units = cls(
1985 'days since '+reftime0,
1986 calendar=getattr(from_units, 'calendar', None))
1987 x = numpy_asanyarray(x)
1988 if inplace:
1989 if units0 in ('month', 'months'):
1990 x *= _month_length
1991 else:
1992 x *= _year_length
1993 else:
1994 if units0 in ('month', 'months'):
1995 x = x * _month_length
1996 else:
1997 x = x * _year_length
1999 inplace = True
2001 ut_unit1 = from_units._ut_unit
2002 ut_unit2 = to_units._ut_unit
2004 convert = _ut_compare(ut_unit1, ut_unit2)
2006 if units1 in _months_or_years:
2007 to_units = cls('days since '+reftime1,
2008 calendar=getattr(to_units, 'calendar', None))
2010 offset = to_units._utime._jd0 - from_units._utime._jd0
2011 else:
2012 offset = 0
2014 # ------------------------------------------------------------
2015 # If the two units are identical then no need to alter the
2016 # value, so return it unchanged.
2017 # ------------------------------------------------------------
2018# if not convert and not offset:
2019# return x
2021 if convert:
2022 cv_converter = _ut_get_converter(ut_unit1, ut_unit2)
2023 if not cv_converter:
2024 _cv_free(cv_converter)
2025 raise ValueError(
2026 "Units are not convertible: {!r}, {!r}".format(
2027 from_units, to_units))
2028 # --- End: if
2030 # ------------------------------------------------------------
2031 # Find out if x is (or should be) a numpy array or a python
2032 # number
2033 # ------------------------------------------------------------
2034 if isinstance(x, numpy_generic):
2035 # Convert a generic numpy scalar to a 0-d numpy array
2036 x = numpy_array(x)
2037 x_is_numpy = True
2038 elif not isinstance(x, numpy_ndarray):
2039 if numpy_size(x) > 1 or len(numpy_shape(x)):
2040 # Convert a non-numpy (possibly nested) sequence to a
2041 # numpy array. E.g. [1], ((1.5, 2.5))
2042 x = numpy_asanyarray(x)
2043 x_is_numpy = True
2044 inplace = True
2045 else:
2046 x_is_numpy = False
2047 else:
2048 x_is_numpy = True
2050 if x_is_numpy:
2051 if not x.flags.contiguous:
2052 x = numpy_array(x, order='C')
2053#ARRRGGHH dch
2055 # --------------------------------------------------------
2056 # Convert an integer numpy array to a float numpy array
2057 # --------------------------------------------------------
2058 if inplace:
2059 if x.dtype.kind is 'i':
2060 if x.dtype.char is 'i':
2061 y = x.view(dtype='float32')
2062 y[...] = x
2063 x.dtype = numpy_dtype('float32')
2064 elif x.dtype.char is 'l':
2065 y = x.view(dtype=float)
2066 y[...] = x
2067 x.dtype = numpy_dtype(float)
2068 else:
2069 # At numpy vn1.7 astype has many more keywords ...
2070 if x.dtype.kind is 'i':
2071 if x.dtype.char is 'i':
2072 x = x.astype('float32')
2073 elif x.dtype.char is 'l':
2074 x = x.astype(float)
2075 else:
2076 x = x.copy()
2077 # --- End: if
2079 # ------------------------------------------------------------
2080 # Convert the array to the new units
2081 # ------------------------------------------------------------
2082 if convert:
2084 if x_is_numpy:
2085 # Create a pointer to the array cast to the
2086 # appropriate ctypes object
2087 itemsize = x.dtype.itemsize
2088 pointer = x.ctypes.data_as(_ctypes_POINTER[itemsize])
2090 # Convert the array in place
2091 _cv_convert_array[itemsize](cv_converter,
2092 pointer,
2093 _c_size_t(x.size),
2094 pointer)
2095 else:
2096 # Create a pointer to the number cast to a ctypes
2097 # double object.
2098 y = _c_double(x)
2099 pointer = ctypes.pointer(y)
2100 # Convert the pointer
2101 _cv_convert_doubles(cv_converter,
2102 pointer,
2103 _c_size_t(1),
2104 pointer)
2105 # Reset the number
2106 x = y.value
2108 _cv_free(cv_converter)
2110 # ------------------------------------------------------------
2111 # Apply an offset for reference-time units
2112 # ------------------------------------------------------------
2113 if offset:
2114 # Convert the offset from 'days' to the correct units and
2115 # subtract it from x
2116 if _ut_compare(_day_ut_unit, ut_unit2):
2118 cv_converter = _ut_get_converter(_day_ut_unit, ut_unit2)
2119 scale = numpy_array(1.0)
2120 pointer = scale.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
2121 _cv_convert_doubles(cv_converter,
2122 pointer,
2123 _c_size_t(scale.size),
2124 pointer)
2125 _cv_free(cv_converter)
2127 offset *= scale.item()
2129 x -= offset
2131 return x
2133 def copy(self):
2134 '''Return a deep copy.
2136 Equivalent to ``copy.deepcopy(u)``.
2138 :Returns:
2140 The deep copy.
2142 **Examples:**
2144 >>> v = u.copy()
2146 '''
2147 return self
2149 def equals(self, other, rtol=None, atol=None, verbose=False):
2150 '''Return True if and only if numeric values in one unit are
2151 convertible to numeric values in the other unit and their
2152 conversion is a scale factor of 1.
2154 .. seealso:: `equivalent`
2156 :Parameters:
2158 other: `Units`
2159 The other units.
2161 :Returns:
2163 `bool`
2164 `True` if the units are equal, `False` otherwise.
2166 **Examples:**
2168 >>> u = Units('km')
2169 >>> v = Units('1000m')
2170 >>> w = Units('100000m')
2171 >>> u.equals(v)
2172 True
2173 >>> u.equals(w)
2174 False
2176 >>> u = Units('m s-1')
2177 >>> m = Units('m')
2178 >>> s = Units('s')
2179 >>> u.equals(m)
2180 False
2181 >>> u.equals(m/s)
2182 True
2183 >>> (m/s).equals(u)
2184 True
2186 Undefined units are considered equal:
2188 >>> u = Units()
2189 >>> v = Units()
2190 >>> u.equals(v)
2191 True
2193 '''
2194 isreftime1 = self._isreftime
2195 isreftime2 = other._isreftime
2197 if isreftime1 and isreftime2:
2198 # Both units are reference-time units
2199 if self._canonical_calendar != other._canonical_calendar:
2200 if verbose:
2201 print("{}: Incompatible calendars: {!r}, {!r}".format(
2202 self.__class__.__name__,
2203 self._calendar, other._calendar)) # pragma: no cover
2205 return False
2207 reftime0 = getattr(self, 'reftime', None)
2208 reftime1 = getattr(other, 'reftime', None)
2209 if reftime0 != reftime1:
2210 if verbose:
2211 print("{}: Different reference date-times: "
2212 "{!r}, {!r}".format(
2213 self.__class__.__name__,
2214 reftime0, reftime1)) # pragma: no cover
2216 return False
2218 elif isreftime1 or isreftime2:
2219 if verbose:
2220 print("{}: Only one is reference time".format(
2221 self.__class__.__name__)) # pragma: no cover
2223 return False
2226# utime0 = self._utime
2227# utime1 = other._utime
2228# if utime0 is not None and utime1 is not None:
2229# return utime0.origin_equals(utime1)
2230# elif utime0 is None and utime1 is None:
2231# return self.reftime
2232#
2233#
2234#
2235# units0 = self._units
2236# units1 = other._units
2237# if units0 and units1 or (not units0 and not units1):
2238# out = (self._canonical_calendar == other._canonical_calendar)
2239# if verbose and not out:
2240# print("{}: Incompatible calendars: {!r}, {!r}".format(
2241# self.__class__.__name__,
2242# self._calendar, other._calendar)) # pragma: no cover
2243#
2244# return out
2245# else:
2246# return False
2247# # --- End: if
2248# if not self.isvalid:
2249# if verbose:
2250# print("{}: {!r} is not valid".format(self.__class__.__name__, self)) # pragma: no cover
2251# return False
2252#
2253# if not other.isvalid:
2254# if verbose:
2255# print("{}: {!r} is not valid".format(self.__class__.__name__, other)) # pragma: no cover
2256# return False
2257#
2259# if not self.isvalid or not other.isvalid:
2260# print ('ppp')
2261# return False
2263 try:
2264 if not _ut_compare(self._ut_unit, other._ut_unit):
2265 return True
2267 if verbose:
2268 print("{}: Different units: {!r}, {!r}".format(
2269 self.__class__.__name__,
2270 self.units, other.units)) # pragma: no cover
2272 return False
2273 except AttributeError:
2274 return False
2276#isreftime1 = self._isreftime
2277# isreftime2 = other._isreftime
2278#
2279# if not isreftime1 and not isreftime2:
2280# # Neither units is reference-time so they're equal
2281# return True
2282#
2283# if isreftime1 and isreftime2:
2284# # Both units are reference-time
2285# utime0 = self._utime
2286# utime1 = other._utime
2287# if utime0.calendar != utime1.calendar:
2288# return False
2289#
2290# return utime0.origin_equals(utime1)
2292 # One unit is a reference-time and the other is not so they're
2293 # not equal
2294# return False
2296 def log(self, base):
2297 '''Return the logarithmic unit corresponding to the given logarithmic
2298 base.
2300 :Parameters:
2302 base: `int` or `float`
2303 The logarithmic base.
2305 :Returns:
2307 `Units`
2308 The logarithmic unit corresponding to the given
2309 logarithmic base.
2311 **Examples:**
2313 >>> u = Units('W', names=True)
2314 >>> u
2315 <Units: watt>
2317 >>> u.log(10)
2318 <Units: lg(re 1 W)>
2319 >>> u.log(2)
2320 <Units: lb(re 1 W)>
2322 >>> import math
2323 >>> u.log(math.e)
2324 <Units: ln(re 1 W)>
2326 >>> u.log(3.5)
2327 <Units: 0.798235600147928 ln(re 1 W)>
2329 '''
2330 try:
2331 _ut_unit = _ut_log(_c_double(base), self._ut_unit)
2332 except TypeError:
2333 pass
2334 else:
2335 if _ut_unit:
2336 return type(self)(_ut_unit=_ut_unit)
2337 # --- End: try
2339 raise ValueError(
2340 "Can't take the logarithm to the base {!r} of {!r}".format(
2341 base, self))
2343# --- End: class
2346class Utime(cftime.utime):
2347 '''Performs conversions of netCDF time coordinate data to/from
2348 datetime objects.
2350 This object is (currently) functionally equivalent to a
2351 `netCDF4.netcdftime.utime` object.
2353 **Attributes**
2355 ============== ==================================================
2356 Attribute Description
2357 ============== ==================================================
2358 `!_jd0`
2359 `!calendar` The calendar used in the time calculation.
2360 `!origin` A date/time object for the reference time.
2361 `!tzoffset` Time zone offset in minutes.
2362 `!unit_string`
2363 `!units`
2364 ============== ==================================================
2366 '''
2367 def __init__(self, calendar, unit_string=None,
2368 only_use_cftime_datetimes=True):
2369 '''**Initialization**
2371 :Parameters:
2373 calendar: `str`
2374 The calendar used in the time calculations. Must be one
2375 of: ``'gregorian'``, ``'360_day'``, ``'365_day'``,
2376 ``'366_day'``, ``'julian'``, ``'proleptic_gregorian'``,
2377 although this is not checked.
2379 unit_string: `str`, optional
2380 A string of the form "time-units since <time-origin>"
2381 defining the reference-time units.
2383 only_use_cftime_datetimes: `bool`, optional
2384 If False, datetime.datetime objects are returned from
2385 `num2date` where possible; By default. dates which
2386 subclass `cftime.datetime` are returned for all calendars.
2388 '''
2389 if unit_string:
2390 super().__init__(
2391 unit_string, calendar,
2392 only_use_cftime_datetimes=only_use_cftime_datetimes)
2393 else:
2394 self.calendar = calendar
2395 self._jd0 = None
2396 self.origin = None
2397 self.tzoffset = None
2398 self.unit_string = None
2399 self.units = None
2401 def __repr__(self):
2402 '''x.__repr__() <==> repr(x)
2404 '''
2405 unit_string = self.unit_string
2406 if unit_string:
2407 x = [unit_string]
2408 else:
2409 x = []
2411 x.append(self.calendar)
2413 return "<Utime: {}>".format(' '.join(x))
2415 def num2date(self, time_value):
2416 '''Return a datetime-like object given a time value.
2418 The units of the time value are described by the `!unit_string`
2419 and `!calendar` attributes.
2421 See `netCDF4.netcdftime.utime.num2date` for details.
2423 In addition to `netCDF4.netcdftime.utime.num2date`, this method
2424 handles units of months and years as defined by Udunits, ie. 1
2425 year = 365.242198781 days, 1 month = 365.242198781/12 days.
2427 '''
2428 units = self.units
2429 unit_string = self.unit_string
2431 if units in ('month', 'months'):
2432 # Convert months to days
2433 unit_string = unit_string.replace(units, 'days', 1)
2434 time_value = numpy_array(time_value)*_month_length
2435 elif units in ('year', 'years', 'yr'):
2436 # Convert years to days
2437 unit_string = unit_string.replace(units, 'days', 1)
2438 time_value = numpy_array(time_value)*_year_length
2440 u = cftime.utime(unit_string, self.calendar)
2442 return u.num2date(time_value)
2444# def origin_equals(self, other):
2445# '''
2446#
2447# '''
2448# if self is other:
2449# return True
2450# else:
2451# return (self._jd0 == other._jd0 and
2452# self.calendar == other.calendar and
2453# self.tzoffset == other.tzoffset)
2455# --- End: class