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 operator 

4import sys 

5 

6from copy import deepcopy 

7 

8from numpy import array as numpy_array 

9from numpy import asanyarray as numpy_asanyarray 

10from numpy import dtype as numpy_dtype 

11from numpy import generic as numpy_generic 

12from numpy import ndarray as numpy_ndarray 

13from numpy import shape as numpy_shape 

14from numpy import size as numpy_size 

15 

16 

17# -------------------------------------------------------------------- 

18# Aliases for ctypes 

19# -------------------------------------------------------------------- 

20_sizeof_buffer = 257 

21_string_buffer = ctypes.create_string_buffer(_sizeof_buffer) 

22_c_char_p = ctypes.c_char_p 

23_c_int = ctypes.c_int 

24_c_uint = ctypes.c_uint 

25_c_float = ctypes.c_float 

26_c_double = ctypes.c_double 

27_c_size_t = ctypes.c_size_t 

28_c_void_p = ctypes.c_void_p 

29_pointer = ctypes.pointer 

30_POINTER = ctypes.POINTER 

31 

32_ctypes_POINTER = {4: _POINTER(_c_float), 

33 8: _POINTER(_c_double)} 

34 

35# -------------------------------------------------------------------- 

36# Load the Udunits-2 library and read the database 

37# -------------------------------------------------------------------- 

38#if sys.platform == 'darwin': 

39# # This has been tested on Mac OSX 10.5.8 and 10.6.8 

40# _udunits = ctypes.CDLL('libudunits2.0.dylib') 

41#else: 

42# # Linux 

43# _udunits = ctypes.CDLL('libudunits2.so.0') 

44_libpath = ctypes.util.find_library('udunits2') 

45if _libpath is None: 

46 raise FileNotFoundError( 

47 "cfunits requires UNIDATA UDUNITS-2. Can't find the 'udunits2' library.") 

48 

49_udunits = ctypes.CDLL(_libpath) 

50 

51# Suppress "overrides prefixed-unit" messages. This also suppresses 

52# all other error messages - so watch out! 

53# 

54# Messages may be turned back on by calling the module function 

55# udunits_error_messages. 

56# ut_error_message_handler ut_set_error_message_handler( 

57# ut_error_message_handler handler); 

58_ut_set_error_message_handler = _udunits.ut_set_error_message_handler 

59_ut_set_error_message_handler.argtypes = (_c_void_p, ) 

60_ut_set_error_message_handler.restype = _c_void_p 

61 

62_ut_set_error_message_handler(_udunits.ut_ignore) 

63 

64# Read the data base 

65# ut_system* ut_read_xml(const char* path); 

66_ut_read_xml = _udunits.ut_read_xml 

67_ut_read_xml.argtypes = (_c_char_p, ) 

68_ut_read_xml.restype = _c_void_p 

69 

70#print('units: before _udunits.ut_read_xml(',_unit_database,')') 

71_ut_system = _ut_read_xml(None) 

72#print('units: after _udunits.ut_read_xml(',_unit_database,')') 

73 

74# Reinstate the reporting of error messages 

75#_ut_set_error_message_handler(_udunits.ut_write_to_stderr) 

76 

77# -------------------------------------------------------------------- 

78# Aliases for the UDUNITS-2 C API. See 

79# http://www.unidata.ucar.edu/software/udunits/udunits-2.0.4/udunits2lib.html 

80# for documentation. 

81# -------------------------------------------------------------------- 

82# int ut_format(const ut_unit* const unit, char* buf, size_t size, unsigned opts); 

83_ut_format = _udunits.ut_format 

84_ut_format.argtypes = (_c_void_p, _c_char_p, _c_size_t, _c_uint) 

85_ut_format.restype = _c_int 

86 

87# char* ut_trim(char* const string, const ut_encoding encoding); 

88_ut_trim = _udunits.ut_trim 

89_ut_trim.argtypes = (_c_char_p, _c_int) # ut_encoding assumed to be int! 

90_ut_trim.restype = _c_char_p 

91 

92# ut_unit* ut_parse(const ut_system* const system, 

93# const char* const string, const ut_encoding encoding); 

94_ut_parse = _udunits.ut_parse 

95_ut_parse.argtypes = (_c_void_p, _c_char_p, _c_int) # ut_encoding assumed to be int! 

96_ut_parse.restype = _c_void_p 

97 

98# int ut_compare(const ut_unit* const unit1, const ut_unit* const 

99# unit2); 

100_ut_compare = _udunits.ut_compare 

101_ut_compare.argtypes = (_c_void_p, _c_void_p) 

102_ut_compare.restype = _c_int 

103 

104# int ut_are_convertible(const ut_unit* const unit1, const ut_unit* 

105# const unit2); 

106_ut_are_convertible = _udunits.ut_are_convertible 

107_ut_are_convertible.argtypes = (_c_void_p, _c_void_p) 

108_ut_are_convertible.restype = _c_int 

109 

110# cv_converter* ut_get_converter(ut_unit* const from, ut_unit* const 

111# to); 

112_ut_get_converter = _udunits.ut_get_converter 

113_ut_get_converter.argtypes = (_c_void_p, _c_void_p) 

114_ut_get_converter.restype = _c_void_p 

115 

116# ut_unit* ut_divide(const ut_unit* const numer, const ut_unit* const 

117# denom); 

118_ut_divide = _udunits.ut_divide 

119_ut_divide.argtypes = (_c_void_p, _c_void_p) 

120_ut_divide.restype = _c_void_p 

121 

122# ut_unit* ut_offset(const ut_unit* const unit, const double offset); 

123_ut_offset = _udunits.ut_offset 

124_ut_offset.argtypes = (_c_void_p, _c_double) 

125_ut_offset.restype = _c_void_p 

126 

127# ut_unit* ut_raise(const ut_unit* const unit, const int power); 

128_ut_raise = _udunits.ut_raise 

129_ut_raise.argtypes = (_c_void_p, _c_int) 

130_ut_raise.restype = _c_void_p 

131 

132# ut_unit* ut_scale(const double factor, const ut_unit* const unit); 

133_ut_scale = _udunits.ut_scale 

134_ut_scale.argtypes = (_c_double, _c_void_p) 

135_ut_scale.restype = _c_void_p 

136 

137# ut_unit* ut_multiply(const ut_unit* const unit1, const ut_unit* 

138# const unit2); 

139_ut_multiply = _udunits.ut_multiply 

140_ut_multiply.argtypes = (_c_void_p, _c_void_p) 

141_ut_multiply.restype = _c_void_p 

142 

143# ut_unit* ut_log(const double base, const ut_unit* const reference); 

144_ut_log = _udunits.ut_log 

145_ut_log.argtypes = (_c_double, _c_void_p) 

146_ut_log.restype = _c_void_p 

147 

148# ut_unit* ut_root(const ut_unit* const unit, const int root); 

149_ut_root = _udunits.ut_root 

150_ut_root.argtypes = (_c_void_p, _c_int) 

151_ut_root.restype = _c_void_p 

152 

153# void ut_free_system(ut_system* system); 

154_ut_free = _udunits.ut_free 

155_ut_free.argypes = (_c_void_p, ) 

156_ut_free.restype = None 

157 

158# float* cv_convert_floats(const cv_converter* converter, const float* 

159# const in, const size_t count, float* out); 

160_cv_convert_floats = _udunits.cv_convert_floats 

161_cv_convert_floats.argtypes = (_c_void_p, _c_void_p, _c_size_t, _c_void_p) 

162_cv_convert_floats.restype = _c_void_p 

163 

164# double* cv_convert_doubles(const cv_converter* converter, const 

165# double* const in, const size_t count, 

166# double* out); 

167_cv_convert_doubles = _udunits.cv_convert_doubles 

168_cv_convert_doubles.argtypes = (_c_void_p, _c_void_p, _c_size_t, _c_void_p) 

169_cv_convert_doubles.restype = _c_void_p 

170 

171# double cv_convert_double(const cv_converter* converter, const double 

172# value); 

173_cv_convert_double = _udunits.cv_convert_double 

174_cv_convert_double.argtypes = (_c_void_p, _c_double) 

175_cv_convert_double.restype = _c_double 

176 

177# void cv_free(cv_converter* const conv); 

178_cv_free = _udunits.cv_free 

179_cv_free.argtypes = (_c_void_p, ) 

180_cv_free.restype = None 

181 

182_UT_ASCII = 0 

183_UT_NAMES = 4 

184_UT_DEFINITION = 8 

185 

186_cv_convert_array = {4: _cv_convert_floats, 

187 8: _cv_convert_doubles} 

188 

189# Some function definitions necessary for the following 

190# changes to the unit system. 

191_ut_get_unit_by_name = _udunits.ut_get_unit_by_name 

192_ut_get_unit_by_name.argtypes = (_c_void_p, _c_char_p) 

193_ut_get_unit_by_name.restype = _c_void_p 

194_ut_get_status = _udunits.ut_get_status 

195_ut_get_status.restype = _c_int 

196_ut_unmap_symbol_to_unit = _udunits.ut_unmap_symbol_to_unit 

197_ut_unmap_symbol_to_unit.argtypes = (_c_void_p, _c_char_p, _c_int) 

198_ut_unmap_symbol_to_unit.restype = _c_int 

199_ut_map_symbol_to_unit = _udunits.ut_map_symbol_to_unit 

200_ut_map_symbol_to_unit.argtypes = (_c_char_p, _c_int, _c_void_p) 

201_ut_map_symbol_to_unit.restype = _c_int 

202_ut_map_unit_to_symbol = _udunits.ut_map_unit_to_symbol 

203_ut_map_unit_to_symbol.argtypes = (_c_void_p, _c_char_p, _c_int) 

