Hide keyboard shortcuts

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 

6 

7from copy import deepcopy 

8 

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 

16 

17 

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 

32 

33_ctypes_POINTER = {4: _POINTER(_c_float), 

34 8: _POINTER(_c_double)} 

35 

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 ) 

51 

52_udunits = ctypes.CDLL(_libpath) 

53 

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 

64 

65_ut_set_error_message_handler(_udunits.ut_ignore) 

66 

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 

72 

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,')') 

76 

77# Reinstate the reporting of error messages 

78# _ut_set_error_message_handler(_udunits.ut_write_to_stderr) 

79 

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 

90 

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 

96 

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 

103 

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 

109 

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 

115 

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 

121 

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 

127 

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 

132 

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 

137 

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 

142 

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 

147 

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 

153 

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 

158 

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 

163 

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 

168 

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 

174 

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 

181 

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 

187 

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 

192 

193_UT_ASCII = 0 

194_UT_NAMES = 4 

195_UT_DEFINITION = 8 

196 

197_cv_convert_array = {4: _cv_convert_floats, 

198 8: _cv_convert_doubles} 

199 

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 

225 

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)) 

238 

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)) 

251 

252 

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)) 

266 

267 

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") 

276 

277# _udunits.ut_get_unit_by_name(_udunits.ut_new_base_unit(_ut_system), 

278# _ut_system, 'calendar_year') 

279 

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)) 

285 

286# -------------------------------------------------------------------- 

287# Aliases for netCDF4.netcdftime classes 

288# -------------------------------------------------------------------- 

289import cftime 

290# _netCDF4_netcdftime_utime = cftime.utime 

291# _datetime = cftime.datetime 

292 

293# -------------------------------------------------------------------- 

294# Aliases for netCDF4.netcdftime functions 

295# -------------------------------------------------------------------- 

296_num2date = cftime.num2date 

297_date2num = cftime.date2num 

298 

299_cached_ut_unit = {} 

300_cached_utime = {} 

301 

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 

319 

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 

323 

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} 

340 

341_months_or_years = ('month', 'months', 'year', 'years', 'yr') 

342 

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]]) 

350 

351 

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. 

358 

359 :Parameters: 

360 

361 flag: `bool` 

362 Set to True to print Udunits error messages and False to 

363 not print Udunits error messages. 

364 

365 :Returns: 

366 

367 `None` 

368 

369 **Examples:** 

370 

371 >>> udunits_error_messages(True) 

372 >>> udunits_error_messages(False) 

373 

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) 

379 

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 

461 

462 

463# -------------------------------------------------------------------- 

464# Constants, as defined by UDUNITS 

465# -------------------------------------------------------------------- 

466_year_length = 365.242198781 

467_month_length = _year_length / 12 

468 

469 

470class Units(): 

471 '''Store, combine and compare physical units and convert numeric 

472 values to different units. 

473 

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. 

477 

478 

479 **Modifications to the standard Udunits database** 

480 

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): 

485 

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 ======================= ====== ============ ============== 

498 

499 Plural forms of the new units' names are allowed, such as 

500 ``practical_salinity_units``. 

501 

502 The modified database is in the *udunits* subdirectory of the 

503 *etc* directory found in the same location as this module. 

504 

505 

506 **Accessing units** 

507 

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. 

512 

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> 

519 

520 

521 **Equality and equivalence of units** 

522 

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. 

532 

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') 

538 

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) 

547 

548 

549 **Time and reference time units** 

550 

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*): 

553 

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' 

559 

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." 

572 

573 **Calendar** 

574 

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: 

581 

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 

590 

591 

592 **Arithmetic with units** 

593 

594 The following operators, operations and assignments are 

595 overloaded: 

596 

597 Comparison operators: 

598 

599 ``==, !=`` 

600 

601 Binary arithmetic operations: 

602 

603 ``+, -, *, /, pow(), **`` 

604 

605 Unary arithmetic operations: 

606 

607 ``-, +`` 

608 

609 Augmented arithmetic assignments: 

610 

611 ``+=, -=, *=, /=, **=`` 

612 

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. 

616 

617 >>> u = Units('m') 

618 <Units: m> 

619 

620 >>> v = u * 1000 

621 >>> v 

622 <Units: 1000 m> 

623 

624 >>> u == v 

625 False 

626 >>> u != v 

627 True 

628 

629 >>> u **= 2 

630 >>> u 

631 <Units: m2> 

632 

633 It is also possible to create the logarithm of a unit 

634 corresponding to the given logarithmic base: 

635 

636 >>> u = Units('seconds') 

637 >>> u.log(10) 

638 <Units: lg(re 1 s)> 

639 

640 

641 **Modifying data for equivalent units** 

642 

643 Any numpy array or python numeric type may be modified for 

644 equivalent units using the `conform` static method. 

645 

646 >>> Units.conform(2, Units('km'), Units('m')) 

647 2000.0 

648 

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.]) 

655 

656 If the *inplace* keyword is True, then a numpy array is modified 

657 in place, without any copying overheads: 

658 

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.]) 

665 

666 ''' 

