Coverage for /home/sadie/cfunits/cfunits/units.py : 74%

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 datetime
4import operator
5import sys
7from copy import deepcopy
9from numpy import array as numpy_array
10from numpy import asanyarray as numpy_asanyarray
11from numpy import dtype as numpy_dtype
12from numpy import generic as numpy_generic
13from numpy import ndarray as numpy_ndarray
14from numpy import shape as numpy_shape
15from numpy import size as numpy_size
18# --------------------------------------------------------------------
19# Aliases for ctypes
20# --------------------------------------------------------------------
21_sizeof_buffer = 257
22_string_buffer = ctypes.create_string_buffer(_sizeof_buffer)
23_c_char_p = ctypes.c_char_p
24_c_int = ctypes.c_int
25_c_uint = ctypes.c_uint
26_c_float = ctypes.c_float
27_c_double = ctypes.c_double
28_c_size_t = ctypes.c_size_t
29_c_void_p = ctypes.c_void_p
30_pointer = ctypes.pointer
31_POINTER = ctypes.POINTER
33_ctypes_POINTER = {4: _POINTER(_c_float),
34 8: _POINTER(_c_double)}
36# --------------------------------------------------------------------
37# Load the Udunits-2 library and read the database
38# --------------------------------------------------------------------
39# if sys.platform == 'darwin':
40# # This has been tested on Mac OSX 10.5.8 and 10.6.8
41# _udunits = ctypes.CDLL('libudunits2.0.dylib')
42# else:
43# # Linux
44# _udunits = ctypes.CDLL('libudunits2.so.0')
45_libpath = ctypes.util.find_library('udunits2')
46if _libpath is None:
47 raise FileNotFoundError(
48 "cfunits requires UNIDATA UDUNITS-2. Can't find the 'udunits2' "
49 "library."
50 )
52_udunits = ctypes.CDLL(_libpath)
54# Suppress "overrides prefixed-unit" messages. This also suppresses
55# all other error messages - so watch out!
56#
57# Messages may be turned back on by calling the module function
58# udunits_error_messages.
59# ut_error_message_handler ut_set_error_message_handler(
60# ut_error_message_handler handler);
61_ut_set_error_message_handler = _udunits.ut_set_error_message_handler
62_ut_set_error_message_handler.argtypes = (_c_void_p, )
63_ut_set_error_message_handler.restype = _c_void_p
65_ut_set_error_message_handler(_udunits.ut_ignore)
67# Read the data base
68# ut_system* ut_read_xml(const char* path);
69_ut_read_xml = _udunits.ut_read_xml
70_ut_read_xml.argtypes = (_c_char_p, )
71_ut_read_xml.restype = _c_void_p
73# print('units: before _udunits.ut_read_xml(',_unit_database,')')
74_ut_system = _ut_read_xml(None)
75# print('units: after _udunits.ut_read_xml(',_unit_database,')')
77# Reinstate the reporting of error messages
78# _ut_set_error_message_handler(_udunits.ut_write_to_stderr)
80# --------------------------------------------------------------------
81# Aliases for the UDUNITS-2 C API. See
82# http://www.unidata.ucar.edu/software/udunits/udunits-2.0.4/udunits2lib.html
83# for documentation.
84# --------------------------------------------------------------------
85# int ut_format(const ut_unit* const unit, char* buf, size_t size,
86# unsigned opts);
87_ut_format = _udunits.ut_format
88_ut_format.argtypes = (_c_void_p, _c_char_p, _c_size_t, _c_uint)
89_ut_format.restype = _c_int
91# char* ut_trim(char* const string, const ut_encoding encoding);
92_ut_trim = _udunits.ut_trim
93# ut_encoding assumed to be int!:
94_ut_trim.argtypes = (_c_char_p, _c_int)
95_ut_trim.restype = _c_char_p
97# ut_unit* ut_parse(const ut_system* const system,
98# const char* const string, const ut_encoding encoding);
99_ut_parse = _udunits.ut_parse
100# ut_encoding assumed to be int!:
101_ut_parse.argtypes = (_c_void_p, _c_char_p, _c_int)
102_ut_parse.restype = _c_void_p
104# int ut_compare(const ut_unit* const unit1, const ut_unit* const
105# unit2);
106_ut_compare = _udunits.ut_compare
107_ut_compare.argtypes = (_c_void_p, _c_void_p)
108_ut_compare.restype = _c_int
110# int ut_are_convertible(const ut_unit* const unit1, const ut_unit*
111# const unit2);
112_ut_are_convertible = _udunits.ut_are_convertible
113_ut_are_convertible.argtypes = (_c_void_p, _c_void_p)
114_ut_are_convertible.restype = _c_int
116# cv_converter* ut_get_converter(ut_unit* const from, ut_unit* const
117# to);
118_ut_get_converter = _udunits.ut_get_converter
119_ut_get_converter.argtypes = (_c_void_p, _c_void_p)
120_ut_get_converter.restype = _c_void_p
122# ut_unit* ut_divide(const ut_unit* const numer, const ut_unit* const
123# denom);
124_ut_divide = _udunits.ut_divide
125_ut_divide.argtypes = (_c_void_p, _c_void_p)
126_ut_divide.restype = _c_void_p
128# ut_unit* ut_get_name(const ut_unit* unit, ut_encoding encoding)
129_ut_get_name = _udunits.ut_get_name
130_ut_get_name.argtypes = (_c_void_p, _c_int) # ut_encoding assumed to be int!
131_ut_get_name.restype = _c_char_p
133# ut_unit* ut_offset(const ut_unit* const unit, const double offset);
134_ut_offset = _udunits.ut_offset
135_ut_offset.argtypes = (_c_void_p, _c_double)
136_ut_offset.restype = _c_void_p
138# ut_unit* ut_raise(const ut_unit* const unit, const int power);
139_ut_raise = _udunits.ut_raise
140_ut_raise.argtypes = (_c_void_p, _c_int)
141_ut_raise.restype = _c_void_p
143# ut_unit* ut_scale(const double factor, const ut_unit* const unit);
144_ut_scale = _udunits.ut_scale
145_ut_scale.argtypes = (_c_double, _c_void_p)
146_ut_scale.restype = _c_void_p
148# ut_unit* ut_multiply(const ut_unit* const unit1, const ut_unit*
149# const unit2);
150_ut_multiply = _udunits.ut_multiply
151_ut_multiply.argtypes = (_c_void_p, _c_void_p)
152_ut_multiply.restype = _c_void_p
154# ut_unit* ut_log(const double base, const ut_unit* const reference);
155_ut_log = _udunits.ut_log
156_ut_log.argtypes = (_c_double, _c_void_p)
157_ut_log.restype = _c_void_p
159# ut_unit* ut_root(const ut_unit* const unit, const int root);
160_ut_root = _udunits.ut_root
161_ut_root.argtypes = (_c_void_p, _c_int)
162_ut_root.restype = _c_void_p
164# void ut_free_system(ut_system* system);
165_ut_free = _udunits.ut_free
166_ut_free.argypes = (_c_void_p, )
167_ut_free.restype = None
169# float* cv_convert_floats(const cv_converter* converter, const float*
170# const in, const size_t count, float* out);
171_cv_convert_floats = _udunits.cv_convert_floats
172_cv_convert_floats.argtypes = (_c_void_p, _c_void_p, _c_size_t, _c_void_p)
173_cv_convert_floats.restype = _c_void_p
175# double* cv_convert_doubles(const cv_converter* converter, const
176# double* const in, const size_t count,
177# double* out);
178_cv_convert_doubles = _udunits.cv_convert_doubles
179_cv_convert_doubles.argtypes = (_c_void_p, _c_void_p, _c_size_t, _c_void_p)
180_cv_convert_doubles.restype = _c_void_p
182# double cv_convert_double(const cv_converter* converter, const double
183# value);
184_cv_convert_double = _udunits.cv_convert_double
185_cv_convert_double.argtypes = (_c_void_p, _c_double)
186_cv_convert_double.restype = _c_double
188# void cv_free(cv_converter* const conv);
189_cv_free = _udunits.cv_free
190_cv_free.argtypes = (_c_void_p, )
191_cv_free.restype = None
193_UT_ASCII = 0
194_UT_NAMES = 4
195_UT_DEFINITION = 8
197_cv_convert_array = {4: _cv_convert_floats,
198 8: _cv_convert_doubles}
200# Some function definitions necessary for the following
201# changes to the unit system.
202_ut_get_unit_by_name = _udunits.ut_get_unit_by_name
203_ut_get_unit_by_name.argtypes = (_c_void_p, _c_char_p)
204_ut_get_unit_by_name.restype = _c_void_p
205_ut_get_status = _udunits.ut_get_status
206_ut_get_status.restype = _c_int
207_ut_unmap_symbol_to_unit = _udunits.ut_unmap_symbol_to_unit
208_ut_unmap_symbol_to_unit.argtypes = (_c_void_p, _c_char_p, _c_int)
209_ut_unmap_symbol_to_unit.restype = _c_int
210_ut_map_symbol_to_unit = _udunits.ut_map_symbol_to_unit
211_ut_map_symbol_to_unit.argtypes = (_c_char_p, _c_int, _c_void_p)
212_ut_map_symbol_to_unit.restype = _c_int
213_ut_map_unit_to_symbol = _udunits.ut_map_unit_to_symbol
214_ut_map_unit_to_symbol.argtypes = (_c_void_p, _c_char_p, _c_int)
215_ut_map_unit_to_symbol.restype = _c_int
216_ut_map_name_to_unit = _udunits.ut_map_name_to_unit
217_ut_map_name_to_unit.argtypes = (_c_char_p, _c_int, _c_void_p)
218_ut_map_name_to_unit.restype = _c_int
219_ut_map_unit_to_name = _udunits.ut_map_unit_to_name
220_ut_map_unit_to_name.argtypes = (_c_void_p, _c_char_p, _c_int)
221_ut_map_unit_to_name.restype = _c_int
222_ut_new_base_unit = _udunits.ut_new_base_unit
223_ut_new_base_unit.argtypes = (_c_void_p, )
224_ut_new_base_unit.restype = _c_void_p
226# Change Sv mapping. Both sievert and sverdrup are just aliases,
227# so no unit to symbol mapping needs to be changed.
228# We don't need to remove rem, since it was constructed with
229# the correct sievert mapping in place; because that mapping
230# was only an alias, the unit now doesn't depend on the mapping
231# persisting.
232assert(0 == _ut_unmap_symbol_to_unit(_ut_system, _c_char_p(b'Sv'), _UT_ASCII))
233assert(0 == _ut_map_symbol_to_unit(
234 _c_char_p(b'Sv'),
235 _UT_ASCII,
236 _ut_get_unit_by_name(_ut_system, _c_char_p(b'sverdrup'))
237))
239# Add new base unit calendar_year
240calendar_year_unit = _ut_new_base_unit(_ut_system)
241assert(0 == _ut_map_symbol_to_unit(
242 _c_char_p(b'cY'), _UT_ASCII, calendar_year_unit))
243assert(0 == _ut_map_unit_to_symbol(
244 calendar_year_unit, _c_char_p(b'cY'), _UT_ASCII))
245assert(0 == _ut_map_name_to_unit(
246 _c_char_p(b'calendar_year'), _UT_ASCII, calendar_year_unit))
247assert(0 == _ut_map_unit_to_name(
248 calendar_year_unit, _c_char_p(b'calendar_year'), _UT_ASCII))
249assert(0 == _ut_map_name_to_unit(
250 _c_char_p(b'calendar_years'), _UT_ASCII, calendar_year_unit))
253# Add various aliases useful for CF
254def add_unit_alias(definition, symbol, singular, plural):
255 unit = _ut_parse(
256 _ut_system, _c_char_p(definition.encode('utf-8')), _UT_ASCII)
257 if symbol is not None:
258 assert(0 == _ut_map_symbol_to_unit(
259 _c_char_p(symbol.encode('utf-8')), _UT_ASCII, unit))
260 if singular is not None:
261 assert(0 == _ut_map_name_to_unit(
262 _c_char_p(singular.encode('utf-8')), _UT_ASCII, unit))
263 if plural is not None:
264 assert(0 == _ut_map_name_to_unit(
265 _c_char_p(plural.encode('utf-8')), _UT_ASCII, unit))
268add_unit_alias(
269 "1.e-3", "psu", "practical_salinity_unit", "practical_salinity_units")
270add_unit_alias("calendar_year/12", "cM", "calendar_month", "calendar_months")
271add_unit_alias("1", None, "level", "levels")
272add_unit_alias("1", None, "layer", "layers")
273add_unit_alias("1", None, "sigma_level", "sigma_levels")
274add_unit_alias("1", "dB", "decibel", "debicels")
275add_unit_alias("10 dB", None, "bel", "bels")
277# _udunits.ut_get_unit_by_name(_udunits.ut_new_base_unit(_ut_system),
278# _ut_system, 'calendar_year')
280# --------------------------------------------------------------------
281# Create a calendar year unit
282# --------------------------------------------------------------------
283# _udunits.ut_map_name_to_unit('calendar_year', _UT_ASCII,
284# _udunits.ut_new_base_unit(_ut_system))
286# --------------------------------------------------------------------
287# Aliases for netCDF4.netcdftime classes
288# --------------------------------------------------------------------
289import cftime
290# _netCDF4_netcdftime_utime = cftime.utime
291# _datetime = cftime.datetime
293# --------------------------------------------------------------------
294# Aliases for netCDF4.netcdftime functions
295# --------------------------------------------------------------------
296_num2date = cftime.num2date
297_date2num = cftime.date2num
299_cached_ut_unit = {}
300_cached_utime = {}
302# --------------------------------------------------------------------
303# Save some useful units
304# --------------------------------------------------------------------
305# A time ut_unit (equivalent to 'day', 'second', etc.)
306_day_ut_unit = _ut_parse(_ut_system, _c_char_p(b'day'), _UT_ASCII)
307_cached_ut_unit['days'] = _day_ut_unit
308# A pressure ut_unit (equivalent to 'Pa', 'hPa', etc.)
309_pressure_ut_unit = _ut_parse(_ut_system, _c_char_p(b'pascal'), _UT_ASCII)
310_cached_ut_unit['pascal'] = _pressure_ut_unit
311# A calendar time ut_unit (equivalent to 'cY', 'cM')
312_calendartime_ut_unit = _ut_parse(
313 _ut_system, _c_char_p(b'calendar_year'), _UT_ASCII)
314_cached_ut_unit['calendar_year'] = _calendartime_ut_unit
315# A dimensionless unit one (equivalent to '', '1', '2', etc.)
316# _dimensionless_unit_one = _udunits.ut_get_dimensionless_unit_one(_ut_system)
317# _cached_ut_unit[''] = _dimensionless_unit_one
318# _cached_ut_unit['1'] = _dimensionless_unit_one
320_dimensionless_unit_one = _ut_parse(_ut_system, _c_char_p(b'1'), _UT_ASCII)
321_cached_ut_unit[''] = _dimensionless_unit_one
322_cached_ut_unit['1'] = _dimensionless_unit_one
324# --------------------------------------------------------------------
325# Set the default calendar type according to the CF conventions
326# --------------------------------------------------------------------
327_default_calendar = 'gregorian'
328_canonical_calendar = {
329 'gregorian': 'gregorian',
330 'standard': 'gregorian',
331 'none': 'gregorian',
332 'proleptic_gregorian': 'proleptic_gregorian',
333 '360_day': '360_day',
334 'noleap': '365_day',
335 '365_day': '365_day',
336 'all_leap': '366_day',
337 '366_day': '366_day',
338 'julian': 'julian',
339}
341_months_or_years = ('month', 'months', 'year', 'years', 'yr')
343# # --------------------------------------------------------------------
344# # Set month lengths in days for non-leap years (_days_in_month[0,1:])
345# # and leap years (_days_in_month[1,1:])
346# # --------------------------------------------------------------------
347# _days_in_month = numpy_array(
348# [[-99, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
349# [-99, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]])
352# --------------------------------------------------------------------
353# Function to control Udunits error messages
354# --------------------------------------------------------------------
355def udunits_error_messages(flag):
356 '''Control the printing of error messages from Udunits, which are
357 turned off by default.
359 :Parameters:
361 flag: `bool`
362 Set to True to print Udunits error messages and False to
363 not print Udunits error messages.
365 :Returns:
367 `None`
369 **Examples:**
371 >>> udunits_error_messages(True)
372 >>> udunits_error_messages(False)
374 '''
375 if flag:
376 _ut_set_error_message_handler(_udunits.ut_write_to_stderr)
377 else:
378 _ut_set_error_message_handler(_udunits.ut_ignore)
380# def _month_length(year, month, calendar, _days_in_month=_days_in_month):
381# '''
382#
383# Find month lengths in days for each year/month pairing in the input
384# numpy arrays 'year' and 'month', both of which must have the same
385# shape. 'calendar' must be one of the standard CF calendar types.
386#
387# :Parameters:
388#
389#
390# '''
391# shape = month.shape
392# if calendar in ('standard', 'gregorian'):
393# leap = numpy_where(year % 4 == 0, 1, 0)
394# leap = numpy_where((year > 1582) &
395# (year % 100 == 0) & (year % 400 != 0),
396# 0, leap)
397# elif calendar == '360_day':
398# days_in_month = numpy_empty(shape)
399# days_in_month.fill(30)
400# return days_in_month
401#
402# elif calendar in ('all_leap', '366_day'):
403# leap = numpy_zeros(shape)
404#
405# elif calendar in ('no_leap', '365_day'):
406# leap = numpy_ones(shape)
407#
408# elif calendar == 'proleptic_gregorian':
409# leap = numpy_where(year % 4 == 0, 1, 0)
410# leap = numpy_where((year % 100 == 0) & (year % 400 != 0),
411# 0, leap)
412#
413# days_in_month = numpy_array([_days_in_month[l, m]
414# for l, m in zip(leap.flat, month.flat)])
415# days_in_month.resize(shape)
416#
417# return days_in_month
418#
419# def _proper_date(year, month, day, calendar, fix=False,
420# _days_in_month=_days_in_month):
421# '''
422#
423# Given equally shaped numpy arrays of 'year', 'month', 'day' adjust
424# them *in place* to be proper dates. 'calendar' must be one of the
425# standard CF calendar types.
426#
427# Excessive number of months are converted to years but excessive days
428# are not converted to months nor years. If a day is illegal in the
429# proper date then a ValueError is raised, unless 'fix' is True, in
430# which case the day is lowered to the nearest legal date:
431#
432# 2000/26/1 -> 2002/3/1
433#
434# 2001/2/31 -> ValueError if 'fix' is False
435# 2001/2/31 -> 2001/2/28 if 'fix' is True
436# 2001/2/99 -> 2001/2/28 if 'fix' is True
437#
438# :Parameters:
439#
440# '''
441# # y, month = divmod(month, 12)
442# # year += y
443#
444# year += month // 12
445# month[...] = month % 12
446#
447# mask = (month == 0)
448# year[...] = numpy_where(mask, year-1, year)
449# month[...] = numpy_where(mask, 12, month)
450# del mask
451#
452# days_in_month = _month_length(year, month, calendar,
453# _days_in_month=_days_in_month)
454#
455# if fix:
456# day[...] = numpy_where(day > days_in_month, days_in_month, day)
457# elif (day > days_in_month).any():
458# raise ValueError("Illegal date(s) in %s calendar" % calendar)
459#
460# return year, month, day
463# --------------------------------------------------------------------
464# Constants, as defined by UDUNITS
465# --------------------------------------------------------------------
466_year_length = 365.242198781
467_month_length = _year_length / 12
470class Units():
471 '''Store, combine and compare physical units and convert numeric
472 values to different units.
474 Units are as defined in UNIDATA's Udunits-2 package, with a few
475 exceptions for greater consistency with the CF conventions namely
476 support for CF calendars and new units definitions.
479 **Modifications to the standard Udunits database**
481 Whilst a standard Udunits-2 database may be used, greater
482 consistency with CF is achieved by using a modified database. The
483 following units are either new to, modified from, or removed from
484 the standard Udunits-2 database (version 2.1.24):
486 ======================= ====== ============ ==============
487 Unit name Symbol Definition Status
488 ======================= ====== ============ ==============
489 practical_salinity_unit psu 1e-3 New unit
490 level 1 New unit
491 sigma_level 1 New unit
492 layer 1 New unit
493 decibel dB 1 New unit
494 bel 10 dB New unit
495 sverdrup Sv 1e6 m3 s-1 Added symbol
496 sievert J kg-1 Removed symbol
497 ======================= ====== ============ ==============
499 Plural forms of the new units' names are allowed, such as
500 ``practical_salinity_units``.
502 The modified database is in the *udunits* subdirectory of the
503 *etc* directory found in the same location as this module.
506 **Accessing units**
508 Units may be set, retrieved and deleted via the `units`
509 attribute. Its value is a string that can be recognized by
510 UNIDATA's Udunits-2 package, with the few exceptions given in the
511 CF conventions.
513 >>> u = Units('m s-1')
514 >>> u
515 <Cf Units: 'm s-1'>
516 >>> u.units = 'days since 2004-3-1'
517 >>> u
518 <Units: days since 2004-3-1>
521 **Equality and equivalence of units**
523 There are methods for assessing whether two units are equivalent
524 or equal. Two units are equivalent if numeric values in one unit
525 are convertible to numeric values in the other unit (such as
526 ``kilometres`` and ``metres``). Two units are equal if they are
527 equivalent and their conversion is a scale factor of 1 and an
528 offset of 0 (such as ``kilometres`` and ``1000 metres``). Note
529 that equivalence and equality are based on internally stored
530 binary representations of the units, rather than their string
531 representations.
533 >>> u = Units('m/s')
534 >>> v = Units('m s-1')
535 >>> w = Units('km.s-1')
536 >>> x = Units('0.001 kilometer.second-1')
537 >>> y = Units('gram')
539 >>> u.equivalent(v), u.equals(v), u == v
540 (True, True, True)
541 >>> u.equivalent(w), u.equals(w)
542 (True, False)
543 >>> u.equivalent(x), u.equals(x)
544 (True, True)
545 >>> u.equivalent(y), u.equals(y)
546 (False, False)
549 **Time and reference time units**
551 Time units may be given as durations of time (*time units*) or as
552 an amount of time since a reference time (*reference time units*):
554 >>> v = Units()
555 >>> v.units = 's'
556 >>> v.units = 'day'
557 >>> v.units = 'days since 1970-01-01'
558 >>> v.units = 'seconds since 1992-10-8 15:15:42.5 -6:00'
560 .. note:: It is recommended that the units ``year`` and ``month``
561 be used with caution, as explained in the following
562 excerpt from the CF conventions: "The Udunits package
563 defines a year to be exactly 365.242198781 days (the
564 interval between 2 successive passages of the sun
565 through vernal equinox). It is not a calendar
566 year. Udunits includes the following definitions for
567 years: a common_year is 365 days, a leap_year is 366
568 days, a Julian_year is 365.25 days, and a Gregorian_year
569 is 365.2425 days. For similar reasons the unit
570 ``month``, which is defined to be exactly year/12,
571 should also be used with caution."
573 **Calendar**
575 The date given in reference time units is associated with one of
576 the calendars recognized by the CF conventions and may be set with
577 the `calendar` attribute. However, as in the CF conventions, if
578 the calendar is not set then, for the purposes of calculation and
579 comparison, it defaults to the mixed Gregorian/Julian calendar as
580 defined by Udunits:
582 >>> u = Units('days since 2000-1-1')
583 >>> u.calendar
584 AttributeError: Units has no attribute 'calendar'
585 >>> v = Units('days since 2000-1-1', 'gregorian')
586 >>> v.calendar
587 'gregorian'
588 >>> v.equals(u)
589 True
592 **Arithmetic with units**
594 The following operators, operations and assignments are
595 overloaded:
597 Comparison operators:
599 ``==, !=``
601 Binary arithmetic operations:
603 ``+, -, *, /, pow(), **``
605 Unary arithmetic operations:
607 ``-, +``
609 Augmented arithmetic assignments:
611 ``+=, -=, *=, /=, **=``
613 The comparison operations return a boolean and all other
614 operations return a new units object or modify the units object in
615 place.
617 >>> u = Units('m')
618 <Units: m>
620 >>> v = u * 1000
621 >>> v
622 <Units: 1000 m>
624 >>> u == v
625 False
626 >>> u != v
627 True
629 >>> u **= 2
630 >>> u
631 <Units: m2>
633 It is also possible to create the logarithm of a unit
634 corresponding to the given logarithmic base:
636 >>> u = Units('seconds')
637 >>> u.log(10)
638 <Units: lg(re 1 s)>
641 **Modifying data for equivalent units**
643 Any numpy array or python numeric type may be modified for
644 equivalent units using the `conform` static method.
646 >>> Units.conform(2, Units('km'), Units('m'))
647 2000.0
649 >>> import numpy
650 >>> a = numpy.arange(5.0)
651 >>> Units.conform(a, Units('minute'), Units('second'))
652 array([ 0., 60., 120., 180., 240.])
653 >>> a
654 array([ 0., 1., 2., 3., 4.])
656 If the *inplace* keyword is True, then a numpy array is modified
657 in place, without any copying overheads:
659 >>> Units.conform(a,
660 Units('days since 2000-12-1'),
661 Units('days since 2001-1-1'), inplace=True)
662 array([-31., -30., -29., -28., -27.])
663 >>> a
664 array([-31., -30., -29., -28., -27.])
666 '''
667 def __init__(self, units=None, calendar=None, formatted=False,
668 names=False, definition=False, _ut_unit=None):
669 '''**Initialization**
671 :Parameters:
673 units: `str` or `Units`, optional
674 Set the new units from this string.
676 calendar: `str`, optional
677 Set the calendar for reference time units.
679 formatted: `bool`, optional
680 Format the string representation of the units in a
681 standardized manner. See the `formatted` method.
683 names: `bool`, optional
684 Format the string representation of the units using names
685 instead of symbols. See the `formatted` method.
687 definition: `bool`, optional
688 Format the string representation of the units using basic
689 units. See the `formatted` method.
691 _ut_unit: `int`, optional
692 Set the new units from this Udunits binary unit
693 representation. This should be an integer returned by a
694 call to `ut_parse` function of Udunits. Ignored if `units`
695 is set.
697 '''
699 if isinstance(units, self.__class__):
700 self.__dict__ = units.__dict__
701 return
703 self._isvalid = True
704 self._reason_notvalid = ''
705 self._units = units
706 self._ut_unit = None
707 self._isreftime = False
708 self._calendar = calendar
709 self._canonical_calendar = None
710 self._utime = None
711 self._units_since_reftime = None
713 # Set the calendar
714 _calendar = None
715 if calendar is not None:
716 _calendar = _canonical_calendar.get(calendar.lower())
717 if _calendar is None:
718 self._new_reason_notvalid(
719 "Invalid calendar={!r}".format(calendar))
720 self._isvalid = False
721 _calendar = calendar
722 # --- End: if
724 if units is not None:
725 try:
726 units = units.strip()
727 except AttributeError:
728 self._isvalid = False
729 self._new_reason_notvalid(
730 "Bad units type: {}".format(type(units)))
731 return
733 unit = None
735 if isinstance(units, str) and ' since ' in units:
736 # ----------------------------------------------------
737 # Set a reference time unit
738 # ----------------------------------------------------
739 # Set the calendar
740 if calendar is None:
741 _calendar = _default_calendar
742 else:
743 _calendar = _canonical_calendar.get(calendar.lower())
744 if _calendar is None:
745 _calendar = calendar
746 # --- End: if
748 units_split = units.split(' since ')
749 unit = units_split[0].strip()
751 _units_since_reftime = unit
753 ut_unit = _cached_ut_unit.get(unit, None)
754 if ut_unit is None:
755 ut_unit = _ut_parse(_ut_system, _c_char_p(
756 unit.encode('utf-8')), _UT_ASCII)
757 if not ut_unit or not _ut_are_convertible(
758 ut_unit, _day_ut_unit):
759 ut_unit = None
760 self._isvalid = False
761 else:
762 _cached_ut_unit[unit] = ut_unit
763 # --- End: if
765 utime = None
767 if (_calendar, units) in _cached_utime:
768 utime = _cached_utime[(_calendar, units)]
769 else:
770 # Create a new Utime object
771 unit_string = '{} since {}'.format(
772 unit, units_split[1].strip())
774 if (_calendar, unit_string) in _cached_utime:
775 utime = _cached_utime[(_calendar, unit_string)]
776 else:
777 try:
778 utime = Utime(_calendar, unit_string)
779 except Exception as error:
780 utime = None
781 if unit in _months_or_years:
782 temp_unit_string = 'days since {}'.format(
783 units_split[1].strip())
784 try:
785 _ = Utime(_calendar, temp_unit_string)
786 except Exception as error:
787 self._new_reason_notvalid(str(error))
788 self._isvalid = False
789 else:
790 self._new_reason_notvalid(str(error))
791 self._isvalid = False
792 # --- End: try
794 _cached_utime[(_calendar, unit_string)] = utime
795 # --- End: if
797 self._isreftime = True
798 self._calendar = calendar
799 self._canonical_calendar = _calendar
800 self._utime = utime
802 else:
803 # ----------------------------------------------------
804 # Set a unit other than a reference time unit
805 # ----------------------------------------------------
806 ut_unit = _cached_ut_unit.get(units, None)
807 if ut_unit is None:
808 ut_unit = _ut_parse(_ut_system,
809 _c_char_p(units.encode('utf-8')),
810 _UT_ASCII)
811 if not ut_unit:
812 ut_unit = None
813 self._isvalid = False
814 self._new_reason_notvalid(
815 "Invalid units: {!r}; "
816 "Not recognised by UDUNITS".format(units))
817 else:
818 _cached_ut_unit[units] = ut_unit
819 # --- End: if
821# if ut_unit is None:
822# ut_unit = _ut_parse(
823# _ut_system, _c_char_p(units.encode('utf-8')),
824# _UT_ASCII
825# )
826# if not ut_unit:
827# raise ValueError(
828# "Can't set unsupported unit: %r" % units)
829# _cached_ut_unit[units] = ut_unit
831 self._isreftime = False
832 self._calendar = None
833 self._canonial_calendar = None
834 self._utime = None
836 self._ut_unit = ut_unit
837 self._units = units
838 self._units_since_reftime = unit
840 if formatted or names or definition:
841 self._units = self.formatted(names, definition)
843 return
845 elif calendar:
846 # ---------------------------------------------------------
847 # Calendar is set, but units are not.
848 # ---------------------------------------------------------
849 self._units = None
850 self._ut_unit = None
851 self._isreftime = True
852 self._calendar = calendar
853 self._canonical_calendar = _canonical_calendar[calendar.lower()]
854 self._units_since_reftime = None
855 try:
856 self._utime = Utime(_canonical_calendar[calendar.lower()])
857 except Exception as error:
858 self._new_reason_notvalid(
859 'Invalid calendar={!r}'.format(calendar))
860 self._isvalid = True
862 return
864 if _ut_unit is not None:
865 # ---------------------------------------------------------
866 # _ut_unit is set
867 # ---------------------------------------------------------
868 self._ut_unit = _ut_unit
869 self._isreftime = False
871 units = self.formatted(names, definition)
872 _cached_ut_unit[units] = _ut_unit
873 self._units = units
875 self._units_since_reftime = None
877 self._calendar = None
878 self._utime = None
880 return
882 # -------------------------------------------------------------
883 # Nothing has been set
884 # -------------------------------------------------------------
885 self._units = None
886 self._ut_unit = None
887 self._isreftime = False
888 self._calendar = None
889 self._canonical_calendar = None
890 self._utime = None
891 self._units_since_reftime = None
893 def __getstate__(self):
894 '''Called when pickling.
896 :Returns:
898 `dict`
899 A dictionary of the instance's attributes
901 **Examples:**
903 >>> u = Units('days since 3-4-5', calendar='gregorian')
904 >>> u.__getstate__()
905 {'calendar': 'gregorian',
906 'units': 'days since 3-4-5'}
908 '''
909 return dict([(attr, getattr(self, attr))
910 for attr in ('_units', '_calendar')
911 if hasattr(self, attr)])
913 def __setstate__(self, odict):
914 '''Called when unpickling.
916 :Parameters:
918 odict: `dict`
919 The output from the instance's `__getstate__` method.
921 :Returns:
923 `None`
925 '''
926 units = None
927 if '_units' in odict:
928 units = odict['_units']
930 calendar = None
931 if '_calendar' in odict:
932 calendar = odict['_calendar']
934 self.__init__(units=units, calendar=calendar)
936 def __hash__(self):
937 '''x.__hash__() <==> hash(x)
939 '''
940 if not self._isreftime:
941 return hash(('Units', self._ut_unit))
943 return hash(
944 ('Units', self._ut_unit, self._utime.origin,
945 self._utime.calendar)
946 )
948 def __repr__(self):
949 '''x.__repr__() <==> repr(x)
951 '''
952 return '<{0}: {1}>'.format(self.__class__.__name__, self)
954 def __str__(self):
955 '''x.__str__() <==> str(x)
957 '''
958 string = []
959 if self._units is not None:
960 if self._units == '':
961 string.append("\'\'")
962 else:
963 string.append(str(self._units))
964 # --- End: if
966 if self._calendar is not None:
967 string.append('{0}'.format(self._calendar))
969 return ' '.join(string)
971 def __deepcopy__(self, memo):
972 '''Used if copy.deepcopy is called on the variable.
974 '''
975 return self
977 def __bool__(self):
978 '''Truth value testing and the built-in operation ``bool``
980 x.__bool__() <==> x!=0
982 '''
983 return self._ut_unit is not None
985 def __eq__(self, other):
986 '''The rich comparison operator ``==``
988 x.__eq__(y) <==> x==y
990 '''
991 return self.equals(other)
993 def __ne__(self, other):
994 '''The rich comparison operator ``!=``
996 x.__ne__(y) <==> x!=y
998 '''
999 return not self.equals(other)
1001 def __gt__(self, other):
1002 '''The rich comparison operator ``>``
1004 x.__gt__(y) <==> x>y
1006 '''
1007 return self._comparison(other, '__gt__')
1009 def __ge__(self, other):
1010 '''The rich comparison operator ````
1012 x.__ge__(y) <==> x>y
1014 '''
1015 return self._comparison(other, '__ge__')
1017 def __lt__(self, other):
1018 '''The rich comparison operator ````
1020 x.__lt__(y) <==> x<y
1022 '''
1023 return self._comparison(other, '__lt__')
1025 def __le__(self, other):
1026 '''The rich comparison operator ````
1028 x.__le__(y) <==> x<=y
1030 '''
1031 return self._comparison(other, '__le__')
1033 def __sub__(self, other):
1034 '''The binary arithmetic operation ``-``
1036 x.__sub__(y) <==> x-y
1038 '''
1039 if (self._isreftime or
1040 (isinstance(other, self.__class__) and other._isreftime)):
1041 raise ValueError("Can't do {!r} - {!r}".format(self, other))
1043 try:
1044 _ut_unit = _ut_offset(self._ut_unit, _c_double(other))
1045 return type(self)(_ut_unit=_ut_unit)
1046 except:
1047 raise ValueError("Can't do {!r} - {!r}".format(self, other))
1049 def __add__(self, other):
1050 '''The binary arithmetic operation ``+``
1052 x.__add__(y) <==> x+y
1054 '''
1055 if (self._isreftime or
1056 (isinstance(other, self.__class__) and other._isreftime)):
1057 raise ValueError("Can't do {!r} + {!r}".format(self, other))
1059 try:
1060 _ut_unit = _ut_offset(self._ut_unit, _c_double(-other))
1061 return type(self)(_ut_unit=_ut_unit)
1062 except:
1063 raise ValueError("Can't do {!r} + {!r}".format(self, other))
1065 def __mul__(self, other):
1066 '''The binary arithmetic operation ``*``
1068 x.__mul__(y) <==> x*y
1070 '''
1071 if isinstance(other, self.__class__):
1072 if self._isreftime or other._isreftime:
1073 raise ValueError("Can't do {!r} * {!r}".format(self, other))
1075 try:
1076 ut_unit = _ut_multiply(self._ut_unit, other._ut_unit)
1077 except:
1078 raise ValueError("Can't do {!r} * {!r}".format(self, other))
1079 else:
1080 if self._isreftime:
1081 raise ValueError("Can't do {!r} * {!r}".format(self, other))
1083 try:
1084 ut_unit = _ut_scale(_c_double(other), self._ut_unit)
1085 except:
1086 raise ValueError("Can't do {!r} * {!r}".format(self, other))
1087 # --- End: if
1089 return type(self)(_ut_unit=ut_unit)
1091 def __div__(self, other):
1092 '''x.__div__(y) <==> x/y
1094 '''
1095 if isinstance(other, self.__class__):
1096 if self._isreftime or other._isreftime:
1097 raise ValueError("Can't do {!r} / {!r}".format(self, other))
1099 try:
1100 ut_unit = _ut_divide(self._ut_unit, other._ut_unit)
1101 except:
1102 raise ValueError("Can't do {!r} / {!r}".format(self, other))
1103 else:
1104 if self._isreftime:
1105 raise ValueError("Can't do {!r} / {!r}".format(self, other))
1107 try:
1108 ut_unit = _ut_scale(_c_double(1.0/other), self._ut_unit)
1109 except:
1110 raise ValueError("Can't do {!r} / {!r}".format(self, other))
1111 # --- End: if
1113 return type(self)(_ut_unit=ut_unit)
1115 def __pow__(self, other, modulo=None):
1116 '''The binary arithmetic operations ``**`` and ``pow``
1118 x.__pow__(y) <==> x**y
1120 '''
1121 # ------------------------------------------------------------
1122 # y must be either an integer or the reciprocal of a positive
1123 # integer.
1124 # ------------------------------------------------------------
1126 if modulo is not None:
1127 raise NotImplementedError(
1128 "3-argument power not supported for {!r}".format(
1129 self.__class__.__name__))
1131 if self and not self._isreftime:
1132 ut_unit = self._ut_unit
1133 try:
1134 return type(self)(_ut_unit=_ut_raise(ut_unit, _c_int(other)))
1135 except:
1136 pass
1138 if 0 < other <= 1:
1139 # If other is a float and (1/other) is a positive
1140 # integer then take the (1/other)-th root. E.g. if
1141 # other is 0.125 then we take the 8-th root.
1142 try:
1143 recip_other = 1/other
1144 root = int(recip_other)
1145 if recip_other == root:
1146 ut_unit = _ut_root(ut_unit, _c_int(root))
1147 if ut_unit is not None:
1148 return type(self)(_ut_unit=ut_unit)
1149 except:
1150 pass
1151 else:
1152 # If other is a float equal to its integer then raise
1153 # to the integer part. E.g. if other is 3.0 then we
1154 # raise to the power of 3; if other is -2.0 then we
1155 # raise to the power of -2
1156 try:
1157 root = int(other)
1158 if other == root:
1159 ut_unit = _ut_raise(ut_unit, _c_int(root))
1160 if ut_unit is not None:
1161 return type(self)(_ut_unit=ut_unit)
1162 except:
1163 pass
1164 # --- End: if
1166 raise ValueError("Can't do {!r} ** {!r}".format(self, other))
1168 def __isub__(self, other):
1169 '''x.__isub__(y) <==> x-=y
1171 '''
1172 return self - other
1174 def __iadd__(self, other):
1175 '''x.__iadd__(y) <==> x+=y
1177 '''
1178 return self + other
1180 def __imul__(self, other):
1181 '''The augmented arithmetic assignment ``*=``
1183 x.__imul__(y) <==> x*=y
1185 '''
1186 return self * other
1188 def __idiv__(self, other):
1189 '''The augmented arithmetic assignment ``/=``
1191 x.__idiv__(y) <==> x/=y
1193 '''
1194 return self / other
1196 def __ipow__(self, other):
1197 '''The augmented arithmetic assignment ``**=``
1199 x.__ipow__(y) <==> x**=y
1201 '''
1202 return self ** other
1204 def __rsub__(self, other):
1205 '''The binary arithmetic operation ``-`` with reflected operands
1207 x.__rsub__(y) <==> y-x
1209 '''
1210 try:
1211 return -self + other
1212 except:
1213 raise ValueError("Can't do {!r} - {!r}".format(other, self))
1215 def __radd__(self, other):
1216 '''The binary arithmetic operation ``+`` with reflected operands
1218 x.__radd__(y) <==> y+x
1220 '''
1221 return self + other
1223 def __rmul__(self, other):
1224 '''The binary arithmetic operation ``*`` with reflected operands
1226 x.__rmul__(y) <==> y*x
1228 '''
1229 return self * other
1231 def __rdiv__(self, other):
1232 '''x.__rdiv__(y) <==> y/x
1234 '''
1235 try:
1236 return (self ** -1) * other
1237 except:
1238 raise ValueError("Can't do {!r} / {!r}".format(other, self))
1240 def __floordiv__(self, other):
1241 '''x.__floordiv__(y) <==> x//y <==> x/y
1243 '''
1244 return self / other
1246 def __ifloordiv__(self, other):
1247 '''x.__ifloordiv__(y) <==> x//=y <==> x/=y
1249 '''
1250 return self / other
1252 def __rfloordiv__(self, other):
1253 '''x.__rfloordiv__(y) <==> y//x <==> y/x
1255 '''
1256 try:
1257 return (self ** -1) * other
1258 except:
1259 raise ValueError("Can't do {!r} // {!r}".format(other, self))
1261 def __truediv__(self, other):
1262 '''x.__truediv__(y) <==> x/y
1264 '''
1265 return self.__div__(other)
1267 def __itruediv__(self, other):
1268 '''x.__itruediv__(y) <==> x/=y
1270 '''
1271 return self.__idiv__(other)
1273 def __rtruediv__(self, other):
1274 '''x.__rtruediv__(y) <==> y/x
1276 '''
1277 return self.__rdiv__(other)
1279 def __mod__(self, other):
1280 '''TODO
1282 '''
1283 raise ValueError("Can't do {!r} % {!r}".format(other, self))
1285 def __neg__(self):
1286 '''The unary arithmetic operation ``-``
1288 x.__neg__() <==> -x
1290 '''
1291 return self * -1
1293 def __pos__(self):
1294 '''The unary arithmetic operation ``+``
1296 x.__pos__() <==> +x
1298 '''
1299 return self
1301 # ----------------------------------------------------------------
1302 # Private methods
1303 # ----------------------------------------------------------------
1304 def _comparison(self, other, method):
1305 '''
1306 '''
1307 try:
1308 cv_converter = _ut_get_converter(self._ut_unit, other._ut_unit)
1309 except:
1310 raise ValueError(
1311 "Units are not compatible: {!r}, {!r}".format(self, other))
1313 if not cv_converter:
1314 _cv_free(cv_converter)
1315 raise ValueError(
1316 "Units are not compatible: {!r}, {!r}".format(self, other))
1318 y = _c_double(1.0)
1319 pointer = ctypes.pointer(y)
1320 _cv_convert_doubles(cv_converter,
1321 pointer,
1322 _c_size_t(1),
1323 pointer)
1324 _cv_free(cv_converter)
1326 return getattr(operator, method)(y.value, 1)
1328 def _new_reason_notvalid(self, reason):
1329 '''TODO
1331 '''
1332 _reason_notvalid = self._reason_notvalid
1333 if _reason_notvalid:
1334 self._reason_notvalid = _reason_notvalid+'; '+reason
1335 else:
1336 self._reason_notvalid = reason
1338 # ----------------------------------------------------------------
1339 # Attributes
1340 # ----------------------------------------------------------------
1341 @property
1342 def has_offset(self):
1343 '''True if the units contain an offset.
1345 Note that if a multiplicative component of the units had an offset
1346 during instantiation, then the offset is ignored in the resulting
1347 `Units` object. See below for examples.
1349 **Examples**
1351 >>> Units('K').has_offset
1352 False
1353 >>> Units('K @ 0').has_offset
1354 False
1355 >>> Units('K @ 273.15').has_offset
1356 True
1357 >>> Units('degC').has_offset
1358 True
1359 >>> Units('degF').has_offset
1360 True
1362 >>> Units('Watt').has_offset
1363 False
1364 >>> Units('m2.kg.s-3').has_offset
1365 False
1367 >>> Units('km').has_offset
1368 False
1369 >>> Units('1000 m').has_offset
1370 False
1372 >>> Units('m2.kg.s-3 @ 3.14').has_offset
1373 True
1374 >>> Units('(K @ 273.15) m s-1').has_offset
1375 False
1376 >>> Units('degC m s-1').has_offset
1377 False
1378 >>> Units('degC m s-1') == Units('K m s-1')
1379 True
1381 '''
1382 return '@' in self.formatted()
1384 @property
1385 def isreftime(self):
1386 '''True if the units are reference time units, False otherwise.
1388 Note that time units (such as ``'days'``) are not reference time
1389 units.
1391 .. seealso:: `isdimensionless`, `islongitude`, `islatitude`,
1392 `ispressure`, `istime`
1394 **Examples:**
1396 >>> Units('days since 2000-12-1 03:00').isreftime
1397 True
1398 >>> Units('hours since 2100-1-1', calendar='noleap').isreftime
1399 True
1400 >>> Units(calendar='360_day').isreftime
1401 True
1402 >>> Units('days').isreftime
1403 False
1404 >>> Units('kg').isreftime
1405 False
1406 >>> Units().isreftime
1407 False
1409 '''
1410 return self._isreftime
1412 @property
1413 def iscalendartime(self):
1414 '''True if the units are calendar time units, False otherwise.
1416 Note that regular time units (such as ``'days'``) are not calendar
1417 time units.
1419 .. seealso:: `isdimensionless`, `islongitude`, `islatitude`,
1420 `ispressure`, `isreftime`, `istime`
1422 **Examples:**
1424 >>> Units('calendar_months').iscalendartime
1425 True
1426 >>> Units('calendar_years').iscalendartime
1427 True
1428 >>> Units('days').iscalendartime
1429 False
1430 >>> Units('km s-1').iscalendartime
1431 False
1432 >>> Units('kg').isreftime
1433 False
1434 >>> Units('').isreftime
1435 False
1436 >>> Units().isreftime
1437 False
1439 '''
1440 return bool(_ut_are_convertible(self._ut_unit, _calendartime_ut_unit))
1442 @property
1443 def isdimensionless(self):
1444 '''True if the units are dimensionless, false otherwise.
1446 .. seealso:: `islongitude`, `islatitude`, `ispressure`, `isreftime`,
1447 `istime`
1449 **Examples:**
1451 >>> Units('').isdimensionless
1452 True
1453 >>> Units('1').isdimensionless
1454 True
1455 >>> Units('100').isdimensionless
1456 True
1457 >>> Units('m/m').isdimensionless
1458 True
1459 >>> Units('m km-1').isdimensionless
1460 True
1461 >>> Units().isdimensionless
1462 False
1463 >>> Units('m').isdimensionless
1464 False
1465 >>> Units('m/s').isdimensionless
1466 False
1467 >>> Units('days since 2000-1-1', calendar='noleap').isdimensionless
1468 False
1470 '''
1471 return bool(
1472 _ut_are_convertible(self._ut_unit, _dimensionless_unit_one))
1474 @property
1475 def ispressure(self):
1476 '''True if the units are pressure units, false otherwise.
1478 .. seealso:: `isdimensionless`, `islongitude`, `islatitude`,
1479 `isreftime`, `istime`
1481 **Examples:**
1483 >>> Units('bar').ispressure
1484 True
1485 >>> Units('hPa').ispressure
1486 True
1487 >>> Units('meter^-1-kilogram-second^-2').ispressure
1488 True
1489 >>> Units('hours since 2100-1-1', calendar='noleap').ispressure
1490 False
1492 '''
1493 ut_unit = self._ut_unit
1494 if ut_unit is None:
1495 return False
1497 return bool(_ut_are_convertible(ut_unit, _pressure_ut_unit))
1499 @property
1500 def islatitude(self):
1501 '''True if and only if the units are latitude units.
1503 This is the case if and only if the `units` attribute is one of
1504 ``'degrees_north'``, ``'degree_north'``, ``'degree_N'``,
1505 ``'degrees_N'``, ``'degreeN'``, and ``'degreesN'``.
1507 Note that units of ``'degrees'`` are not latitude units.
1509 .. seealso:: `isdimensionless`, `islongitude`, `ispressure`,
1510 `isreftime`, `istime`
1512 **Examples:**
1514 >>> Units('degrees_north').islatitude
1515 True
1516 >>> Units('degrees').islatitude
1517 False
1518 >>> Units('degrees_east').islatitude
1519 False
1520 >>> Units('kg').islatitude
1521 False
1522 >>> Units().islatitude
1523 False
1525 '''
1526 return self._units in ('degrees_north', 'degree_north', 'degree_N',
1527 'degrees_N', 'degreeN', 'degreesN')
1529 @property
1530 def islongitude(self):
1531 '''True if and only if the units are longitude units.
1533 This is the case if and only if the `units` attribute is one of
1534 ``'degrees_east'``, ``'degree_east'``, ``'degree_E'``,
1535 ``'degrees_E'``, ``'degreeE'``, and ``'degreesE'``.
1537 Note that units of ``'degrees'`` are not longitude units.
1539 .. seealso:: `isdimensionless`, `islatitude`, `ispressure`,
1540 `isreftime`, `istime`
1542 **Examples:**
1544 >>> Units('degrees_east').islongitude
1545 True
1546 >>> Units('degrees').islongitude
1547 False
1548 >>> Units('degrees_north').islongitude
1549 False
1550 >>> Units('kg').islongitude
1551 False
1552 >>> Units().islongitude
1553 False
1555 '''
1556 return self._units in ('degrees_east', 'degree_east', 'degree_E',
1557 'degrees_E', 'degreeE', 'degreesE')
1559 @property
1560 def istime(self):
1561 '''True if the units are time units, False otherwise.
1563 Note that reference time units (such as ``'days since
1564 2000-12-1'``) are not time units, nor are calendar years and
1565 calendar months.
1567 .. seealso:: `iscalendartime`, `isdimensionless`, `islongitude`,
1568 `islatitude`, `ispressure`, `isreftime`
1570 **Examples:**
1572 >>> Units('days').istime
1573 True
1574 >>> Units('seconds').istime
1575 True
1576 >>> Units('kg').istime
1577 False
1578 >>> Units().istime
1579 False
1580 >>> Units('hours since 2100-1-1', calendar='noleap').istime
1581 False
1582 >>> Units(calendar='360_day').istime
1583 False
1584 >>> Units('calendar_years').istime
1585 False
1586 >>> Units('calendar_months').istime
1587 False
1589 '''
1590 if self._isreftime:
1591 return False
1593 ut_unit = self._ut_unit
1594 if ut_unit is None:
1595 return False
1597 return bool(_ut_are_convertible(ut_unit, _day_ut_unit))
1599 @property
1600 def isvalid(self):
1601 '''Whether the units are valid.
1603 .. seealso:: `reason_notvalid`
1605 **Examples:**
1607 >>> u = Units('km')
1608 >>> u.isvalid
1609 True
1610 >>> u.reason_notvalid
1611 ''
1613 >>> u = Units('Bad Units')
1614 >>> u.isvalid
1615 False
1616 >>> u.reason_notvalid
1617 "Invalid units: 'Bad Units'; Not recognised by UDUNITS"
1618 >>> u = Units(days since 2000-1-1', calendar='Bad Calendar')
1619 >>> u.isvalid
1620 False
1621 >>> u.reason_notvalid
1622 "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'"
1624 '''
1625 return getattr(self, '_isvalid', False)
1627 @property
1628 def reason_notvalid(self):
1629 '''The reason for invalid units.
1631 If the units are valid then the reason is an empty string.
1633 .. seealso:: `isvalid`
1635 **Examples:**
1637 >>> u = Units('km')
1638 >>> u.isvalid
1639 True
1640 >>> u.reason_notvalid
1641 ''
1643 >>> u = Units('Bad Units')
1644 >>> u.isvalid
1645 False
1646 >>> u.reason_notvalid
1647 "Invalid units: 'Bad Units'; Not recognised by UDUNITS"
1649 >>> u = Units(days since 2000-1-1', calendar='Bad Calendar')
1650 >>> u.isvalid
1651 False
1652 >>> u.reason_notvalid
1653 "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'"
1655 '''
1656 return getattr(self, '_reason_notvalid', '')
1658 @property
1659 def reftime(self):
1660 '''The reference date-time of reference time units.
1662 .. seealso:: `calendar`, `isreftime`, `units`
1664 :Returns:
1666 `cftime.datetime`
1668 **Examples:**
1670 >>> u = Units('days since 2001-01-01', calendar='360_day')
1671 >>> u.reftime
1672 cftime.datetime(2001, 1, 1, 0, 0, 0, 0)
1674 '''
1675 if self.isreftime:
1676 # utime = self._utime
1677 # if utime:
1678 # origin = utime.origin
1679 # if origin:
1680 # if isinstance(origin, datetime.datetime):
1681 # return cftime.datetime(*origin.timetuple()[:7])
1682 # else:
1683 # origin
1684 # else:
1685 # # Some refrence date-times do not have a utime, such
1686 # # as those defined by months or years
1688 calendar = self._canonical_calendar
1689 return cftime.datetime(
1690 *cftime._parse_date(self.units.split(' since ')[1])[:7],
1691 calendar=calendar
1692 )
1694 raise AttributeError(
1695 "{!r} has no attribute 'reftime'".format(self))
1697 @property
1698 def calendar(self):
1699 '''The calendar for reference time units.
1701 May be any string allowed by the calendar CF property.
1703 If it is unset then the default CF calendar is assumed when
1704 required.
1706 .. seealso:: `units`
1708 **Examples:**
1710 >>> Units(calendar='365_day').calendar
1711 '365_day'
1712 >>> Units('days since 2001-1-1', calendar='noleap').calendar
1713 'noleap'
1714 >>> Units('days since 2001-1-1').calendar
1715 AttributeError: Units has no attribute 'calendar'
1717 '''
1718 value = self._calendar
1719 if value is not None:
1720 return value
1722 raise AttributeError("%s has no attribute 'calendar'" %
1723 self.__class__.__name__)
1725 @property
1726 def units(self):
1727 '''The units.
1729 May be any string allowed by the units CF property.
1731 .. seealso:: `calendar`
1733 **Examples:**
1735 >>> Units('kg').units
1736 'kg'
1737 >>> Units('seconds').units
1738 'seconds'
1739 >>> Units('days since 2000-1-1', calendar='366_day').units
1740 'days since 2000-1-1'
1742 '''
1743 value = self._units
1744 if value is not None:
1745 return value
1747 raise AttributeError("'%s' object has no attribute 'units'" %
1748 self.__class__.__name__)
1750 # ----------------------------------------------------------------
1751 # Methods
1752 # ----------------------------------------------------------------
1753 def equivalent(self, other, verbose=False):
1754 '''Returns True if numeric values in one unit are convertible to
1755 numeric values in the other unit.
1757 .. seealso:: `equals`
1759 :Parameters:
1761 other: `Units`
1762 The other units.
1764 :Returns:
1766 `bool`
1767 True if the units are equivalent, False otherwise.
1769 **Examples:**
1771 >>> u = Units('m')
1772 >>> v = Units('km')
1773 >>> w = Units('s')
1775 >>> u.equivalent(v)
1776 True
1777 >>> u.equivalent(w)
1778 False
1780 >>> u = Units('days since 2000-1-1')
1781 >>> v = Units('days since 2000-1-1', calendar='366_day')
1782 >>> w = Units('seconds since 1978-3-12', calendar='gregorian)
1784 >>> u.equivalent(v)
1785 False
1786 >>> u.equivalent(w)
1787 True
1789 '''
1790# if not self.isvalid or not other.isvalid:
1791# return False
1793 isreftime1 = self._isreftime
1794 isreftime2 = other._isreftime
1796# if isreftime1 and isreftime2:
1797# # Both units are reference-time units
1798# if self._canonical_calendar != other._canonical_calendar:
1799# if verbose:
1800# print("{}: Incompatible calendars: {!r}, {!r}".format(
1801# self.__class__.__name__,
1802# self._calendar, other._calendar)) # pragma: no cover
1803# return False
1804#
1805# reftime0 = getattr(self, 'reftime', None)
1806# reftime1 = getattr(other, 'reftime', None)
1807# if reftime0 != reftime1:
1808# if verbose:
1809# print(
1810# "{}: Different reference date-times: "
1811# "{!r}, {!r}".format(
1812# self.__class__.__name__,
1813# reftime0, reftime1
1814# )
1815# ) # pragma: no cover
1816# return False
1817#
1818# elif isreftime1 or isreftime2:
1819# if verbose:
1820# print("{}: Only one is reference time".format(
1821# self.__class__.__name__)) # pragma: no cover
1822# return False
1824 if isreftime1 and isreftime2:
1825 # Both units are reference-time units
1826 units0 = self._units
1827 units1 = other._units
1828 if units0 and units1 or (not units0 and not units1):
1829 out = (self._canonical_calendar == other._canonical_calendar)
1830 if verbose and not out:
1831 print("{}: Incompatible calendars: {!r}, {!r}".format(
1832 self.__class__.__name__,
1833 self._calendar, other._calendar)) # pragma: no cover
1835 return out
1836 else:
1837 return False
1839 elif isreftime1 or isreftime2:
1840 if verbose:
1841 print("{}: Only one is reference time".format(
1842 self.__class__.__name__)) # pragma: no cover
1843 return False
1845# if not self.isvalid:
1846# if verbose:
1847# print(
1848# "{}: {!r} is not valid".format(
1849# self.__class__.__name__, self)
1850# ) # pragma: no cover
1851# return False
1852#
1853# if not other.isvalid:
1854# if verbose:
1855# print(
1856# "{}: {!r} is not valid".format(
1857# self.__class__.__name__, other)
1858# ) # pragma: no cover
1859# return False
1861# if isreftime1 and isreftime2:
1862# # Both units are reference-time units
1863# units0 = self._units
1864# units1 = other._units
1865# if units0 and units1 or (not units0 and not units1):
1866# return self._calendar == other._calendar
1867# else:
1868# return False
1869# # --- End: if
1871 # Still here?
1872 if not self and not other:
1873 # Both units are null and therefore equivalent
1874 return True
1876# if not isreftime1 and not isreftime2:
1877 # Both units are not reference-time units
1878 return bool(_ut_are_convertible(self._ut_unit, other._ut_unit))
1880 # Still here? Then units are not equivalent.
1881# return False
1883 def formatted(self, names=None, definition=None):
1884 '''Formats the string stored in the `units` attribute in a
1885 standardized manner. The `units` attribute is modified in place
1886 and its new value is returned.
1888 :Parameters:
1890 names: `bool`, optional
1891 Use unit names instead of symbols.
1893 definition: `bool`, optional
1894 The formatted string is given in terms of basic units
1895 instead of stopping any expansion at the highest level
1896 possible.
1898 :Returns:
1900 `str` or `None`
1901 The formatted string. If the units have not yet been set,
1902 then `None` is returned.
1904 **Examples:**
1906 >>> u = Units('W')
1907 >>> u.units
1908 'W'
1909 >>> u.formatted(names=True)
1910 'watt'
1911 >>> u.formatted(definition=True)
1912 'm2.kg.s-3'
1913 >>> u.formatted(names=True, definition=True)
1914 'meter^2-kilogram-second^-3'
1915 >>> u.formatted()
1916 'W'
1918 >>> u = Units('dram')
1919 >>> u.formatted(names=True)
1920 '0.001771845 kilogram'
1922 >>> u = Units('hours since 2100-1-1', calendar='noleap')
1923 >>> u.formatted(names=True)
1924 'hour since 2100-1-1 00:00:00'
1925 >>> u.formatted()
1926 'h since 2100-1-1 00:00:00'
1928 Formatting is also available during object initialization:
1930 >>> u = Units('m/s', formatted=True)
1931 >>> u.units
1932 'm.s-1'
1934 >>> u = Units('dram', names=True)
1935 >>> u.units
1936 '0.001771845 kilogram'
1938 >>> u = Units('Watt')
1939 >>> u.units
1940 'Watt'
1942 >>> u = Units('Watt', formatted=True)
1943 >>> u.units
1944 'W'
1946 >>> u = Units('Watt', names=True)
1947 >>> u.units
1948 'watt'
1950 >>> u = Units('Watt', definition=True)
1951 >>> u.units
1952 'm2.kg.s-3'
1954 >>> u = Units('Watt', names=True, definition=True)
1955 >>> u.units
1956 'meter^2-kilogram-second^-3'
1958 '''
1959 ut_unit = self._ut_unit
1961 if ut_unit is None:
1962 return None
1964 opts = _UT_ASCII
1965 if names:
1966 opts |= _UT_NAMES
1967 if definition:
1968 opts |= _UT_DEFINITION
1970 if _ut_format(ut_unit, _string_buffer, _sizeof_buffer, opts) != -1:
1971 out = _string_buffer.value
1972 else:
1973 raise ValueError("Can't format unit {!r}".format(self))
1975 if self.isreftime:
1976 out = str(out, 'utf-8') # needs converting from byte-string
1977 out += ' since ' + self.reftime.strftime()
1978 return out
1980 return out.decode('utf-8')
1982 @classmethod
1983 def conform(cls, x, from_units, to_units, inplace=False):
1984 '''Conform values in one unit to equivalent values in another,
1985 compatible unit.
1987 Returns the conformed values.
1989 The values may either be a `numpy` array, a python numeric type,
1990 or a `list` or `tuple`. The returned value is of the same type,
1991 except that input integers are converted to floats and python
1992 sequences are converted to `numpy` arrays (see the *inplace*
1993 keyword).
1995 .. warning:: Do not change the calendar of reference time units in
1996 the current version. Whilst this is possible, it will
1997 almost certainly result in an incorrect
1998 interpretation of the data or an error.
2000 :Parameters:
2002 x: `numpy.ndarray` or python numeric type or `list` or `tuple`
2004 from_units: `Units`
2005 The original units of *x*
2007 to_units: `Units`
2008 The units to which *x* should be conformed to.
2010 inplace: `bool`, optional
2011 If True and *x* is a `numpy` array then change it in place,
2012 creating no temporary copies, with one exception: If *x*
2013 is of integer type and the conversion is not null, then it
2014 will not be changed inplace and the returned conformed
2015 array will be of float type.
2017 If *x* is a `list` or `tuple` then the *inplace* parameter
2018 is ignored and a `numpy` array is returned.
2020 :Returns:
2022 `numpy.ndarray` or python numeric
2023 The modified numeric values.
2025 **Examples:**
2027 >>> Units.conform(2, Units('km'), Units('m'))
2028 2000.0
2030 >>> import numpy
2031 >>> a = numpy.arange(5.0)
2032 >>> Units.conform(a, Units('minute'), Units('second'))
2033 array([ 0., 60., 120., 180., 240.])
2034 >>> print(a)
2035 [ 0. 1. 2. 3. 4.]
2037 >>> Units.conform(a,
2038 Units('days since 2000-12-1'),
2039 Units('days since 2001-1-1'), inplace=True)
2040 array([-31., -30., -29., -28., -27.])
2041 >>> print(a)
2042 [-31. -30. -29. -28. -27.]
2044 '''
2045 if from_units.equals(to_units):
2046 if not isinstance(x, (int, float)):
2047 x = numpy_asanyarray(x)
2049 if inplace:
2050 return x
2051 else:
2052 try:
2053 return x.copy()
2054 except AttributeError:
2055 x
2056 # --- End: if
2058 if not from_units.equivalent(to_units):
2059 raise ValueError("Units are not convertible: {!r}, {!r}".format(
2060 from_units, to_units))
2062 ut_unit1 = from_units._ut_unit
2063 ut_unit2 = to_units._ut_unit
2065 if ut_unit1 is None or ut_unit2 is None:
2066 raise ValueError("Units are not convertible: {!r}, {!r}".format(
2067 from_units, to_units))
2069 convert = _ut_compare(ut_unit1, ut_unit2)
2071 if from_units._isreftime and to_units._isreftime:
2072 # --------------------------------------------------------
2073 # Both units are time-reference units, so calculate the
2074 # non-zero offset in units of days.
2075 # --------------------------------------------------------
2076 units0, reftime0 = from_units.units.split(' since ')
2077 units1, reftime1 = to_units.units.split(' since ')
2078 if units0 in _months_or_years:
2079 from_units = cls(
2080 'days since '+reftime0,
2081 calendar=getattr(from_units, 'calendar', None))
2082 x = numpy_asanyarray(x)
2083 if inplace:
2084 if units0 in ('month', 'months'):
2085 x *= _month_length
2086 else:
2087 x *= _year_length
2088 else:
2089 if units0 in ('month', 'months'):
2090 x = x * _month_length
2091 else:
2092 x = x * _year_length
2094 inplace = True
2096 ut_unit1 = from_units._ut_unit
2097 ut_unit2 = to_units._ut_unit
2099 convert = _ut_compare(ut_unit1, ut_unit2)
2101 if units1 in _months_or_years:
2102 to_units = cls('days since '+reftime1,
2103 calendar=getattr(to_units, 'calendar', None))
2105 to_jd0 = cftime.JulianDayFromDate(
2106 to_units._utime.origin,
2107 calendar=to_units._utime.calendar)
2108 from_jd0 = cftime.JulianDayFromDate(
2109 from_units._utime.origin,
2110 calendar=from_units._utime.calendar)
2112 offset = to_jd0 - from_jd0
2113# offset = to_units._utime._jd0 - from_units._utime._jd0
2114 else:
2115 offset = 0
2117 # ------------------------------------------------------------
2118 # If the two units are identical then no need to alter the
2119 # value, so return it unchanged.
2120 # ------------------------------------------------------------
2121# if not convert and not offset:
2122# return x
2124 if convert:
2125 cv_converter = _ut_get_converter(ut_unit1, ut_unit2)
2126 if not cv_converter:
2127 _cv_free(cv_converter)
2128 raise ValueError(
2129 "Units are not convertible: {!r}, {!r}".format(
2130 from_units, to_units))
2131 # --- End: if
2133 # ------------------------------------------------------------
2134 # Find out if x is (or should be) a numpy array or a python
2135 # number
2136 # ------------------------------------------------------------
2137 if isinstance(x, numpy_generic):
2138 # Convert a generic numpy scalar to a 0-d numpy array
2139 x = numpy_array(x)
2140 x_is_numpy = True
2141 elif not isinstance(x, numpy_ndarray):
2142 if numpy_size(x) > 1 or len(numpy_shape(x)):
2143 # Convert a non-numpy (possibly nested) sequence to a
2144 # numpy array. E.g. [1], ((1.5, 2.5))
2145 x = numpy_asanyarray(x)
2146 x_is_numpy = True
2147 inplace = True
2148 else:
2149 x_is_numpy = False
2150 else:
2151 x_is_numpy = True
2153 if x_is_numpy:
2154 if not x.flags.contiguous:
2155 x = numpy_array(x, order='C')
2156# ARRRGGHH dch TODO
2158 # --------------------------------------------------------
2159 # Convert an integer numpy array to a float numpy array
2160 # --------------------------------------------------------
2161 if inplace:
2162 if x.dtype.kind == 'i':
2163 if x.dtype.char == 'i':
2164 y = x.view(dtype='float32')
2165 y[...] = x
2166 x.dtype = numpy_dtype('float32')
2167 elif x.dtype.char == 'l':
2168 y = x.view(dtype=float)
2169 y[...] = x
2170 x.dtype = numpy_dtype(float)
2171 else:
2172 # At numpy vn1.7 astype has many more keywords ...
2173 if x.dtype.kind == 'i':
2174 if x.dtype.char == 'i':
2175 x = x.astype('float32')
2176 elif x.dtype.char == 'l':
2177 x = x.astype(float)
2178 else:
2179 x = x.copy()
2180 # --- End: if
2182 # ------------------------------------------------------------
2183 # Convert the array to the new units
2184 # ------------------------------------------------------------
2185 if convert:
2187 if x_is_numpy:
2188 # Create a pointer to the array cast to the
2189 # appropriate ctypes object
2190 itemsize = x.dtype.itemsize
2191 pointer = x.ctypes.data_as(_ctypes_POINTER[itemsize])
2193 # Convert the array in place
2194 _cv_convert_array[itemsize](cv_converter,
2195 pointer,
2196 _c_size_t(x.size),
2197 pointer)
2198 else:
2199 # Create a pointer to the number cast to a ctypes
2200 # double object.
2201 y = _c_double(x)
2202 pointer = ctypes.pointer(y)
2203 # Convert the pointer
2204 _cv_convert_doubles(cv_converter,
2205 pointer,
2206 _c_size_t(1),
2207 pointer)
2208 # Reset the number
2209 x = y.value
2211 _cv_free(cv_converter)
2213 # ------------------------------------------------------------
2214 # Apply an offset for reference-time units
2215 # ------------------------------------------------------------
2216 if offset:
2217 # Convert the offset from 'days' to the correct units and
2218 # subtract it from x
2219 if _ut_compare(_day_ut_unit, ut_unit2):
2221 cv_converter = _ut_get_converter(_day_ut_unit, ut_unit2)
2222 scale = numpy_array(1.0)
2223 pointer = scale.ctypes.data_as(
2224 ctypes.POINTER(ctypes.c_double))
2225 _cv_convert_doubles(cv_converter,
2226 pointer,
2227 _c_size_t(scale.size),
2228 pointer)
2229 _cv_free(cv_converter)
2231 offset *= scale.item()
2233 x -= offset
2235 return x
2237 def copy(self):
2238 '''Return a deep copy.
2240 Equivalent to ``copy.deepcopy(u)``.
2242 :Returns:
2244 The deep copy.
2246 **Examples:**
2248 >>> v = u.copy()
2250 '''
2251 return self
2253 def equals(self, other, rtol=None, atol=None, verbose=False):
2254 '''Return True if and only if numeric values in one unit are
2255 convertible to numeric values in the other unit and their
2256 conversion is a scale factor of 1.
2258 .. seealso:: `equivalent`
2260 :Parameters:
2262 other: `Units`
2263 The other units.
2265 :Returns:
2267 `bool`
2268 `True` if the units are equal, `False` otherwise.
2270 **Examples:**
2272 >>> u = Units('km')
2273 >>> v = Units('1000m')
2274 >>> w = Units('100000m')
2275 >>> u.equals(v)
2276 True
2277 >>> u.equals(w)
2278 False
2280 >>> u = Units('m s-1')
2281 >>> m = Units('m')
2282 >>> s = Units('s')
2283 >>> u.equals(m)
2284 False
2285 >>> u.equals(m/s)
2286 True
2287 >>> (m/s).equals(u)
2288 True
2290 Undefined units are considered equal:
2292 >>> u = Units()
2293 >>> v = Units()
2294 >>> u.equals(v)
2295 True
2297 '''
2298 isreftime1 = self._isreftime
2299 isreftime2 = other._isreftime
2301 if isreftime1 and isreftime2:
2302 # Both units are reference-time units
2303 if self._canonical_calendar != other._canonical_calendar:
2304 if verbose:
2305 print("{}: Incompatible calendars: {!r}, {!r}".format(
2306 self.__class__.__name__,
2307 self._calendar, other._calendar)) # pragma: no cover
2309 return False
2311 reftime0 = getattr(self, 'reftime', None)
2312 reftime1 = getattr(other, 'reftime', None)
2313 if reftime0 != reftime1:
2314 if verbose:
2315 print("{}: Different reference date-times: "
2316 "{!r}, {!r}".format(
2317 self.__class__.__name__,
2318 reftime0, reftime1)) # pragma: no cover
2320 return False
2322 elif isreftime1 or isreftime2:
2323 if verbose:
2324 print("{}: Only one is reference time".format(
2325 self.__class__.__name__)) # pragma: no cover
2327 return False
2330# utime0 = self._utime
2331# utime1 = other._utime
2332# if utime0 is not None and utime1 is not None:
2333# return utime0.origin_equals(utime1)
2334# elif utime0 is None and utime1 is None:
2335# return self.reftime
2336#
2337#
2338#
2339# units0 = self._units
2340# units1 = other._units
2341# if units0 and units1 or (not units0 and not units1):
2342# out = (self._canonical_calendar == other._canonical_calendar)
2343# if verbose and not out:
2344# print("{}: Incompatible calendars: {!r}, {!r}".format(
2345# self.__class__.__name__,
2346# self._calendar, other._calendar)) # pragma: no cover
2347#
2348# return out
2349# else:
2350# return False
2351# # --- End: if
2352# if not self.isvalid:
2353# if verbose:
2354# print(
2355# "{}: {!r} is not valid".format(
2356# self.__class__.__name__, self)
2357# ) # pragma: no cover
2358# return False
2359#
2360# if not other.isvalid:
2361# if verbose:
2362# print(
2363# "{}: {!r} is not valid".format(
2364# self.__class__.__name__, other)
2365# ) # pragma: no cover
2366# return False
2367#
2369# if not self.isvalid or not other.isvalid:
2370# print ('ppp')
2371# return False
2373 try:
2374 if not _ut_compare(self._ut_unit, other._ut_unit):
2375 return True
2377 if verbose:
2378 print("{}: Different units: {!r}, {!r}".format(
2379 self.__class__.__name__,
2380 self.units, other.units)) # pragma: no cover
2382 return False
2383 except AttributeError:
2384 return False
2386# isreftime1 = self._isreftime
2387# isreftime2 = other._isreftime
2388#
2389# if not isreftime1 and not isreftime2:
2390# # Neither units is reference-time so they're equal
2391# return True
2392#
2393# if isreftime1 and isreftime2:
2394# # Both units are reference-time
2395# utime0 = self._utime
2396# utime1 = other._utime
2397# if utime0.calendar != utime1.calendar:
2398# return False
2399#
2400# return utime0.origin_equals(utime1)
2401#
2402# # One unit is a reference-time and the other is not so they're
2403# # not equal
2404# return False
2406 def log(self, base):
2407 '''Return the logarithmic unit corresponding to the given logarithmic
2408 base.
2410 :Parameters:
2412 base: `int` or `float`
2413 The logarithmic base.
2415 :Returns:
2417 `Units`
2418 The logarithmic unit corresponding to the given
2419 logarithmic base.
2421 **Examples:**
2423 >>> u = Units('W', names=True)
2424 >>> u
2425 <Units: watt>
2427 >>> u.log(10)
2428 <Units: lg(re 1 W)>
2429 >>> u.log(2)
2430 <Units: lb(re 1 W)>
2432 >>> import math
2433 >>> u.log(math.e)
2434 <Units: ln(re 1 W)>
2436 >>> u.log(3.5)
2437 <Units: 0.798235600147928 ln(re 1 W)>
2439 '''
2440 try:
2441 _ut_unit = _ut_log(_c_double(base), self._ut_unit)
2442 except TypeError:
2443 pass
2444 else:
2445 if _ut_unit:
2446 return type(self)(_ut_unit=_ut_unit)
2447 # --- End: try
2449 raise ValueError(
2450 "Can't take the logarithm to the base {!r} of {!r}".format(
2451 base, self))
2452# --- End: class
2455class Utime(cftime.utime):
2456 '''Performs conversions of netCDF time coordinate data to/from
2457 datetime objects.
2459 This object is (currently) functionally equivalent to a
2460 `netCDF4.netcdftime.utime` object.
2462 **Attributes**
2464 ============== ==================================================
2465 Attribute Description
2466 ============== ==================================================
2467 `!calendar` The calendar used in the time calculation.
2468 `!origin` A date/time object for the reference time.
2469 `!tzoffset` Time zone offset in minutes.
2470 `!unit_string`
2471 `!units`
2472 ============== ==================================================
2474 '''
2475 def __init__(self, calendar, unit_string=None,
2476 only_use_cftime_datetimes=True):
2477 '''**Initialization**
2479 :Parameters:
2481 calendar: `str`
2482 The calendar used in the time calculations. Must be one
2483 of: ``'gregorian'``, ``'360_day'``, ``'365_day'``,
2484 ``'366_day'``, ``'julian'``, ``'proleptic_gregorian'``,
2485 although this is not checked.
2487 unit_string: `str`, optional
2488 A string of the form "time-units since <time-origin>"
2489 defining the reference-time units.
2491 only_use_cftime_datetimes: `bool`, optional
2492 If False, datetime.datetime objects are returned from
2493 `num2date` where possible; By default. dates which
2494 subclass `cftime.datetime` are returned for all calendars.
2496 '''
2497 if unit_string:
2498 super().__init__(
2499 unit_string, calendar,
2500 only_use_cftime_datetimes=only_use_cftime_datetimes)
2501 else:
2502 self.calendar = calendar
2503 self.origin = None
2504 self.tzoffset = None
2505 self.unit_string = None
2506 self.units = None
2508 def __repr__(self):
2509 '''x.__repr__() <==> repr(x)
2511 '''
2512 unit_string = self.unit_string
2513 if unit_string:
2514 x = [unit_string]
2515 else:
2516 x = []
2518 x.append(self.calendar)
2520 return "<Utime: {}>".format(' '.join(x))
2522 def num2date(self, time_value):
2523 '''Return a datetime-like object given a time value.
2525 The units of the time value are described by the `!unit_string`
2526 and `!calendar` attributes.
2528 See `netCDF4.netcdftime.utime.num2date` for details.
2530 In addition to `netCDF4.netcdftime.utime.num2date`, this method
2531 handles units of months and years as defined by Udunits, ie. 1
2532 year = 365.242198781 days, 1 month = 365.242198781/12 days.
2534 '''
2535 units = self.units
2536 unit_string = self.unit_string
2538 if units in ('month', 'months'):
2539 # Convert months to days
2540 unit_string = unit_string.replace(units, 'days', 1)
2541 time_value = numpy_array(time_value)*_month_length
2542 elif units in ('year', 'years', 'yr'):
2543 # Convert years to days
2544 unit_string = unit_string.replace(units, 'days', 1)
2545 time_value = numpy_array(time_value)*_year_length
2547 u = cftime.utime(unit_string, self.calendar)
2549 return u.num2date(time_value)
2551# --- End: class