204_ut_map_unit_to_symbol.restype = _c_int 

205_ut_map_name_to_unit = _udunits.ut_map_name_to_unit 

206_ut_map_name_to_unit.argtypes = (_c_char_p, _c_int, _c_void_p) 

207_ut_map_name_to_unit.restype = _c_int 

208_ut_map_unit_to_name = _udunits.ut_map_unit_to_name 

209_ut_map_unit_to_name.argtypes = (_c_void_p, _c_char_p, _c_int) 

210_ut_map_unit_to_name.restype = _c_int 

211_ut_new_base_unit = _udunits.ut_new_base_unit 

212_ut_new_base_unit.argtypes = (_c_void_p, ) 

213_ut_new_base_unit.restype = _c_void_p 

214 

215# Change Sv mapping. Both sievert and sverdrup are just aliases, 

216# so no unit to symbol mapping needs to be changed. 

217# We don't need to remove rem, since it was constructed with 

218# the correct sievert mapping in place; because that mapping 

219# was only an alias, the unit now doesn't depend on the mapping 

220# persisting. 

221assert(0 == _ut_unmap_symbol_to_unit(_ut_system, _c_char_p(b'Sv'), _UT_ASCII)) 

222assert(0 == _ut_map_symbol_to_unit(_c_char_p(b'Sv'), _UT_ASCII, 

223 _ut_get_unit_by_name(_ut_system, _c_char_p(b'sverdrup')))) 

224 

225# Add new base unit calendar_year 

226calendar_year_unit = _ut_new_base_unit(_ut_system) 

227assert(0 == _ut_map_symbol_to_unit(_c_char_p(b'cY'), _UT_ASCII, calendar_year_unit)) 

228assert(0 == _ut_map_unit_to_symbol(calendar_year_unit, _c_char_p(b'cY'), _UT_ASCII)) 

229assert(0 == _ut_map_name_to_unit(_c_char_p(b'calendar_year'), _UT_ASCII, calendar_year_unit)) 

230assert(0 == _ut_map_unit_to_name(calendar_year_unit, _c_char_p(b'calendar_year'), _UT_ASCII)) 

231assert(0 == _ut_map_name_to_unit(_c_char_p(b'calendar_years'), _UT_ASCII, calendar_year_unit)) 

232 

233# Add various aliases useful for CF 

234def add_unit_alias(definition, symbol, singular, plural): 

235 unit = _ut_parse(_ut_system, _c_char_p(definition.encode('utf-8')), _UT_ASCII) 

236 if symbol is not None: 

237 assert(0 == _ut_map_symbol_to_unit(_c_char_p(symbol.encode('utf-8')), _UT_ASCII, unit)) 

238 if singular is not None: 

239 assert(0 == _ut_map_name_to_unit(_c_char_p(singular.encode('utf-8')), _UT_ASCII, unit)) 

240 if plural is not None: 

241 assert(0 == _ut_map_name_to_unit(_c_char_p(plural.encode('utf-8')), _UT_ASCII, unit)) 

242 

243add_unit_alias("1.e-3", "psu", "practical_salinity_unit", "practical_salinity_units") 

244add_unit_alias("calendar_year/12", "cM", "calendar_month", "calendar_months") 

245add_unit_alias("1", None, "level", "levels") 

246add_unit_alias("1", None, "layer", "layers") 

247add_unit_alias("1", None, "sigma_level", "sigma_levels") 

248add_unit_alias("1", "dB", "decibel", "debicels") 

249add_unit_alias("10 dB", None, "bel", "bels") 

250 

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

252# _ut_system, 'calendar_year') 

253 

254# -------------------------------------------------------------------- 

255# Create a calendar year unit 

256# -------------------------------------------------------------------- 

257#_udunits.ut_map_name_to_unit('calendar_year', _UT_ASCII, 

258# _udunits.ut_new_base_unit(_ut_system)) 

259 

260# -------------------------------------------------------------------- 

261# Aliases for netCDF4.netcdftime classes 

262# -------------------------------------------------------------------- 

263import cftime 

264#_netCDF4_netcdftime_utime = cftime.utime 

265_datetime = cftime.datetime 

266 

267# -------------------------------------------------------------------- 

268# Aliases for netCDF4.netcdftime functions 

269# -------------------------------------------------------------------- 

270_num2date = cftime.num2date 

271_date2num = cftime.date2num 

272 

273_cached_ut_unit = {} 

274_cached_utime = {} 

275 

276# -------------------------------------------------------------------- 

277# Save some useful units 

278# -------------------------------------------------------------------- 

279# A time ut_unit (equivalent to 'day', 'second', etc.) 

280_day_ut_unit = _ut_parse(_ut_system, _c_char_p(b'day'), _UT_ASCII) 

281_cached_ut_unit['days'] = _day_ut_unit 

282# A pressure ut_unit (equivalent to 'Pa', 'hPa', etc.) 

283_pressure_ut_unit = _ut_parse(_ut_system, _c_char_p(b'pascal'), _UT_ASCII) 

284_cached_ut_unit['pascal'] = _pressure_ut_unit 

285# A calendar time ut_unit (equivalent to 'cY', 'cM') 

286_calendartime_ut_unit = _ut_parse(_ut_system, _c_char_p(b'calendar_year'), _UT_ASCII) 

287_cached_ut_unit['calendar_year'] = _calendartime_ut_unit 

288# A dimensionless unit one (equivalent to '', '1', '2', etc.) 

289#_dimensionless_unit_one = _udunits.ut_get_dimensionless_unit_one(_ut_system) 

290#_cached_ut_unit[''] = _dimensionless_unit_one 

291#_cached_ut_unit['1'] = _dimensionless_unit_one 

292 

293_dimensionless_unit_one = _ut_parse(_ut_system, _c_char_p(b'1'), _UT_ASCII) 

294_cached_ut_unit[''] = _dimensionless_unit_one 

295_cached_ut_unit['1'] = _dimensionless_unit_one 

296 

297# -------------------------------------------------------------------- 

298# Set the default calendar type according to the CF conventions 

299# -------------------------------------------------------------------- 

300_default_calendar = 'gregorian' 

301_canonical_calendar = {'gregorian' : 'gregorian' , 

302 'standard' : 'gregorian' , 

303 'none' : 'gregorian' , 

304 'proleptic_gregorian': 'proleptic_gregorian', 

305 '360_day' : '360_day' , 

306 'noleap' : '365_day' , 

307 '365_day' : '365_day' , 

308 'all_leap' : '366_day' , 

309 '366_day' : '366_day' , 

310 'julian' : 'julian' , 

311 } 

312 

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

314 

315## -------------------------------------------------------------------- 

316## Set month lengths in days for non-leap years (_days_in_month[0,1:]) 

317## and leap years (_days_in_month[1,1:]) 

318## -------------------------------------------------------------------- 

319#_days_in_month = numpy_array( 

320# [[-99, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], 

321# [-99, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]]) 

322 

323# -------------------------------------------------------------------- 

324# Function to control Udunits error messages 

325# -------------------------------------------------------------------- 

326def udunits_error_messages(flag): 

327 '''Control the printing of error messages from Udunits, which are 

328 turned off by default. 

329  

330 :Parameters: 

331  

332 flag: `bool` 

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

334 not print Udunits error messages. 

335  

336 :Returns: 

337  

338 `None` 

339  

340 **Examples:** 

341  

342 >>> udunits_error_messages(True) 

343 >>> udunits_error_messages(False) 

344 

345 ''' 

346 if flag: 

347 _ut_set_error_message_handler(_udunits.ut_write_to_stderr) 

348 else: 

349 _ut_set_error_message_handler(_udunits.ut_ignore) 

350 

351#def _month_length(year, month, calendar, _days_in_month=_days_in_month): 

352# ''' 

353# 

354#Find month lengths in days for each year/month pairing in the input 

355#numpy arrays 'year' and 'month', both of which must have the same 

356#shape. 'calendar' must be one of the standard CF calendar types. 

357# 

358#:Parameters: 

359# 

360# 

361#''' 

362# shape = month.shape 

363# if calendar in ('standard', 'gregorian'): 

364# leap = numpy_where(year % 4 == 0, 1, 0) 

365# leap = numpy_where((year > 1582) & 

366# (year % 100 == 0) & (year % 400 != 0), 

367# 0, leap) 

368# elif calendar == '360_day': 

369# days_in_month = numpy_empty(shape) 

370# days_in_month.fill(30) 

371# return days_in_month 

372# 

373# elif calendar in ('all_leap', '366_day'): 

374# leap = numpy_zeros(shape) 

375# 

376# elif calendar in ('no_leap', '365_day'): 

377# leap = numpy_ones(shape) 

378# 

379# elif calendar == 'proleptic_gregorian': 

380# leap = numpy_where(year % 4 == 0, 1, 0) 

381# leap = numpy_where((year % 100 == 0) & (year % 400 != 0), 

382# 0, leap) 

383# 

384# days_in_month = numpy_array([_days_in_month[l, m] 

385# for l, m in zip(leap.flat, month.flat)]) 

386# days_in_month.resize(shape) 

387# 

388# return days_in_month 

389# 

390#def _proper_date(year, month, day, calendar, fix=False, 

391# _days_in_month=_days_in_month): 

392# ''' 

393# 

394#Given equally shaped numpy arrays of 'year', 'month', 'day' adjust 

395#them *in place* to be proper dates. 'calendar' must be one of the 

396#standard CF calendar types. 

397# 

398#Excessive number of months are converted to years but excessive days 

399#are not converted to months nor years. If a day is illegal in the 

400#proper date then a ValueError is raised, unless 'fix' is True, in 