667 def __init__(self, units=None, calendar=None, formatted=False, 

668 names=False, definition=False, _ut_unit=None): 

669 '''**Initialization** 

670 

671 :Parameters: 

672 

673 units: `str` or `Units`, optional 

674 Set the new units from this string. 

675 

676 calendar: `str`, optional 

677 Set the calendar for reference time units. 

678 

679 formatted: `bool`, optional 

680 Format the string representation of the units in a 

681 standardized manner. See the `formatted` method. 

682 

683 names: `bool`, optional 

684 Format the string representation of the units using names 

685 instead of symbols. See the `formatted` method. 

686 

687 definition: `bool`, optional 

688 Format the string representation of the units using basic 

689 units. See the `formatted` method. 

690 

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. 

696 

697 ''' 

698 

699 if isinstance(units, self.__class__): 

700 self.__dict__ = units.__dict__ 

701 return 

702 

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 

712 

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 

723 

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 

732 

733 unit = None 

734 

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 

747 

748 units_split = units.split(' since ') 

749 unit = units_split[0].strip() 

750 

751 _units_since_reftime = unit 

752 

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 

764 

765 utime = None 

766 

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()) 

773 

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 

793 

794 _cached_utime[(_calendar, unit_string)] = utime 

795 # --- End: if 

796 

797 self._isreftime = True 

798 self._calendar = calendar 

799 self._canonical_calendar = _calendar 

800 self._utime = utime 

801 

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 

820 

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 

830 

831 self._isreftime = False 

832 self._calendar = None 

833 self._canonial_calendar = None 

834 self._utime = None 

835 

836 self._ut_unit = ut_unit 

837 self._units = units 

838 self._units_since_reftime = unit 

839 

840 if formatted or names or definition: 

841 self._units = self.formatted(names, definition) 

842 

843 return 

844 

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 

861 

862 return 

863 

864 if _ut_unit is not None: 

865 # --------------------------------------------------------- 

866 # _ut_unit is set 

867 # --------------------------------------------------------- 

868 self._ut_unit = _ut_unit 

869 self._isreftime = False 

870 

871 units = self.formatted(names, definition) 

872 _cached_ut_unit[units] = _ut_unit 

873 self._units = units 

874 

875 self._units_since_reftime = None 

876 

877 self._calendar = None 

878 self._utime = None 

879 

880 return 

881 

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 

892 

893 def __getstate__(self): 

894 '''Called when pickling. 

895 

896 :Returns: 

897 

898 `dict` 

899 A dictionary of the instance's attributes 

900 

901 **Examples:** 

902 

903 >>> u = Units('days since 3-4-5', calendar='gregorian') 

904 >>> u.__getstate__() 

905 {'calendar': 'gregorian', 

906 'units': 'days since 3-4-5'} 

907 

908 ''' 

909 return dict([(attr, getattr(self, attr)) 

910 for attr in ('_units', '_calendar') 

911 if hasattr(self, attr)]) 

912 

913 def __setstate__(self, odict): 

914 '''Called when unpickling. 

915 

916 :Parameters: 

917 

918 odict: `dict` 

919 The output from the instance's `__getstate__` method. 

920 

921 :Returns: 

922 

923 `None` 

924 

925 ''' 

926 units = None 

927 if '_units' in odict: 

928 units = odict['_units'] 

929 

930 calendar = None 

931 if '_calendar' in odict: 

932 calendar = odict['_calendar'] 

933 

934 self.__init__(units=units, calendar=calendar) 

935 

936 def __hash__(self): 

937 '''x.__hash__() <==> hash(x) 

938 

939 ''' 

940 if not self._isreftime: 

941 return hash(('Units', self._ut_unit)) 

942 

943 return hash( 

944 ('Units', self._ut_unit, self._utime.origin, 

945 self._utime.calendar) 

946 ) 

947 

948 def __repr__(self): 

949 '''x.__repr__() <==> repr(x) 

950 

951 ''' 

952 return '<{0}: {1}>'.format(self.__class__.__name__, self) 

953 

954 def __str__(self): 

955 '''x.__str__() <==> str(x) 

956 

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 

965 

966 if self._calendar is not None: 

967 string.append('{0}'.format(self._calendar)) 

968 

969 return ' '.join(string) 

970 

971 def __deepcopy__(self, memo): 

972 '''Used if copy.deepcopy is called on the variable. 

973 

974 ''' 

975 return self 

976 

977 def __bool__(self): 

978 '''Truth value testing and the built-in operation ``bool`` 

979 

980 x.__bool__() <==> x!=0 

981 

982 ''' 

983 return self._ut_unit is not None 

984 

985 def __eq__(self, other): 

986 '''The rich comparison operator ``==`` 

987 

988 x.__eq__(y) <==> x==y 

989 

990 ''' 

991 return self.equals(other) 

992 

993 def __ne__(self, other): 

994 '''The rich comparison operator ``!=`` 

995 

996 x.__ne__(y) <==> x!=y 

997 

998 ''' 

999 return not self.equals(other) 

1000 

1001 def __gt__(self, other): 

1002 '''The rich comparison operator ``>`` 

1003 

1004 x.__gt__(y) <==> x>y 

1005 

1006 ''' 

1007 return self._comparison(other, '__gt__') 

1008 

1009 def __ge__(self, other): 

1010 '''The rich comparison operator ```` 

1011 

1012 x.__ge__(y) <==> x>y 

1013 

1014 ''' 

1015 return self._comparison(other, '__ge__') 

1016 

1017 def __lt__(self, other): 

1018 '''The rich comparison operator ```` 

1019 

1020 x.__lt__(y) <==> x<y 

1021 

1022 ''' 

1023 return self._comparison(other, '__lt__') 

1024 

1025 def __le__(self, other): 

1026 '''The rich comparison operator ```` 

1027 

1028 x.__le__(y) <==> x<=y 

1029 

1030 ''' 

1031 return self._comparison(other, '__le__') 

1032 

1033 def __sub__(self, other): 

1034 '''The binary arithmetic operation ``-`` 

1035 

1036 x.__sub__(y) <==> x-y 

1037 

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)) 

1042 

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)) 

1048 

1049 def __add__(self, other): 

1050 '''The binary arithmetic operation ``+`` 

1051 

1052 x.__add__(y) <==> x+y 

1053 

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)) 

1058 

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)) 

1064 

1065 def __mul__(self, other): 

1066 '''The binary arithmetic operation ``*`` 

1067 

1068 x.__mul__(y) <==> x*y 

1069 

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)) 

1074 

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)) 

1082 

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 

1088 

1089 return type(self)(_ut_unit=ut_unit) 

1090 

1091 def __div__(self, other): 

1092 '''x.__div__(y) <==> x/y 

1093 

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)) 

1098 

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)) 

1106 

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 

1112 

1113 return type(self)(_ut_unit=ut_unit) 

1114 

1115 def __pow__(self, other, modulo=None): 

1116 '''The binary arithmetic operations ``**`` and ``pow`` 

1117 

1118 x.__pow__(y) <==> x**y 

1119 

1120 ''' 

1121 # ------------------------------------------------------------ 

1122 # y must be either an integer or the reciprocal of a positive 

1123 # integer. 

1124 # ------------------------------------------------------------ 

1125 

1126 if modulo is not None: 

1127 raise NotImplementedError( 

1128 "3-argument power not supported for {!r}".format( 

1129 self.__class__.__name__)) 

1130 

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 

1137 

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 

1165 

1166 raise ValueError("Can't do {!r} ** {!r}".format(self, other)) 

1167 

1168 def __isub__(self, other): 

1169 '''x.__isub__(y) <==> x-=y 

1170 

1171 ''' 

1172 return self - other 

1173 

1174 def __iadd__(self, other): 

1175 '''x.__iadd__(y) <==> x+=y 

1176 

1177 ''' 

1178 return self + other 

1179 

1180 def __imul__(self, other): 

1181 '''The augmented arithmetic assignment ``*=`` 

1182 

1183 x.__imul__(y) <==> x*=y 

1184 

1185 ''' 

1186 return self * other 

1187 

1188 def __idiv__(self, other): 

1189 '''The augmented arithmetic assignment ``/=`` 

1190 

1191 x.__idiv__(y) <==> x/=y 

1192 