401#which case the day is lowered to the nearest legal date: 

402# 

403# 2000/26/1 -> 2002/3/1 

404# 

405# 2001/2/31 -> ValueError if 'fix' is False 

406# 2001/2/31 -> 2001/2/28 if 'fix' is True 

407# 2001/2/99 -> 2001/2/28 if 'fix' is True 

408# 

409#:Parameters: 

410# 

411#''' 

412## y, month = divmod(month, 12) 

413## year += y 

414# 

415# year += month // 12 

416# month[...] = month % 12 

417# 

418# mask = (month == 0) 

419# year[...] = numpy_where(mask, year-1, year) 

420# month[...] = numpy_where(mask, 12, month) 

421# del mask 

422# 

423# days_in_month = _month_length(year, month, calendar, 

424# _days_in_month=_days_in_month) 

425# 

426# if fix: 

427# day[...] = numpy_where(day > days_in_month, days_in_month, day) 

428# elif (day > days_in_month).any(): 

429# raise ValueError("Illegal date(s) in %s calendar" % calendar) 

430# 

431# return year, month, day 

432 

433 

434# -------------------------------------------------------------------- 

435# Constants, as defined by UDUNITS 

436# -------------------------------------------------------------------- 

437_year_length = 365.242198781 

438_month_length = _year_length / 12 

439 

440 

441class Units(): 

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

443 values to different units. 

444  

445 Units are as defined in UNIDATA's Udunits-2 package, with a few 

446 exceptions for greater consistency with the CF conventions namely 

447 support for CF calendars and new units definitions. 

448  

449  

450 **Modifications to the standard Udunits database** 

451  

452 Whilst a standard Udunits-2 database may be used, greater 

453 consistency with CF is achieved by using a modified database. The 

454 following units are either new to, modified from, or removed from 

455 the standard Udunits-2 database (version 2.1.24): 

456  

457 ======================= ====== ============ ============== 

458 Unit name Symbol Definition Status 

459 ======================= ====== ============ ============== 

460 practical_salinity_unit psu 1e-3 New unit 

461 level 1 New unit 

462 sigma_level 1 New unit 

463 layer 1 New unit 

464 decibel dB 1 New unit 

465 bel 10 dB New unit 

466 sverdrup Sv 1e6 m3 s-1 Added symbol 

467 sievert J kg-1 Removed symbol 

468 ======================= ====== ============ ============== 

469  

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

471 ``practical_salinity_units``. 

472  

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

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

475  

476  

477 **Accessing units** 

478  

479 Units may be set, retrieved and deleted via the `units` 

480 attribute. Its value is a string that can be recognized by 

481 UNIDATA's Udunits-2 package, with the few exceptions given in the 

482 CF conventions. 

483  

484 >>> u = Units('m s-1') 

485 >>> u 

486 <Cf Units: 'm s-1'> 

487 >>> u.units = 'days since 2004-3-1' 

488 >>> u 

489 <Units: days since 2004-3-1> 

490  

491  

492 **Equality and equivalence of units** 

493  

494 There are methods for assessing whether two units are equivalent 

495 or equal. Two units are equivalent if numeric values in one unit 

496 are convertible to numeric values in the other unit (such as 

497 ``kilometres`` and ``metres``). Two units are equal if they are 

498 equivalent and their conversion is a scale factor of 1 and an 

499 offset of 0 (such as ``kilometres`` and ``1000 metres``). Note 

500 that equivalence and equality are based on internally stored 

501 binary representations of the units, rather than their string 

502 representations. 

503  

504 >>> u = Units('m/s') 

505 >>> v = Units('m s-1') 

506 >>> w = Units('km.s-1') 

507 >>> x = Units('0.001 kilometer.second-1') 

508 >>> y = Units('gram') 

509  

510 >>> u.equivalent(v), u.equals(v), u == v 

511 (True, True, True) 

512 >>> u.equivalent(w), u.equals(w) 

513 (True, False) 

514 >>> u.equivalent(x), u.equals(x) 

515 (True, True) 

516 >>> u.equivalent(y), u.equals(y) 

517 (False, False) 

518  

519  

520 **Time and reference time units** 

521  

522 Time units may be given as durations of time (*time units*) or as 

523 an amount of time since a reference time (*reference time units*): 

524  

525 >>> v = Units() 

526 >>> v.units = 's' 

527 >>> v.units = 'day' 

528 >>> v.units = 'days since 1970-01-01' 

529 >>> v.units = 'seconds since 1992-10-8 15:15:42.5 -6:00' 

530  

531 .. note:: It is recommended that the units ``year`` and ``month`` 

532 be used with caution, as explained in the following 

533 excerpt from the CF conventions: "The Udunits package 

534 defines a year to be exactly 365.242198781 days (the 

535 interval between 2 successive passages of the sun 

536 through vernal equinox). It is not a calendar 

537 year. Udunits includes the following definitions for 

538 years: a common_year is 365 days, a leap_year is 366 

539 days, a Julian_year is 365.25 days, and a Gregorian_year 

540 is 365.2425 days. For similar reasons the unit 

541 ``month``, which is defined to be exactly year/12, 

542 should also be used with caution." 

543  

544 **Calendar** 

545  

546 The date given in reference time units is associated with one of 

547 the calendars recognized by the CF conventions and may be set with 

548 the `calendar` attribute. However, as in the CF conventions, if 

549 the calendar is not set then, for the purposes of calculation and 

550 comparison, it defaults to the mixed Gregorian/Julian calendar as 

551 defined by Udunits: 

552  

553 >>> u = Units('days since 2000-1-1') 

554 >>> u.calendar 

555 AttributeError: Can't get 'Units' attribute 'calendar' 

556 >>> v = Units('days since 2000-1-1') 

557 >>> v.calendar = 'gregorian' 

558 >>> v.equals(u) 

559 True 

560  

561  

562 **Arithmetic with units** 

563  

564 The following operators, operations and assignments are 

565 overloaded: 

566  

567 Comparison operators: 

568  

569 ``==, !=`` 

570  

571 Binary arithmetic operations: 

572  

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

574  

575 Unary arithmetic operations: 

576  

577 ``-, +`` 

578  

579 Augmented arithmetic assignments: 

580  

581 ``+=, -=, *=, /=, **=`` 

582  

583 The comparison operations return a boolean and all other 

584 operations return a new units object or modify the units object in 

585 place. 

586  

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

588 <Units: m> 

589  

590 >>> v = u * 1000 

591 >>> v 

592 <Units: 1000 m> 

593  

594 >>> u == v 

595 False 

596 >>> u != v 

597 True 

598  

599 >>> u **= 2 

600 >>> u 

601 <Units: m2> 

602  

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

604 corresponding to the given logarithmic base: 

605  

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

607 >>> u.log(10) 

608 <Units: lg(re 1 s)> 

609  

610  

611 **Modifying data for equivalent units** 

612  

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

614 equivalent units using the `conform` static method. 

615  

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

617 2000.0 

618  

619 >>> import numpy 

620 >>> a = numpy.arange(5.0) 

621 >>> Units.conform(a, Units('minute'), Units('second')) 

622 array([ 0., 60., 120., 180., 240.]) 

623 >>> a 

624 array([ 0., 1., 2., 3., 4.]) 

625  

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

627 in place, without any copying overheads: 

628  

629 >>> Units.conform(a, 

630 Units('days since 2000-12-1'), 

631 Units('days since 2001-1-1'), inplace=True) 

632 array([-31., -30., -29., -28., -27.]) 

633 >>> a 

634 array([-31., -30., -29., -28., -27.]) 

635 

636 ''' 

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

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

639 '''**Initialization** 

640 

641 :Parameters: 

642  

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

644 Set the new units from this string. 

645  

646 calendar: `str`, optional 

647 Set the calendar for reference time units. 

648  

649 formatted: `bool`, optional 

650 Format the string representation of the units in a 

651 standardized manner. See the `formatted` method. 

652  

653 names: `bool`, optional 

654 Format the string representation of the units using names 

655 instead of symbols. See the `format` method. 

656  

657 definition: `bool`, optional 

658 Format the string representation of the units using basic 

659 units. See the `format` method. 

660  

661 _ut_unit: `int`, optional 

662 Set the new units from this Udunits binary unit 

663 representation. This should be an integer returned by a 

664 call to `ut_parse` function of Udunits. Ignored if `units` 

665 is set. 

666 