1193 ''' 

1194 return self / other 

1195 

1196 def __ipow__(self, other): 

1197 '''The augmented arithmetic assignment ``**=`` 

1198 

1199 x.__ipow__(y) <==> x**=y 

1200 

1201 ''' 

1202 return self ** other 

1203 

1204 def __rsub__(self, other): 

1205 '''The binary arithmetic operation ``-`` with reflected operands 

1206 

1207 x.__rsub__(y) <==> y-x 

1208 

1209 ''' 

1210 try: 

1211 return -self + other 

1212 except: 

1213 raise ValueError("Can't do {!r} - {!r}".format(other, self)) 

1214 

1215 def __radd__(self, other): 

1216 '''The binary arithmetic operation ``+`` with reflected operands 

1217 

1218 x.__radd__(y) <==> y+x 

1219 

1220 ''' 

1221 return self + other 

1222 

1223 def __rmul__(self, other): 

1224 '''The binary arithmetic operation ``*`` with reflected operands 

1225 

1226 x.__rmul__(y) <==> y*x 

1227 

1228 ''' 

1229 return self * other 

1230 

1231 def __rdiv__(self, other): 

1232 '''x.__rdiv__(y) <==> y/x 

1233 

1234 ''' 

1235 try: 

1236 return (self ** -1) * other 

1237 except: 

1238 raise ValueError("Can't do {!r} / {!r}".format(other, self)) 

1239 

1240 def __floordiv__(self, other): 

1241 '''x.__floordiv__(y) <==> x//y <==> x/y 

1242 

1243 ''' 

1244 return self / other 

1245 

1246 def __ifloordiv__(self, other): 

1247 '''x.__ifloordiv__(y) <==> x//=y <==> x/=y 

1248 

1249 ''' 

1250 return self / other 

1251 

1252 def __rfloordiv__(self, other): 

1253 '''x.__rfloordiv__(y) <==> y//x <==> y/x 

1254 

1255 ''' 

1256 try: 

1257 return (self ** -1) * other 

1258 except: 

1259 raise ValueError("Can't do {!r} // {!r}".format(other, self)) 

1260 

1261 def __truediv__(self, other): 

1262 '''x.__truediv__(y) <==> x/y 

1263 

1264 ''' 

1265 return self.__div__(other) 

1266 

1267 def __itruediv__(self, other): 

1268 '''x.__itruediv__(y) <==> x/=y 

1269 

1270 ''' 

1271 return self.__idiv__(other) 

1272 

1273 def __rtruediv__(self, other): 

1274 '''x.__rtruediv__(y) <==> y/x 

1275 

1276 ''' 

1277 return self.__rdiv__(other) 

1278 

1279 def __mod__(self, other): 

1280 '''TODO 

1281 

1282 ''' 

1283 raise ValueError("Can't do {!r} % {!r}".format(other, self)) 

1284 

1285 def __neg__(self): 

1286 '''The unary arithmetic operation ``-`` 

1287 

1288 x.__neg__() <==> -x 

1289 

1290 ''' 

1291 return self * -1 

1292 

1293 def __pos__(self): 

1294 '''The unary arithmetic operation ``+`` 

1295 

1296 x.__pos__() <==> +x 

1297 

1298 ''' 

1299 return self 

1300 

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)) 

1312 

1313 if not cv_converter: 

1314 _cv_free(cv_converter) 

1315 raise ValueError( 

1316 "Units are not compatible: {!r}, {!r}".format(self, other)) 

1317 

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) 

1325 

1326 return getattr(operator, method)(y.value, 1) 

1327 

1328 def _new_reason_notvalid(self, reason): 

1329 '''TODO 

1330 

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 

1337 

1338 # ---------------------------------------------------------------- 

1339 # Attributes 

1340 # ---------------------------------------------------------------- 

1341 @property 

1342 def has_offset(self): 

1343 '''True if the units contain an offset. 

1344 

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. 

1348 

1349 **Examples** 

1350 

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 

1361 

1362 >>> Units('Watt').has_offset 

1363 False 

1364 >>> Units('m2.kg.s-3').has_offset 

1365 False 

1366 

1367 >>> Units('km').has_offset 

1368 False 

1369 >>> Units('1000 m').has_offset 

1370 False 

1371 

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 

1380 

1381 ''' 

1382 return '@' in self.formatted() 

1383 

1384 @property 

1385 def isreftime(self): 