667 ''' 

668 

669 if isinstance(units, self.__class__): 

670 self.__dict__ = units.__dict__ 

671 return 

672 

673 self._isvalid = True 

674 self._reason_notvalid = '' 

675 self._units = units 

676 self._ut_unit = None 

677 self._isreftime = False 

678 self._calendar = calendar 

679 self._canonical_calendar = None 

680 self._utime = None 

681 self._units_since_reftime = None 

682 

683 # Set the calendar 

684 _calendar = None 

685 if calendar is not None: 

686 _calendar = _canonical_calendar.get(calendar.lower()) 

687 if _calendar is None: 

688 self._new_reason_notvalid( 

689 "Invalid calendar={!r}".format(calendar)) 

690 self._isvalid = False 

691 _calendar = calendar 

692 # --- End: if 

693 

694 if units is not None: 

695 try: 

696 units = units.strip() 

697 except AttributeError: 

698 self._isvalid = False 

699 self._new_reason_notvalid( 

700 "Bad units type: {}".format(type(units))) 

701 return 

702 

703 unit = None 

704 

705 if isinstance(units, str) and ' since ' in units: 

706 # ---------------------------------------------------- 

707 # Set a reference time unit 

708 # ---------------------------------------------------- 

709 # Set the calendar 

710 if calendar is None: 

711 _calendar = _default_calendar 

712 else: 

713 _calendar = _canonical_calendar.get(calendar.lower()) 

714 if _calendar is None: 

715 _calendar = calendar 

716 # --- End: if 

717 

718 units_split = units.split(' since ') 

719 unit = units_split[0].strip() 

720 

721 _units_since_reftime = unit 

722 

723 ut_unit = _cached_ut_unit.get(unit, None) 

724 if ut_unit is None: 

725 ut_unit = _ut_parse(_ut_system, _c_char_p( 

726 unit.encode('utf-8')), _UT_ASCII) 

727 if not ut_unit or not _ut_are_convertible( 

728 ut_unit, _day_ut_unit): 

729 ut_unit = None 

730 self._isvalid = False 

731 else: 

732 _cached_ut_unit[unit] = ut_unit 

733 # --- End: if 

734 

735 utime = None 

736 

737 if (_calendar, units) in _cached_utime: 

738 utime = _cached_utime[(_calendar, units)] 

739 else: 

740 # Create a new Utime object 

741 unit_string = '{} since {}'.format( 

742 unit, units_split[1].strip()) 

743 

744 if (_calendar, unit_string) in _cached_utime: 

745 utime = _cached_utime[(_calendar, unit_string)] 

746 else: 

747 try: 

748 utime = Utime(_calendar, unit_string) 

749 except Exception as error: 

750 utime = None 

751 if unit in _months_or_years: 

752 temp_unit_string = 'days since {}'.format( 

753 units_split[1].strip()) 

754 try: 

755 _ = Utime(_calendar, temp_unit_string) 

756 except Exception as error: 

757 self._new_reason_notvalid(str(error)) 

758 self._isvalid = False 

759 else: 

760 self._new_reason_notvalid(str(error)) 

761 self._isvalid = False 

762 # --- End: try 

763 

764 _cached_utime[(_calendar, unit_string)] = utime 

765 # --- End: if 

766 

767 self._isreftime = True 

768 self._calendar = calendar 

769 self._canonical_calendar = _calendar 

770 self._utime = utime 

771 

772 else: 

773 # ---------------------------------------------------- 

774 # Set a unit other than a reference time unit 

775 # ---------------------------------------------------- 

776 ut_unit = _cached_ut_unit.get(units, None) 

777 if ut_unit is None: 

778 ut_unit = _ut_parse(_ut_system, 

779 _c_char_p(units.encode('utf-8')), 

780 _UT_ASCII) 

781 if not ut_unit: 

782 ut_unit = None 

783 self._isvalid = False 

784 self._new_reason_notvalid( 

785 "Invalid units: {!r}; " 

786 "Not recognised by UDUNITS".format(units)) 

787 else: 

788 _cached_ut_unit[units] = ut_unit 

789 # --- End: if 

790 

791# if ut_unit is None: 

792# ut_unit = _ut_parse(_ut_system, _c_char_p(units.encode('utf-8')), _UT_ASCII) 

793# if not ut_unit: 

794# raise ValueError( 

795# "Can't set unsupported unit: %r" % units) 

796# _cached_ut_unit[units] = ut_unit 

797 

798 self._isreftime = False 

799 self._calendar = None 

800 self._canonial_calendar = None 

801 self._utime = None 

802 

803 self._ut_unit = ut_unit 

804 self._units = units 

805 self._units_since_reftime = unit 

806 

807 if formatted or names or definition: 

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

809 

810 return 

811 

812 elif calendar: 

813 # --------------------------------------------------------- 

814 # Calendar is set, but units are not. 

815 # --------------------------------------------------------- 

816 self._units = None 

817 self._ut_unit = None 

818 self._isreftime = True 

819 self._calendar = calendar 

820 self._canonical_calendar = _canonical_calendar[calendar.lower()] 

821 self._units_since_reftime = None 

822 try: 

823 self._utime = Utime(_canonical_calendar[calendar.lower()]) 

824 except Exception as error: 

825 self._new_reason_notvalid( 

826 'Invalid calendar={!r}'.format(calendar)) 

827 self._isvalid = True 

828 

829 return 

830 

831 if _ut_unit is not None: 

832 # --------------------------------------------------------- 

833 # _ut_unit is set 

834 # --------------------------------------------------------- 

835 self._ut_unit = _ut_unit 

836 self._isreftime = False 

837 

838 units = self.formatted(names, definition) 

839 _cached_ut_unit[units] = _ut_unit 

840 self._units = units 

841 

842 self._units_since_reftime = None 

843 

844 self._calendar = None 

845 self._utime = None 

846 

847 return 

848 

849 # ------------------------------------------------------------- 

850 # Nothing has been set 

851 # ------------------------------------------------------------- 

852 self._units = None 

853 self._ut_unit = None 

854 self._isreftime = False 

855 self._calendar = None 

856 self._canonical_calendar = None 

857 self._utime = None 

858 self._units_since_reftime = None 

859 

860 def __getstate__(self): 

861 '''Called when pickling. 

862 

863 :Returns: 

864  

865 `dict` 

866 A dictionary of the instance's attributes 

867  

868 **Examples:** 

869  

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

871 >>> u.__getstate__() 