1386 '''True if the units are reference time units, False otherwise. 

1387 

1388 Note that time units (such as ``'days'``) are not reference time 

1389 units. 

1390 

1391 .. seealso:: `isdimensionless`, `islongitude`, `islatitude`, 

1392 `ispressure`, `istime` 

1393 

1394 **Examples:** 

1395 

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 

1408 

1409 ''' 

1410 return self._isreftime 

1411 

1412 @property 

1413 def iscalendartime(self): 

1414 '''True if the units are calendar time units, False otherwise. 

1415 

1416 Note that regular time units (such as ``'days'``) are not calendar 

1417 time units. 

1418 

1419 .. seealso:: `isdimensionless`, `islongitude`, `islatitude`, 

1420 `ispressure`, `isreftime`, `istime` 

1421 

1422 **Examples:** 

1423 

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 

1438 

1439 ''' 

1440 return bool(_ut_are_convertible(self._ut_unit, _calendartime_ut_unit)) 

1441 

1442 @property 

1443 def isdimensionless(self): 

1444 '''True if the units are dimensionless, false otherwise. 

1445 

1446 .. seealso:: `islongitude`, `islatitude`, `ispressure`, `isreftime`, 

1447 `istime` 

1448 

1449 **Examples:** 

1450 

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 

1469 

1470 ''' 

1471 return bool( 

1472 _ut_are_convertible(self._ut_unit, _dimensionless_unit_one)) 

1473 

1474 @property 

1475 def ispressure(self): 

1476 '''True if the units are pressure units, false otherwise. 

1477 

1478 .. seealso:: `isdimensionless`, `islongitude`, `islatitude`, 

1479 `isreftime`, `istime` 

1480 

1481 **Examples:** 

1482 

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 

1491 

1492 ''' 

1493 ut_unit = self._ut_unit 

1494 if ut_unit is None: 

1495 return False 

1496 

1497 return bool(_ut_are_convertible(ut_unit, _pressure_ut_unit)) 

1498 

1499 @property 

1500 def islatitude(self): 

1501 '''True if and only if the units are latitude units. 

1502 

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'``. 

1506 

1507 Note that units of ``'degrees'`` are not latitude units. 

1508 

1509 .. seealso:: `isdimensionless`, `islongitude`, `ispressure`, 

1510 `isreftime`, `istime` 

1511 

1512 **Examples:** 

1513 

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 

1524 

1525 ''' 

1526 return self._units in ('degrees_north', 'degree_north', 'degree_N', 

1527 'degrees_N', 'degreeN', 'degreesN') 

1528 

1529 @property 

1530 def islongitude(self): 

1531 '''True if and only if the units are longitude units. 

1532 

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'``. 

1536 

1537 Note that units of ``'degrees'`` are not longitude units. 

1538 

1539 .. seealso:: `isdimensionless`, `islatitude`, `ispressure`, 

1540 `isreftime`, `istime` 

1541 

1542 **Examples:** 

1543 

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 

1554 

1555 ''' 

1556 return self._units in ('degrees_east', 'degree_east', 'degree_E', 

1557 'degrees_E', 'degreeE', 'degreesE') 

1558 

1559 @property 

1560 def istime(self): 

1561 '''True if the units are time units, False otherwise. 

1562 

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. 

1566 

1567 .. seealso:: `iscalendartime`, `isdimensionless`, `islongitude`, 

1568 `islatitude`, `ispressure`, `isreftime` 

1569 

1570 **Examples:** 

1571 

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 

1588 

1589 ''' 

1590 if self._isreftime: 

1591 return False 

1592 

1593 ut_unit = self._ut_unit 

1594 if ut_unit is None: 

1595 return False 

1596 

1597 return bool(_ut_are_convertible(ut_unit, _day_ut_unit)) 

1598 

1599 @property 

1600 def isvalid(self): 

1601 '''Whether the units are valid. 

1602 

1603 .. seealso:: `reason_notvalid` 

1604 

1605 **Examples:** 

1606 

1607 >>> u = Units('km') 

1608 >>> u.isvalid 

1609 True 

1610 >>> u.reason_notvalid 

1611 '' 

1612 

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'" 

1623 

1624 ''' 

1625 return getattr(self, '_isvalid', False) 

1626 

1627 @property 

1628 def reason_notvalid(self): 

1629 '''The reason for invalid units. 

1630 

1631 If the units are valid then the reason is an empty string. 

1632 

1633 .. seealso:: `isvalid` 

1634 

1635 **Examples:** 

1636 

1637 >>> u = Units('km') 

1638 >>> u.isvalid 

1639 True 

1640 >>> u.reason_notvalid 

1641 '' 

1642 

1643 >>> u = Units('Bad Units') 

1644 >>> u.isvalid 

1645 False 

1646 >>> u.reason_notvalid 

1647 "Invalid units: 'Bad Units'; Not recognised by UDUNITS" 

1648 

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'" 

1654 

1655 ''' 