872 {'calendar': 'gregorian', 

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

874 

875 ''' 

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

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

878 if hasattr(self, attr)]) 

879 

880 def __setstate__(self, odict): 

881 '''Called when unpickling. 

882 

883 :Parameters: 

884  

885 odict: `dict` 

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

887  

888 :Returns: 

889  

890 `None` 

891 

892 ''' 

893 units = None 

894 if '_units' in odict: 

895 units = odict['_units'] 

896 

897 calendar = None 

898 if '_calendar' in odict: 

899 calendar = odict['_calendar'] 

900 

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

902 

903 def __hash__(self): 

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

905 

906 ''' 

907 if not self._isreftime: 

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

909 

910 return hash(('Units', 

911 self._ut_unit, self._rtime_jd0, self._rtime_calendar, 

912 self._rtime_tzoffset)) 

913 

914 def __repr__(self): 

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

916 

917 ''' 

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

919 

920 def __str__(self): 

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

922 

923 ''' 

924 string = [] 

925 if self._units is not None: 

926 if self._units == '': 

927 string.append("\'\'") 

928 else: 

929 string.append(str(self._units)) 

930 # --- End: if 

931 

932 if self._calendar is not None: 

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

934 

935 return ' '.join(string) 

936 

937 def __deepcopy__(self, memo): 

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

939 

940 ''' 

941 return self 

942 

943 def __bool__(self): 

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

945 

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

947 

948 ''' 

949 return self._ut_unit is not None 

950 

951 def __eq__(self, other): 

952 '''The rich comparison operator ``==`` 

953 

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

955 

956 ''' 

957 return self.equals(other) 

958 

959 def __ne__(self, other): 

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

961 

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

963 

964 ''' 

965 return not self.equals(other) 

966 

967 def __gt__(self, other): 

968 '''The rich comparison operator ``>`` 

969 

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

971 

972 ''' 

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

974 

975 def __ge__(self, other): 

976 '''The rich comparison operator ```` 

977 

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

979 

980 ''' 

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

982 

983 def __lt__(self, other): 

984 '''The rich comparison operator ```` 

985 

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

987 

988 ''' 

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

990 

991 def __le__(self, other): 

992 '''The rich comparison operator ```` 

993 

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

995 

996 ''' 

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

998 

999 def __sub__(self, other): 

1000 '''The binary arithmetic operation ``-`` 

1001 

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

1003 

1004 ''' 

1005 if (self._isreftime 

1006 or (isinstance(other, self.__class__) and other._isreftime)): 

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

1008 

1009 try: 

1010 _ut_unit = _ut_offset(self._ut_unit, _c_double(other)) 

1011 return type(self)(_ut_unit=_ut_unit) 

1012 except: 

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

1014 

1015 def __add__(self, other): 

1016 '''The binary arithmetic operation ``+`` 

1017 

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

1019 

1020 ''' 

1021 if (self._isreftime or 

1022 (isinstance(other, self.__class__) and other._isreftime)): 

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

1024 

1025 try: 

1026 _ut_unit = _ut_offset(self._ut_unit, _c_double(-other)) 

1027 return type(self)(_ut_unit=_ut_unit) 

1028 except: 

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

1030 

1031 def __mul__(self, other): 

1032 '''The binary arithmetic operation ``*`` 

1033 

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

1035 

1036 ''' 

1037 if isinstance(other, self.__class__): 

1038 if self._isreftime or other._isreftime: 

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

1040 

1041 try: 

1042 ut_unit=_ut_multiply(self._ut_unit, other._ut_unit) 

1043 except: 

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

1045 else: 

1046 if self._isreftime: 

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

1048 

1049 try: 

1050 ut_unit=_ut_scale(_c_double(other), self._ut_unit) 

1051 except: 

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

1053 # --- End: if 

1054 

1055 return type(self)(_ut_unit=ut_unit) 

1056 

1057 def __div__(self, other): 

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

1059 

1060 ''' 

1061 if isinstance(other, self.__class__): 

1062 if self._isreftime or other._isreftime: 

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

1064 

1065 try: 

1066 ut_unit=_ut_divide(self._ut_unit, other._ut_unit) 

1067 except: 

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

1069 else: 

1070 if self._isreftime: 

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

1072 

1073 try: 

1074 ut_unit=_ut_scale(_c_double(1.0/other), self._ut_unit) 

1075 except: 

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

1077 # --- End: if 

1078 

1079 return type(self)(_ut_unit=ut_unit) 

1080 

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

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

1083 

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

1085 

1086 ''' 

1087 # ------------------------------------------------------------ 

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

1089 # integer. 

1090 # ------------------------------------------------------------ 

1091 

1092 if modulo is not None: 

1093 raise NotImplementedError( 

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

1095 self.__class__.__name__)) 

1096 

1097 if self and not self._isreftime: 

1098 ut_unit = self._ut_unit 

1099 try: 

1100 return type(self)(_ut_unit=_ut_raise(ut_unit, _c_int(other))) 

1101 except: 

1102 pass 

1103 

1104 if 0 < other <= 1: 

1105 # If other is a float and (1/other) is a positive 

1106 # integer then take the (1/other)-th root. E.g. if 

1107 # other is 0.125 then we take the 8-th root. 

1108 try: 

1109 recip_other = 1/other 

1110 root = int(recip_other) 

1111 if recip_other == root: 

1112 ut_unit = _ut_root(ut_unit, _c_int(root)) 

1113 if ut_unit is not None: 

1114 return type(self)(_ut_unit=ut_unit) 

1115 except: 

1116 pass 

1117 else: 

1118 # If other is a float equal to its integer then raise 

1119 # to the integer part. E.g. if other is 3.0 then we 

1120 # raise to the power of 3; if other is -2.0 then we 

1121 # raise to the power of -2 

1122 try: 

1123 root = int(other) 

1124 if other == root: 

1125 ut_unit = _ut_raise(ut_unit, _c_int(root)) 

1126 if ut_unit is not None: 

1127 return type(self)(_ut_unit=ut_unit) 

1128 except: 

1129 pass 

1130 # --- End: if 

1131 

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

1133 

1134 def __isub__(self, other): 

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

1136 

1137 ''' 

1138 return self - other 

1139 

1140 def __iadd__(self, other): 

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

1142 

1143 ''' 

1144 return self + other 

1145 

1146 def __imul__(self, other): 

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

1148 

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

1150 

1151 ''' 

1152 return self * other 

1153 

1154 def __idiv__(self, other): 

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

1156 

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

1158 

1159 ''' 

1160 return self / other 

1161 

1162 def __ipow__(self, other): 

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

1164 

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

1166 

1167 ''' 

1168 return self ** other 

1169 

1170 def __rsub__(self, other): 

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

1172 

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

1174 

1175 ''' 

1176 try: 

1177 return -self + other 

1178 except: 

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

1180 

1181 def __radd__(self, other): 

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

1183 

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

1185 

1186 ''' 

1187 return self + other 

1188 

1189 def __rmul__(self, other): 

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

1191 

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

1193 

1194 ''' 

1195 return self * other 

1196 

1197 def __rdiv__(self, other): 

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

1199 

1200 ''' 

1201 try: 

1202 return (self ** -1) * other 

1203 except: 

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

1205 

1206 def __floordiv__(self, other): 

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

1208 

1209 ''' 

1210 return self / other 

1211 

1212 def __ifloordiv__(self, other): 

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

1214 

1215 ''' 

1216 return self / other 

1217 

1218 def __rfloordiv__(self, other): 

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

1220 

1221 ''' 

1222 try: 

1223 return (self ** -1) * other 

1224 except: 

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

1226 

1227 def __truediv__(self, other): 

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

1229 

1230 ''' 

1231 return self.__div__(other) 

1232 

1233 def __itruediv__(self, other): 

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

1235 

1236 ''' 

1237 return self.__idiv__(other) 

1238 

1239 def __rtruediv__(self, other): 

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

1241 

1242 ''' 

1243 return self.__rdiv__(other) 

1244 

1245 def __mod__(self, other): 

1246 '''TODO 

1247 

1248 ''' 

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

1250 

1251 def __neg__(self): 

1252 '''The unary arithmetic operation ``-`` 

1253 

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

1255 

1256 ''' 

1257 return self * -1 

1258 

1259 def __pos__(self): 

1260 '''The unary arithmetic operation ``+`` 

1261 

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

1263 

1264 ''' 

1265 return self 

1266 

1267 # ---------------------------------------------------------------- 

1268 # Private methods 

1269 # ---------------------------------------------------------------- 

1270 def _comparison(self, other, method): 

1271 ''' 

1272 ''' 

1273 try: 

1274 cv_converter = _ut_get_converter(self._ut_unit, other._ut_unit) 

1275 except: 

1276 raise ValueError( 

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

1278 

1279 if not cv_converter: 

1280 _cv_free(cv_converter) 

1281 raise ValueError( 

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

1283 

1284 y = _c_double(1.0) 

1285 pointer = ctypes.pointer(y) 

1286 _cv_convert_doubles(cv_converter, 

1287 pointer, 

1288 _c_size_t(1), 

1289 pointer) 

1290 _cv_free(cv_converter) 

1291 

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

1293 

1294 def _new_reason_notvalid(self, reason): 

1295 '''TODO 

1296 

1297 ''' 

1298 _reason_notvalid = self._reason_notvalid 

1299 if _reason_notvalid: 

1300 self._reason_notvalid = _reason_notvalid+'; '+reason 

1301 else: 

1302 self._reason_notvalid = reason 

1303 

1304 # ---------------------------------------------------------------- 

1305 # Attributes 

1306 # ---------------------------------------------------------------- 

1307 @property 

1308 def isreftime(self): 

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

1310 

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

1312 units. 

1313  

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

1315 `ispressure`, `istime` 

1316  

1317 **Examples:** 

1318  

1319 >>> Units('days since 2000-12-1 03:00').isreftime 

1320 True 

1321 >>> Units('hours since 2100-1-1', calendar='noleap').isreftime 

1322 True 

1323 >>> Units(calendar='360_day').isreftime 

1324 True 

1325 >>> Units('days').isreftime 

1326 False 

1327 >>> Units('kg').isreftime 

1328 False 

1329 >>> Units().isreftime 

1330 False 

1331 

1332 ''' 

1333 return self._isreftime 

1334 

1335 @property 

1336 def iscalendartime(self): 

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

1338 

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

1340 time units. 

1341  

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

1343 `ispressure`, `isreftime`, `istime` 

1344  

1345 **Examples:** 

1346  

1347 >>> Units('calendar_months').iscalendartime 

1348 True 

1349 >>> Units('calendar_years').iscalendartime 

1350 True 

1351 >>> Units('days').iscalendartime 

1352 False 

1353 >>> Units('km s-1').iscalendartime 

1354 False 

1355 >>> Units('kg').isreftime 

1356 False 

1357 >>> Units('').isreftime 

1358 False 

1359 >>> Units().isreftime 

1360 False 

1361 

1362 ''' 

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

1364 

1365 @property 

1366 def isdimensionless(self): 

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

1368 

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

1370 `istime` 

1371  

1372 **Examples:** 

1373  

1374 >>> Units('').isdimensionless 

1375 True 

1376 >>> Units('1').isdimensionless 

1377 True 

1378 >>> Units('100').isdimensionless 

1379 True 

1380 >>> Units('m/m').isdimensionless 

1381 True 

1382 >>> Units('m km-1').isdimensionless 

1383 True 

1384 >>> Units().isdimensionless 

1385 False 

1386 >>> Units('m').isdimensionless 

1387 False 

1388 >>> Units('m/s').isdimensionless 

1389 False 

1390 >>> Units('days since 2000-1-1', calendar='noleap').isdimensionless 

1391 False 

1392 

1393 ''' 

1394 return bool(_ut_are_convertible(self._ut_unit, _dimensionless_unit_one)) 

1395 

1396 @property 

1397 def ispressure(self): 

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

1399  

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

1401 `isreftime`, `istime` 

1402  

1403 **Examples:** 

1404  

1405 >>> Units('bar').ispressure 

1406 True 

1407 >>> Units('hPa').ispressure 

1408 True 

1409 >>> Units('meter^-1-kilogram-second^-2').ispressure 

1410 True 

1411 >>> Units('hours since 2100-1-1', calendar='noleap').ispressure 

1412 False 

1413 

1414 ''' 

1415 ut_unit = self._ut_unit 

1416 if ut_unit is None: 

1417 return False 

1418 

1419 return bool(_ut_are_convertible(ut_unit, _pressure_ut_unit)) 

1420 

1421 @property 

1422 def islatitude(self): 

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

1424 

1425 This is the case if and only if the `units` attribute is one of 

1426 ``'degrees_north'``, ``'degree_north'``, ``'degree_N'``, 

1427 ``'degrees_N'``, ``'degreeN'``, and ``'degreesN'``. 

1428  

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

1430  

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

1432 `isreftime`, `istime` 

1433  

1434 **Examples:** 

1435  

1436 >>> Units('degrees_north').islatitude 

1437 True 

1438 >>> Units('degrees').islatitude 

1439 False 

1440 >>> Units('degrees_east').islatitude 

1441 False 

1442 >>> Units('kg').islatitude 

1443 False 

1444 >>> Units().islatitude 

1445 False 

1446 

1447 ''' 

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

1449 'degrees_N', 'degreeN', 'degreesN') 

1450 

1451 @property 

1452 def islongitude(self): 

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

1454 

1455 This is the case if and only if the `units` attribute is one of 

1456 ``'degrees_east'``, ``'degree_east'``, ``'degree_E'``, 

1457 ``'degrees_E'``, ``'degreeE'``, and ``'degreesE'``. 

1458  

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

1460  

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

1462 `isreftime`, `istime` 

1463  

1464 **Examples:** 

1465  

1466 >>> Units('degrees_east').islongitude 

1467 True 

1468 >>> Units('degrees').islongitude 

1469 False 

1470 >>> Units('degrees_north').islongitude 

1471 False 

1472 >>> Units('kg').islongitude 

1473 False 

1474 >>> Units().islongitude 

1475 False 

1476 

1477 ''' 

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

1479 'degrees_E', 'degreeE', 'degreesE') 

1480 

1481 @property 

1482 def istime(self): 

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

1484 

1485 Note that reference time units (such as ``'days since 

1486 2000-12-1'``) are not time units, nor are calendar years and 

1487 calendar months. 

1488  

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

1490 `islatitude`, `ispressure`, `isreftime` 

1491  

1492 **Examples:** 

1493  

1494 >>> Units('days').istime 

1495 True 

1496 >>> Units('seconds').istime 

1497 True 

1498 >>> Units('kg').istime 

1499 False 

1500 >>> Units().istime 

1501 False 

1502 >>> Units('hours since 2100-1-1', calendar='noleap').istime 

1503 False 

1504 >>> Units(calendar='360_day').istime 

1505 False 

1506 >>> Units('calendar_years').istime 

1507 False 

1508 >>> Units('calendar_months').istime 

1509 False 

1510 

1511 ''' 

1512 if self._isreftime: 

1513 return False 

1514 

1515 ut_unit = self._ut_unit 

1516 if ut_unit is None: 

1517 return False 

1518 

1519 return bool(_ut_are_convertible(ut_unit, _day_ut_unit)) 

1520 

1521 @property 

1522 def isvalid(self): 

1523 '''Whether the units are valid. 

1524 

1525 .. seealso:: `reason_notvalid` 

1526 

1527 **Examples:** 

1528 

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

1530 >>> u.isvalid 

1531 True 

1532 >>> u.reason_notvalid 

1533 '' 

1534 

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

1536 >>> u.isvalid 

1537 False 

1538 >>> u.reason_notvalid 

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

1540 >>> u = Units(days since 2000-1-1', calendar='Bad Calendar') 

1541 >>> u.isvalid 

1542 False 

1543 >>> u.reason_notvalid 

1544 "Invalid calendar='Bad Calendar'; calendar must be one of ['standard', 'gregorian', 'proleptic_gregorian', 'noleap', 'julian', 'all_leap', '365_day', '366_day', '360_day'], got 'bad calendar'" 

1545  

1546 ''' 

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

1548 

1549 @property 

1550 def reason_notvalid(self): 

1551 '''The reason for invalid units. 

1552 

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

1554 

1555 .. seealso:: `isvalid` 

1556  

1557 **Examples:** 

1558 

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

1560 >>> u.isvalid 

1561 True 

1562 >>> u.reason_notvalid 

1563 '' 

1564 

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

1566 >>> u.isvalid 

1567 False 

1568 >>> u.reason_notvalid 

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

1570 

1571 >>> u = Units(days since 2000-1-1', calendar='Bad Calendar') 

1572 >>> u.isvalid 

1573 False 

1574 >>> u.reason_notvalid 

1575 "Invalid calendar='Bad Calendar'; calendar must be one of ['standard', 'gregorian', 'proleptic_gregorian', 'noleap', 'julian', 'all_leap', '365_day', '366_day', '360_day'], got 'bad calendar'" 

1576 

1577 ''' 

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

1579 

1580 @property 

1581 def reftime(self): 

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

1583 

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

1585  

1586 **Examples:** 

1587  

1588 >>> Units('days since 1900-1-1').reftime 

1589 <Datetime: 1900-01-01 00:00:00> 

1590 >>> str(Units('days since 1900-1-1 03:00').reftime) 

1591 '1900-01-01 03:00:00' 

1592  

1593 TODO 

1594 ''' 

1595 if self.isreftime: 

1596 utime = self._utime 

1597 if utime: 

1598 origin = utime.origin 

1599 if origin: 

1600 return origin 

1601 else: 

1602 # Some refrence date-times do not have a utime, such 

1603 # as those defined by monts or years 

1604 calendar = self._canonical_calendar 

1605 return cftime.datetime( 

1606 *cftime._parse_date(self.units.split(' since ')[1]), 

1607 calendar=calendar 

1608 ) 

1609 # --- End: if 

1610 

1611 raise AttributeError( 

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

1613 

1614 @property 

1615 def calendar(self): 

1616 '''The calendar for reference time units. 

1617 

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

1619  

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

1621 required. 

1622  

1623 .. seealso:: `units` 

1624  

1625 **Examples:** 

1626  

1627 >>> Units(calendar='365_day').calendar 

1628 '365_day' 

1629 >>> Units('days since 2001-1-1', calendar='noleap').calendar 

1630 'noleap' 

1631 >>> Units('days since 2001-1-1').calendar 

1632 AttributeError: Units has no attribute 'calendar' 

1633 

1634 ''' 

1635 value = self._calendar 

1636 if value is not None: 

1637 return value 

1638 

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

1640 self.__class__.__name__) 

1641 

1642 @property 

1643 def units(self): 

1644 '''The units. 

1645 

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

1647  

1648 .. seealso:: `calendar` 

1649  

1650 **Examples:** 

1651  

1652 >>> Units('kg').units 

1653 'kg' 

1654 >>> Units('seconds').units 

1655 'seconds' 

1656 >>> Units('days since 2000-1-1', calendar='366_day').units 

1657 'days since 2000-1-1' 

1658 

1659 ''' 

1660 value = self._units 

1661 if value is not None: 

1662 return value 

1663 

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

1665 self.__class__.__name__) 

1666 

1667 # ---------------------------------------------------------------- 

1668 # Methods 

1669 # ---------------------------------------------------------------- 

1670 def equivalent(self, other, verbose=False): 

1671 '''Returns True if numeric values in one unit are convertible to 

1672 numeric values in the other unit. 

1673  

1674 .. seealso:: `equals` 

1675  

1676 :Parameters: 

1677  

1678 other: `Units` 

1679 The other units. 

1680  

1681 :Returns: 

1682  

1683 `bool` 

1684 True if the units are equivalent, False otherwise. 

1685  

1686 **Examples:** 

1687  

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

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

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

1691 

1692 >>> u.equivalent(v) 

1693 True 

1694 >>> u.equivalent(w) 

1695 False 

1696  

1697 >>> u = Units('days since 2000-1-1') 

1698 >>> v = Units('days since 2000-1-1', calendar='366_day') 

1699 >>> w = Units('seconds since 1978-3-12', calendar='gregorian) 

1700  

1701 >>> u.equivalent(v) 

1702 False 

1703 >>> u.equivalent(w) 

1704 True 

1705 

1706 ''' 

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

1708# return False 

1709 

1710 isreftime1 = self._isreftime 

1711 isreftime2 = other._isreftime 

1712 

1713# if isreftime1 and isreftime2: 

1714# # Both units are reference-time units 

1715# if self._canonical_calendar != other._canonical_calendar: 

1716# if verbose: 

1717# print("{}: Incompatible calendars: {!r}, {!r}".format( 

1718# self.__class__.__name__, 

1719# self._calendar, other._calendar)) # pragma: no cover 

1720# return False 

1721# 

1722# reftime0 = getattr(self, 'reftime', None) 

1723# reftime1 = getattr(other, 'reftime', None) 

1724# if reftime0 != reftime1: 

1725# if verbose: 

1726# print("{}: Different reference date-times: {!r}, {!r}".format( 

1727# self.__class__.__name__, 

1728# reftime0, reftime1)) # pragma: no cover 

1729# return False 

1730# 

1731# elif isreftime1 or isreftime2: 

1732# if verbose: 

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

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

1735# return False 

1736 

1737 

1738 if isreftime1 and isreftime2: 

1739 # Both units are reference-time units 

1740 units0 = self._units 

1741 units1 = other._units 

1742 if units0 and units1 or (not units0 and not units1): 

1743 out = (self._canonical_calendar == other._canonical_calendar) 

1744 if verbose and not out: 

1745 print("{}: Incompatible calendars: {!r}, {!r}".format( 

1746 self.__class__.__name__, 

1747 self._calendar, other._calendar)) # pragma: no cover 

1748 

1749 return out 

1750 else: 

1751 return False 

1752 

1753 elif isreftime1 or isreftime2: 

1754 if verbose: 

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

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

1757 return False 

1758 

1759# if not self.isvalid: 

1760# if verbose: 

1761# print("{}: {!r} is not valid".format(self.__class__.__name__, self)) # pragma: no cover 

1762# return False 

1763#  

1764# if not other.isvalid: 

1765# if verbose: 

1766# print("{}: {!r} is not valid".format(self.__class__.__name__, other)) # pragma: no cover 

1767# return False 

1768 

1769# if isreftime1 and isreftime2: 

1770# # Both units are reference-time units 

1771# units0 = self._units 

1772# units1 = other._units 

1773# if units0 and units1 or (not units0 and not units1): 

1774# return self._calendar == other._calendar 

1775# else: 

1776# return False 

1777# # --- End: if 

1778 

1779 # Still here?  

1780 if not self and not other: 

1781 # Both units are null and therefore equivalent 

1782 return True 

1783 

1784# if not isreftime1 and not isreftime2: 

1785 # Both units are not reference-time units 

1786 return bool(_ut_are_convertible(self._ut_unit, other._ut_unit)) 

1787 

1788 # Still here? Then units are not equivalent. 

1789# return False 

1790 

1791 def formatted(self, names=None, definition=None): 

1792 '''Formats the string stored in the `units` attribute in a 

1793 standardized manner. The `units` attribute is modified in place 

1794 and its new value is returned. 

1795  

1796 :Parameters: 

1797  

1798 names: `bool`, optional 

1799 Use unit names instead of symbols. 

1800  

1801 definition: `bool`, optional 

1802 The formatted string is given in terms of basic units 

1803 instead of stopping any expansion at the highest level 

1804 possible. 

1805  

1806 :Returns: 

1807  

1808 `str` or `None` 

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

1810 then `None` is returned. 

1811  

1812 **Examples:** 

1813  

1814 >>> u = Units('W') 

1815 >>> u.units 

1816 'W' 

1817 >>> u.units = u.format(names=True) 

1818 >>> u.units 

1819 'watt' 

1820 >>> u.units = u.format(definition=True) 

1821 >>> u.units 

1822 'm2.kg.s-3' 

1823 >>> u.units = u.format(names=True, definition=True) 

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

1825 >>> u.units = u.format() 

1826 >>> u.units 

1827 'W' 

1828  

1829 >>> u.units = 'dram' 

1830 >>> u.format(names=True) 

1831 '1.848345703125e-06 meter^3' 

1832  

1833 Formatting is also available during object initialization: 

1834  

1835 >>> u = Units('m/s', format=True) 

1836 >>> u.units 

1837 'm.s-1' 

1838  

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

1840 >>> u.units 

1841 '1.848345703125e-06 m3' 

1842  

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

1844 >>> u.units 

1845 'Watt' 

1846  

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

1848 >>> u.units 

1849 'W' 

1850  

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

1852 >>> u.units 

1853 'watt' 

1854  

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

1856 >>> u.units 

1857 'm2.kg.s-3' 

1858  

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

1860 >>> u.units 

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

1862 

1863 ''' 

1864 ut_unit = self._ut_unit 

1865 

1866 if ut_unit is None: 

1867 return None 

1868 

1869 opts = _UT_ASCII 

1870 if names: 

1871 opts |= _UT_NAMES 

1872 if definition: 

1873 opts |= _UT_DEFINITION 

1874 

1875 if _ut_format(ut_unit, _string_buffer, _sizeof_buffer, opts) != -1: 

1876 out = _string_buffer.value 

1877 else: 

1878 raise ValueError("Can't format unit {!r}".format(self)) 

1879 

1880 if self.isreftime: 

1881 out = str(out) 

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

1883 return out 

1884 

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

1886 

1887 @classmethod 

1888 def conform(cls, x, from_units, to_units, inplace=False): 

1889 '''Conform values in one unit to equivalent values in another, 

1890 compatible unit. 

1891 

1892 Returns the conformed values. 

1893  

1894 The values may either be a `numpy` array, a python numeric type, 

1895 or a `list` or `tuple`. The returned value is of the same type, 

1896 except that input integers are converted to floats and python 

1897 sequences are converted to `numpy` arrays (see the *inplace* 

1898 keyword). 

1899  

1900 .. warning:: Do not change the calendar of reference time units in 

1901 the current version. Whilst this is possible, it will 

1902 almost certainly result in an incorrect 

1903 interpretation of the data or an error. 

1904 

1905 :Parameters: 

1906  

1907 x: `numpy.ndarray` or python numeric object 

1908  

1909 from_units: `Units` 

1910 The original units of *x* 

1911  

1912 to_units: `Units` 

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

1914  

1915 inplace: `bool`, optional 

1916 If True and *x* is a `numpy` array then change it in place, 

1917 creating no temporary copies, with one exception: If *x* 

1918 is of integer type and the conversion is not null, then it 

1919 will not be changed inplace and the returned conformed 

1920 array will be of float type. 

1921 

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

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

1924  

1925 :Returns: 

1926  

1927 `numpy.ndarray` or python numeric 

1928 The modified numeric values. 

1929  

1930 **Examples:** 

1931  

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

1933 2000.0 

1934  

1935 >>> import numpy 

1936 >>> a = numpy.arange(5.0) 

1937 >>> Units.conform(a, Units('minute'), Units('second')) 

1938 array([ 0., 60., 120., 180., 240.]) 

1939 >>> print(a) 

1940 [ 0. 1. 2. 3. 4.] 

1941  

1942 >>> Units.conform(a, 

1943 Units('days since 2000-12-1'), 

1944 Units('days since 2001-1-1'), inplace=True) 

1945 array([-31., -30., -29., -28., -27.]) 

1946 >>> print(a) 

1947 [-31. -30. -29. -28. -27.] 

1948 

1949 ''' 

1950 if from_units.equals(to_units): 

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

1952 x = numpy_asanyarray(x) 

1953 

1954 if inplace: 

1955 return x 

1956 else: 

1957 try: 

1958 return x.copy() 

1959 except AttributeError: 

1960 x 

1961 # --- End: if 

1962 

1963 if not from_units.equivalent(to_units): 

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

1965 from_units, to_units)) 

1966 

1967 ut_unit1 = from_units._ut_unit 

1968 ut_unit2 = to_units._ut_unit 

1969 

1970 if ut_unit1 is None or ut_unit2 is None: 

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

1972 from_units, to_units)) 

1973 

1974 convert = _ut_compare(ut_unit1, ut_unit2) 

1975 

1976 if from_units._isreftime and to_units._isreftime: 

1977 # -------------------------------------------------------- 

1978 # Both units are time-reference units, so calculate the 

1979 # non-zero offset in units of days. 

1980 # -------------------------------------------------------- 

1981 units0, reftime0 = from_units.units.split(' since ') 

1982 units1, reftime1 = to_units.units.split(' since ') 

1983 if units0 in _months_or_years: 

1984 from_units = cls( 

1985 'days since '+reftime0, 

1986 calendar=getattr(from_units, 'calendar', None)) 

1987 x = numpy_asanyarray(x) 

1988 if inplace: 

1989 if units0 in ('month', 'months'): 

1990 x *= _month_length 

1991 else: 

1992 x *= _year_length 

1993 else: 

1994 if units0 in ('month', 'months'): 

1995 x = x * _month_length 

1996 else: 

1997 x = x * _year_length 

1998 

1999 inplace = True 

2000 

2001 ut_unit1 = from_units._ut_unit 

2002 ut_unit2 = to_units._ut_unit 

2003 

2004 convert = _ut_compare(ut_unit1, ut_unit2) 

2005 

2006 if units1 in _months_or_years: 

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

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