1656 return getattr(self, '_reason_notvalid', '') 

1657 

1658 @property 

1659 def reftime(self): 

1660 '''The reference date-time of reference time units. 

1661 

1662 .. seealso:: `calendar`, `isreftime`, `units` 

1663 

1664 :Returns: 

1665 

1666 `cftime.datetime` 

1667 

1668 **Examples:** 

1669 

1670 >>> u = Units('days since 2001-01-01', calendar='360_day') 

1671 >>> u.reftime 

1672 cftime.datetime(2001, 1, 1, 0, 0, 0, 0) 

1673 

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 

1687 

1688 calendar = self._canonical_calendar 

1689 return cftime.datetime( 

1690 *cftime._parse_date(self.units.split(' since ')[1])[:7], 

1691 calendar=calendar 

1692 ) 

1693 

1694 raise AttributeError( 

1695 "{!r} has no attribute 'reftime'".format(self)) 

1696 

1697 @property 

1698 def calendar(self): 

1699 '''The calendar for reference time units. 

1700 

1701 May be any string allowed by the calendar CF property. 

1702 

1703 If it is unset then the default CF calendar is assumed when 

1704 required. 

1705 

1706 .. seealso:: `units` 

1707 

1708 **Examples:** 

1709 

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' 

1716 

1717 ''' 

1718 value = self._calendar 

1719 if value is not None: 

1720 return value 

1721 

1722 raise AttributeError("%s has no attribute 'calendar'" % 

1723 self.__class__.__name__) 

1724 

1725 @property 

1726 def units(self): 

1727 '''The units. 

1728 

1729 May be any string allowed by the units CF property. 

1730 

1731 .. seealso:: `calendar` 

1732 

1733 **Examples:** 

1734 

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' 

1741 

1742 ''' 

1743 value = self._units 

1744 if value is not None: 

1745 return value 

1746 

1747 raise AttributeError("'%s' object has no attribute 'units'" % 

1748 self.__class__.__name__) 

1749 

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. 

1756 

1757 .. seealso:: `equals` 

1758 

1759 :Parameters: 

1760 

1761 other: `Units` 

1762 The other units. 

1763 

1764 :Returns: 

1765 

1766 `bool` 

1767 True if the units are equivalent, False otherwise. 

1768 

1769 **Examples:** 

1770 

1771 >>> u = Units('m') 

1772 >>> v = Units('km') 

1773 >>> w = Units('s') 

1774 

1775 >>> u.equivalent(v) 

1776 True 

1777 >>> u.equivalent(w) 

1778 False 

1779 

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) 

1783 

1784 >>> u.equivalent(v) 

1785 False 

1786 >>> u.equivalent(w) 

1787 True 

1788 

1789 ''' 

1790# if not self.isvalid or not other.isvalid: 

1791# return False 

1792 

1793 isreftime1 = self._isreftime 

1794 isreftime2 = other._isreftime 

1795 

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 

1823 

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 

1834 

1835 return out 

1836 else: 

1837 return False 

1838 

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 

1844 

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 

1860 

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 

1870 

1871 # Still here? 

1872 if not self and not other: 

1873 # Both units are null and therefore equivalent 

1874 return True 

1875 

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)) 

1879 

1880 # Still here? Then units are not equivalent. 

1881# return False 

1882 

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. 

1887 

1888 :Parameters: 

1889 

1890 names: `bool`, optional 

1891 Use unit names instead of symbols. 

1892 

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. 

1897 

1898 :Returns: 

1899 

1900 `str` or `None` 

1901 The formatted string. If the units have not yet been set, 

1902 then `None` is returned. 

1903 

1904 **Examples:** 

1905 

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' 

1917 

1918 >>> u = Units('dram') 

1919 >>> u.formatted(names=True) 

1920 '0.001771845 kilogram' 

1921 

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' 

1927 

1928 Formatting is also available during object initialization: 

1929 

1930 >>> u = Units('m/s', formatted=True) 

1931 >>> u.units 

1932 'm.s-1' 

1933 

1934 >>> u = Units('dram', names=True) 

1935 >>> u.units 

1936 '0.001771845 kilogram' 

1937 

1938 >>> u = Units('Watt') 

1939 >>> u.units 

1940 'Watt' 

1941 