2009 

2010 offset = to_units._utime._jd0 - from_units._utime._jd0 

2011 else: 

2012 offset = 0 

2013 

2014 # ------------------------------------------------------------ 

2015 # If the two units are identical then no need to alter the 

2016 # value, so return it unchanged. 

2017 # ------------------------------------------------------------ 

2018# if not convert and not offset: 

2019# return x 

2020 

2021 if convert: 

2022 cv_converter = _ut_get_converter(ut_unit1, ut_unit2) 

2023 if not cv_converter: 

2024 _cv_free(cv_converter) 

2025 raise ValueError( 

2026 "Units are not convertible: {!r}, {!r}".format( 

2027 from_units, to_units)) 

2028 # --- End: if 

2029 

2030 # ------------------------------------------------------------ 

2031 # Find out if x is (or should be) a numpy array or a python 

2032 # number 

2033 # ------------------------------------------------------------ 

2034 if isinstance(x, numpy_generic): 

2035 # Convert a generic numpy scalar to a 0-d numpy array 

2036 x = numpy_array(x) 

2037 x_is_numpy = True 

2038 elif not isinstance(x, numpy_ndarray): 

2039 if numpy_size(x) > 1 or len(numpy_shape(x)): 

2040 # Convert a non-numpy (possibly nested) sequence to a 

2041 # numpy array. E.g. [1], ((1.5, 2.5)) 

2042 x = numpy_asanyarray(x) 

2043 x_is_numpy = True 

2044 inplace = True 

2045 else: 

2046 x_is_numpy = False 

2047 else: 

2048 x_is_numpy = True 

2049 

2050 if x_is_numpy: 

2051 if not x.flags.contiguous: 

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

2053#ARRRGGHH dch 

2054 

2055 # -------------------------------------------------------- 

2056 # Convert an integer numpy array to a float numpy array 

2057 # -------------------------------------------------------- 

2058 if inplace: 

2059 if x.dtype.kind is 'i': 

2060 if x.dtype.char is 'i': 

2061 y = x.view(dtype='float32') 

2062 y[...] = x 

2063 x.dtype = numpy_dtype('float32') 

2064 elif x.dtype.char is 'l': 

2065 y = x.view(dtype=float) 

2066 y[...] = x 

2067 x.dtype = numpy_dtype(float) 

2068 else: 

2069 # At numpy vn1.7 astype has many more keywords ... 

2070 if x.dtype.kind is 'i': 

2071 if x.dtype.char is 'i': 

2072 x = x.astype('float32') 

2073 elif x.dtype.char is 'l': 

2074 x = x.astype(float) 

2075 else: 

2076 x = x.copy() 

2077 # --- End: if 

2078 

2079 # ------------------------------------------------------------ 

2080 # Convert the array to the new units 

2081 # ------------------------------------------------------------ 

2082 if convert: 

2083 

2084 if x_is_numpy: 

2085 # Create a pointer to the array cast to the 

2086 # appropriate ctypes object 

2087 itemsize = x.dtype.itemsize 

2088 pointer = x.ctypes.data_as(_ctypes_POINTER[itemsize]) 

2089 

2090 # Convert the array in place 

2091 _cv_convert_array[itemsize](cv_converter, 

2092 pointer, 

2093 _c_size_t(x.size), 

2094 pointer) 

2095 else: 

2096 # Create a pointer to the number cast to a ctypes 

2097 # double object. 

2098 y = _c_double(x) 

2099 pointer = ctypes.pointer(y) 

2100 # Convert the pointer 

2101 _cv_convert_doubles(cv_converter, 

2102 pointer, 

2103 _c_size_t(1), 

2104 pointer) 

2105 # Reset the number 

2106 x = y.value 

2107 

2108 _cv_free(cv_converter) 

2109 

2110 # ------------------------------------------------------------ 

2111 # Apply an offset for reference-time units 

2112 # ------------------------------------------------------------ 

2113 if offset: 

2114 # Convert the offset from 'days' to the correct units and 

2115 # subtract it from x 

2116 if _ut_compare(_day_ut_unit, ut_unit2): 

2117 

2118 cv_converter = _ut_get_converter(_day_ut_unit, ut_unit2) 

2119 scale = numpy_array(1.0) 

2120 pointer = scale.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) 

2121 _cv_convert_doubles(cv_converter, 

2122 pointer, 

2123 _c_size_t(scale.size), 

2124 pointer) 

2125 _cv_free(cv_converter) 

2126 

2127 offset *= scale.item() 

2128 

2129 x -= offset 

2130 

2131 return x 

2132 

2133 def copy(self): 

2134 '''Return a deep copy. 

2135 

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

2137  

2138 :Returns: 

2139  

2140 The deep copy. 

2141  

2142 **Examples:** 

2143  

2144 >>> v = u.copy() 

2145  

2146 ''' 

2147 return self 

2148 

2149 def equals(self, other, rtol=None, atol=None, verbose=False): 

2150 '''Return True if and only if numeric values in one unit are 

2151 convertible to numeric values in the other unit and their 

2152 conversion is a scale factor of 1. 

2153  

2154 .. seealso:: `equivalent` 

2155  

2156 :Parameters: 

2157  

2158 other: `Units` 

2159 The other units. 

2160  

2161 :Returns: 

2162  

2163 `bool` 

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

2165  

2166 **Examples:** 

2167  

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

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

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

2171 >>> u.equals(v) 

2172 True 

2173 >>> u.equals(w) 

2174 False 

2175  

2176 >>> u = Units('m s-1') 

2177 >>> m = Units('m') 

2178 >>> s = Units('s') 

2179 >>> u.equals(m) 

2180 False 

2181 >>> u.equals(m/s) 

2182 True 

2183 >>> (m/s).equals(u) 

2184 True 

2185  

2186 Undefined units are considered equal: 

2187  

2188 >>> u = Units() 

2189 >>> v = Units() 

2190 >>> u.equals(v) 

2191 True 

2192 

2193 ''' 

2194 isreftime1 = self._isreftime 

2195 isreftime2 = other._isreftime 

2196 

2197 if isreftime1 and isreftime2: 

2198 # Both units are reference-time units 

2199 if self._canonical_calendar != other._canonical_calendar: 

2200 if verbose: 

2201 print("{}: Incompatible calendars: {!r}, {!r}".format( 

2202 self.__class__.__name__, 

2203 self._calendar, other._calendar)) # pragma: no cover 

2204 

2205 return False 

2206 

2207 reftime0 = getattr(self, 'reftime', None) 

2208 reftime1 = getattr(other, 'reftime', None) 

2209 if reftime0 != reftime1: 

2210 if verbose: 

2211 print("{}: Different reference date-times: " 

2212 "{!r}, {!r}".format( 

2213 self.__class__.__name__, 

2214 reftime0, reftime1)) # pragma: no cover 

2215 

2216 return False 

2217 

2218 elif isreftime1 or isreftime2: 

2219 if verbose: 

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

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

2222 

2223 return False 

2224 

2225 

2226# utime0 = self._utime 

2227# utime1 = other._utime 

2228# if utime0 is not None and utime1 is not None: 

2229# return utime0.origin_equals(utime1) 

2230# elif utime0 is None and utime1 is None: 

2231# return self.reftime 

2232# 

2233#  

2234#  

2235# units0 = self._units 

2236# units1 = other._units 

2237# if units0 and units1 or (not units0 and not units1): 

2238# out = (self._canonical_calendar == other._canonical_calendar) 

2239# if verbose and not out: 

2240# print("{}: Incompatible calendars: {!r}, {!r}".format( 

2241# self.__class__.__name__, 

2242# self._calendar, other._calendar)) # pragma: no cover 

2243#  

2244# return out 

2245# else: 

2246# return False 

2247# # --- End: if 

2248# if not self.isvalid: 

2249# if verbose: 

2250# print("{}: {!r} is not valid".format(self.__class__.__name__, self)) # pragma: no cover 

2251# return False 

2252#  

2253# if not other.isvalid: 

2254# if verbose: 

2255# print("{}: {!r} is not valid".format(self.__class__.__name__, other)) # pragma: no cover 

2256# return False 

2257#  

2258 

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

2260# print ('ppp') 

2261# return False 

2262 

2263 try: 

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

2265 return True 

2266 

2267 if verbose: 

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

2269 self.__class__.__name__, 

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

2271 

2272 return False 

2273 except AttributeError: 

2274 return False 

2275 

2276#isreftime1 = self._isreftime 

2277# isreftime2 = other._isreftime 

2278# 

2279# if not isreftime1 and not isreftime2: 

2280# # Neither units is reference-time so they're equal 

2281# return True 

2282# 

2283# if isreftime1 and isreftime2: 

2284# # Both units are reference-time 

2285# utime0 = self._utime 

2286# utime1 = other._utime 

2287# if utime0.calendar != utime1.calendar: 

2288# return False 

2289#  

2290# return utime0.origin_equals(utime1) 

2291 

2292 # One unit is a reference-time and the other is not so they're 

2293 # not equal 

2294# return False 

2295 

2296 def log(self, base): 

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

2298 base. 

2299 

2300 :Parameters: 

2301  

2302 base: `int` or `float` 

2303 The logarithmic base. 

2304  

2305 :Returns: 

2306  

2307 `Units` 

2308 The logarithmic unit corresponding to the given 

2309 logarithmic base. 

2310  

2311 **Examples:** 

2312  

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

2314 >>> u 

2315 <Units: watt> 

2316  

2317 >>> u.log(10) 

2318 <Units: lg(re 1 W)> 

2319 >>> u.log(2) 

2320 <Units: lb(re 1 W)> 

2321  

2322 >>> import math 

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

2324 <Units: ln(re 1 W)> 

2325  

2326 >>> u.log(3.5) 

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

2328 

2329 ''' 

2330 try: 

2331 _ut_unit = _ut_log(_c_double(base), self._ut_unit) 

2332 except TypeError: 

2333 pass 

2334 else: 

2335 if _ut_unit: 

2336 return type(self)(_ut_unit=_ut_unit) 

2337 # --- End: try 

2338 

2339 raise ValueError( 

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

2341 base, self)) 

2342 

2343# --- End: class 

2344 

2345 

2346class Utime(cftime.utime): 

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

2348 datetime objects. 

2349  

2350 This object is (currently) functionally equivalent to a 

2351 `netCDF4.netcdftime.utime` object. 

2352  

2353 **Attributes** 

2354  

2355 ============== ================================================== 

2356 Attribute Description 

2357 ============== ================================================== 

2358 `!_jd0`  

2359 `!calendar` The calendar used in the time calculation. 

2360 `!origin` A date/time object for the reference time. 

2361 `!tzoffset` Time zone offset in minutes. 

2362 `!unit_string`  

2363 `!units`  

2364 ============== ================================================== 

2365 

2366 ''' 

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

2368 only_use_cftime_datetimes=True): 

2369 '''**Initialization** 

2370 

2371 :Parameters: 

2372  

2373 calendar: `str` 

2374 The calendar used in the time calculations. Must be one 

2375 of: ``'gregorian'``, ``'360_day'``, ``'365_day'``, 

2376 ``'366_day'``, ``'julian'``, ``'proleptic_gregorian'``, 

2377 although this is not checked. 

2378  

2379 unit_string: `str`, optional 

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

2381 defining the reference-time units. 

2382 

2383 only_use_cftime_datetimes: `bool`, optional 

2384 If False, datetime.datetime objects are returned from 

2385 `num2date` where possible; By default. dates which 

2386 subclass `cftime.datetime` are returned for all calendars. 

2387 

2388 ''' 

2389 if unit_string: 

2390 super().__init__( 

2391 unit_string, calendar, 

2392 only_use_cftime_datetimes=only_use_cftime_datetimes) 

2393 else: 

2394 self.calendar = calendar 

2395 self._jd0 = None 

2396 self.origin = None 

2397 self.tzoffset = None 

2398 self.unit_string = None 

2399 self.units = None 

2400 

2401 def __repr__(self): 

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

2403 

2404 ''' 

2405 unit_string = self.unit_string 

2406 if unit_string: 

2407 x = [unit_string] 

2408 else: 

2409 x = [] 

2410 

2411 x.append(self.calendar) 

2412 

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

2414 

2415 def num2date(self, time_value): 

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

2417 

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

2419 and `!calendar` attributes. 

2420  

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

2422  

2423 In addition to `netCDF4.netcdftime.utime.num2date`, this method 

2424 handles units of months and years as defined by Udunits, ie. 1 

2425 year = 365.242198781 days, 1 month = 365.242198781/12 days. 

2426 

2427 ''' 

2428 units = self.units 

2429 unit_string = self.unit_string 

2430 

2431 if units in ('month', 'months'): 

2432 # Convert months to days 

2433 unit_string = unit_string.replace(units, 'days', 1) 

2434 time_value = numpy_array(time_value)*_month_length 

2435 elif units in ('year', 'years', 'yr'): 

2436 # Convert years to days 

2437 unit_string = unit_string.replace(units, 'days', 1) 

2438 time_value = numpy_array(time_value)*_year_length 

2439 

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

2441 

2442 return u.num2date(time_value) 

2443 

2444# def origin_equals(self, other): 

2445# ''' 

2446#  

2447# ''' 

2448# if self is other: 

2449# return True 

2450# else: 

2451# return (self._jd0 == other._jd0 and 

2452# self.calendar == other.calendar and 

2453# self.tzoffset == other.tzoffset) 

2454 

2455# --- End: class