1942 >>> u = Units('Watt', formatted=True) 

1943 >>> u.units 

1944 'W' 

1945 

1946 >>> u = Units('Watt', names=True) 

1947 >>> u.units 

1948 'watt' 

1949 

1950 >>> u = Units('Watt', definition=True) 

1951 >>> u.units 

1952 'm2.kg.s-3' 

1953 

1954 >>> u = Units('Watt', names=True, definition=True) 

1955 >>> u.units 

1956 'meter^2-kilogram-second^-3' 

1957 

1958 ''' 

1959 ut_unit = self._ut_unit 

1960 

1961 if ut_unit is None: 

1962 return None 

1963 

1964 opts = _UT_ASCII 

1965 if names: 

1966 opts |= _UT_NAMES 

1967 if definition: 

1968 opts |= _UT_DEFINITION 

1969 

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)) 

1974 

1975 if self.isreftime: 

1976 out = str(out, 'utf-8') # needs converting from byte-string 

1977 out += ' since ' + self.reftime.strftime() 

1978 return out 

1979 

1980 return out.decode('utf-8') 

1981 

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. 

1986 

1987 Returns the conformed values. 

1988 

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). 

1994 

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. 

1999 

2000 :Parameters: 

2001 

2002 x: `numpy.ndarray` or python numeric type or `list` or `tuple` 

2003 

2004 from_units: `Units` 

2005 The original units of *x* 

2006 

2007 to_units: `Units` 

2008 The units to which *x* should be conformed to. 

2009 

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. 

2016 

2017 If *x* is a `list` or `tuple` then the *inplace* parameter 

2018 is ignored and a `numpy` array is returned. 

2019 

2020 :Returns: 

2021 

2022 `numpy.ndarray` or python numeric 

2023 The modified numeric values. 

2024 

2025 **Examples:** 

2026 

2027 >>> Units.conform(2, Units('km'), Units('m')) 

2028 2000.0 

2029 

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.] 

2036 

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.] 

2043 

2044 ''' 

2045 if from_units.equals(to_units): 

2046 if not isinstance(x, (int, float)): 

2047 x = numpy_asanyarray(x) 

2048 

2049 if inplace: 

2050 return x 

2051 else: 

2052 try: 

2053 return x.copy() 

2054 except AttributeError: 

2055 x 

2056 # --- End: if 

2057 

2058 if not from_units.equivalent(to_units): 

2059 raise ValueError("Units are not convertible: {!r}, {!r}".format( 

2060 from_units, to_units)) 

2061 

2062 ut_unit1 = from_units._ut_unit 

2063 ut_unit2 = to_units._ut_unit 

2064 

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)) 

2068 

2069 convert = _ut_compare(ut_unit1, ut_unit2) 

2070 

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 

2093 

2094 inplace = True 

2095 

2096 ut_unit1 = from_units._ut_unit 

2097 ut_unit2 = to_units._ut_unit 

2098 

2099 convert = _ut_compare(ut_unit1, ut_unit2) 

2100 

2101 if units1 in _months_or_years: 

2102 to_units = cls('days since '+reftime1, 

2103 calendar=getattr(to_units, 'calendar', None)) 

2104 

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) 

2111 

2112 offset = to_jd0 - from_jd0 

2113# offset = to_units._utime._jd0 - from_units._utime._jd0 

2114 else: 

2115 offset = 0 

2116 

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 

2123 

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 

2132 

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 

2152 

2153 if x_is_numpy: 

2154 if not x.flags.contiguous: 

2155 x = numpy_array(x, order='C') 

2156# ARRRGGHH dch TODO 

2157 

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 

2181 

2182 # ------------------------------------------------------------ 

2183 # Convert the array to the new units 

2184 # ------------------------------------------------------------ 

2185 if convert: 

2186 

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]) 

2192 

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 

2210 

2211 _cv_free(cv_converter) 

2212 

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): 

2220 

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) 

2230 

2231 offset *= scale.item() 

2232 

2233 x -= offset 

2234 

2235 return x 

2236 

2237 def copy(self): 

2238 '''Return a deep copy. 

2239 

2240 Equivalent to ``copy.deepcopy(u)``. 

2241 

2242 :Returns: 

2243 

2244 The deep copy. 

2245 

2246 **Examples:** 

2247 

2248 >>> v = u.copy() 

2249 

2250 ''' 

2251 return self 

2252 

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. 

2257 

2258 .. seealso:: `equivalent` 

2259 

2260 :Parameters: 

2261 

2262 other: `Units` 

2263 The other units. 

2264 

2265 :Returns: 

2266 

2267 `bool` 

2268 `True` if the units are equal, `False` otherwise. 

2269 

2270 **Examples:** 

2271 

2272 >>> u = Units('km') 

2273 >>> v = Units('1000m') 

2274 >>> w = Units('100000m') 

2275 >>> u.equals(v) 

2276 True 

2277 >>> u.equals(w) 

2278 False 

2279 

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 

2289 

2290 Undefined units are considered equal: 

2291 

2292 >>> u = Units() 

2293 >>> v = Units() 

2294 >>> u.equals(v) 

2295 True 

2296 

2297 ''' 

2298 isreftime1 = self._isreftime 

2299 isreftime2 = other._isreftime 

2300 

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 

2308 

2309 return False 

2310 

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 

2319 

2320 return False 

2321 

2322 elif isreftime1 or isreftime2: 

2323 if verbose: 

2324 print("{}: Only one is reference time".format( 

2325 self.__class__.__name__)) # pragma: no cover 

2326 

2327 return False 

2328 

2329 

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# 

2368 

2369# if not self.isvalid or not other.isvalid: 

2370# print ('ppp') 

2371# return False 

2372 

2373 try: 

2374 if not _ut_compare(self._ut_unit, other._ut_unit): 

2375 return True 

2376 

2377 if verbose: 

2378 print("{}: Different units: {!r}, {!r}".format( 

2379 self.__class__.__name__, 

2380 self.units, other.units)) # pragma: no cover 

2381 

2382 return False 

2383 except AttributeError: 

2384 return False 

2385 

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 

2405 

2406 def log(self, base): 

2407 '''Return the logarithmic unit corresponding to the given logarithmic 

2408 base. 

2409 

2410 :Parameters: 

2411 

2412 base: `int` or `float` 

2413 The logarithmic base. 

2414 

2415 :Returns: 

2416 

2417 `Units` 

2418 The logarithmic unit corresponding to the given 

2419 logarithmic base. 

2420 

2421 **Examples:** 

2422 

2423 >>> u = Units('W', names=True) 

2424 >>> u 

2425 <Units: watt> 

2426 

2427 >>> u.log(10) 

2428 <Units: lg(re 1 W)> 

2429 >>> u.log(2) 

2430 <Units: lb(re 1 W)> 

2431 

2432 >>> import math 

2433 >>> u.log(math.e) 

2434 <Units: ln(re 1 W)> 

2435 

2436 >>> u.log(3.5) 

2437 <Units: 0.798235600147928 ln(re 1 W)> 

2438 

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 

2448 

2449 raise ValueError( 

2450 "Can't take the logarithm to the base {!r} of {!r}".format( 

2451 base, self)) 

2452# --- End: class 

2453 

2454 

2455class Utime(cftime.utime): 

2456 '''Performs conversions of netCDF time coordinate data to/from 

2457 datetime objects. 

2458 

2459 This object is (currently) functionally equivalent to a 

2460 `netCDF4.netcdftime.utime` object. 

2461 

2462 **Attributes** 

2463 

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 ============== ================================================== 

2473 

2474 ''' 

2475 def __init__(self, calendar, unit_string=None, 

2476 only_use_cftime_datetimes=True): 

2477 '''**Initialization** 

2478 

2479 :Parameters: 

2480 

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. 

2486 

2487 unit_string: `str`, optional 

2488 A string of the form "time-units since <time-origin>" 

2489 defining the reference-time units. 

2490 

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. 

2495 

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 

2507 

2508 def __repr__(self): 

2509 '''x.__repr__() <==> repr(x) 

2510 

2511 ''' 

2512 unit_string = self.unit_string 

2513 if unit_string: 

2514 x = [unit_string] 

2515 else: 

2516 x = [] 

2517 

2518 x.append(self.calendar) 

2519 

2520 return "<Utime: {}>".format(' '.join(x)) 

2521 

2522 def num2date(self, time_value): 

2523 '''Return a datetime-like object given a time value. 

2524 

2525 The units of the time value are described by the `!unit_string` 

2526 and `!calendar` attributes. 

2527 

2528 See `netCDF4.netcdftime.utime.num2date` for details. 

2529 

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. 

2533 

2534 ''' 

2535 units = self.units 

2536 unit_string = self.unit_string 

2537 

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 

2546 

2547 u = cftime.utime(unit_string, self.calendar) 

2548 

2549 return u.num2date(time_value) 

2550 

2551# --- End: class