Package netcdftime :: Module netcdftime

Source Code for Module netcdftime.netcdftime

   1  """ 
   2  Performs conversions of netCDF time coordinate data to/from datetime objects. 
   3  """ 
   4  import math, numpy, re, time 
   5  from datetime import datetime as real_datetime 
   6  from datetime import tzinfo, timedelta 
   7   
   8  _units = ['days','hours','minutes','seconds','day','hour','minute','second'] 
   9  _calendars = ['standard','gregorian','proleptic_gregorian','noleap','julian','all_leap','365_day','366_day','360_day'] 
  10   
  11  __version__ = '0.9.2' 
  12   
  13  # Adapted from http://delete.me.uk/2005/03/iso8601.html 
  14  ISO8601_REGEX = re.compile(r"(?P<year>[0-9]{4})(-(?P<month>[0-9]{1,2})(-(?P<day>[0-9]{1,2})" 
  15      r"((?P<separator>.)(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2})(:(?P<second>[0-9]{2})(\.(?P<fraction>[0-9]+))?)?" 
  16      r"(?P<timezone>Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?" 
  17  ) 
  18  TIMEZONE_REGEX = re.compile("(?P<prefix>[+-])(?P<hours>[0-9]{2}).(?P<minutes>[0-9]{2})") 
  19   
20 -class datetime:
21 """ 22 Phony datetime object which mimics the python datetime object, 23 but allows for dates that don't exist in the proleptic gregorian calendar. 24 Doesn't do timedelta operations, doesn't overload + and -. 25 26 Has strftime, timetuple and __repr__ methods. The format 27 of the string produced by __repr__ is controlled by self.format 28 (default %Y-%m-%d %H:%M:%S). 29 30 Instance variables are year,month,day,hour,minute,second,dayofwk,dayofyr 31 and format. 32 """
33 - def __init__(self,year,month,day,hour=0,minute=0,second=0,dayofwk=-1,dayofyr=1):
34 """dayofyr set to 1 by default - otherwise time.strftime will complain""" 35 self.year=year 36 self.month=month 37 self.day=day 38 self.hour=hour 39 self.minute=minute 40 self.dayofwk=dayofwk 41 self.dayofyr=dayofyr 42 self.second=second 43 self.format='%Y-%m-%d %H:%M:%S'
44 - def strftime(self,format=None):
45 if format is None: 46 format = self.format 47 return _strftime(self,format)
48 - def timetuple(self):
49 return (self.year,self.month,self.day,self.hour,self.minute,self.second,self.dayofwk,self.dayofyr,-1)
50 - def __repr__(self):
51 return self.strftime(self.format)
52
53 -def JulianDayFromDate(date,calendar='standard'):
54 55 """ 56 57 creates a Julian Day from a 'datetime-like' object. Returns the fractional 58 Julian Day (resolution 1 second). 59 60 if calendar='standard' or 'gregorian' (default), Julian day follows Julian 61 Calendar on and before 1582-10-5, Gregorian calendar after 1582-10-15. 62 63 if calendar='proleptic_gregorian', Julian Day follows gregorian calendar. 64 65 if calendar='julian', Julian Day follows julian calendar. 66 67 Algorithm: 68 69 Meeus, Jean (1998) Astronomical Algorithms (2nd Edition). Willmann-Bell, 70 Virginia. p. 63 71 72 """ 73 74 # based on redate.py by David Finlayson. 75 76 year=date.year; month=date.month; day=date.day 77 hour=date.hour; minute=date.minute; second=date.second 78 # Convert time to fractions of a day 79 day = day + hour/24.0 + minute/1440.0 + second/86400.0 80 81 # Start Meeus algorithm (variables are in his notation) 82 if (month < 3): 83 month = month + 12 84 year = year - 1 85 86 A = int(year/100) 87 88 jd = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + \ 89 day - 1524.5 90 91 # optionally adjust the jd for the switch from 92 # the Julian to Gregorian Calendar 93 # here assumed to have occurred the day after 1582 October 4 94 if calendar in ['standard','gregorian']: 95 if jd >= 2299170.5: 96 # 1582 October 15 (Gregorian Calendar) 97 B = 2 - A + int(A/4) 98 elif jd < 2299160.5: 99 # 1582 October 5 (Julian Calendar) 100 B = 0 101 else: 102 raise ValueError, 'impossible date (falls in gap between end of Julian calendar and beginning of Gregorian calendar' 103 elif calendar == 'proleptic_gregorian': 104 B = 2 - A + int(A/4) 105 elif calendar == 'julian': 106 B = 0 107 else: 108 raise ValueError, 'unknown calendar, must be one of julian,standard,gregorian,proleptic_gregorian, got %s' % calendar 109 110 # adjust for Julian calendar if necessary 111 jd = jd + B 112 113 return jd
114
115 -def _NoLeapDayFromDate(date):
116 117 """ 118 119 creates a Julian Day for a calendar with no leap years from a datetime 120 instance. Returns the fractional Julian Day (resolution 1 second). 121 122 """ 123 124 year=date.year; month=date.month; day=date.day 125 hour=date.hour; minute=date.minute; second=date.second 126 # Convert time to fractions of a day 127 day = day + hour/24.0 + minute/1440.0 + second/86400.0 128 129 # Start Meeus algorithm (variables are in his notation) 130 if (month < 3): 131 month = month + 12 132 year = year - 1 133 134 jd = int(365. * (year + 4716)) + int(30.6001 * (month + 1)) + \ 135 day - 1524.5 136 137 return jd
138
139 -def _AllLeapFromDate(date):
140 141 """ 142 143 creates a Julian Day for a calendar where all years have 366 days from 144 a 'datetime-like' object. 145 Returns the fractional Julian Day (resolution 1 second). 146 147 """ 148 149 year=date.year; month=date.month; day=date.day 150 hour=date.hour; minute=date.minute; second=date.second 151 # Convert time to fractions of a day 152 day = day + hour/24.0 + minute/1440.0 + second/86400.0 153 154 # Start Meeus algorithm (variables are in his notation) 155 if (month < 3): 156 month = month + 12 157 year = year - 1 158 159 jd = int(366. * (year + 4716)) + int(30.6001 * (month + 1)) + \ 160 day - 1524.5 161 162 return jd
163
164 -def _360DayFromDate(date):
165 166 """ 167 168 creates a Julian Day for a calendar where all months have 30 daysfrom 169 a 'datetime-like' object. 170 Returns the fractional Julian Day (resolution 1 second). 171 172 """ 173 174 year=date.year; month=date.month; day=date.day 175 hour=date.hour; minute=date.minute; second=date.second 176 # Convert time to fractions of a day 177 day = day + hour/24.0 + minute/1440.0 + second/86400.0 178 179 jd = int(360. * (year + 4716)) + int(30. * (month - 1)) + day 180 181 return jd
182
183 -def DateFromJulianDay(JD,calendar='standard'):
184 """ 185 186 returns a 'datetime-like' object given Julian Day. Julian Day is a 187 fractional day with a resolution of 1 second. 188 189 if calendar='standard' or 'gregorian' (default), Julian day follows Julian 190 Calendar on and before 1582-10-5, Gregorian calendar after 1582-10-15. 191 192 if calendar='proleptic_gregorian', Julian Day follows gregorian calendar. 193 194 if calendar='julian', Julian Day follows julian calendar. 195 196 The datetime object is a 'real' datetime object if the date falls in 197 the Gregorian calendar (i.e. calendar='proleptic_gregorian', or 198 calendar = 'standard'/'gregorian' and the date is after 1582-10-15). 199 Otherwise, it's a 'phony' datetime object which is actually an instance 200 of netcdftime.datetime. 201 202 203 Algorithm: 204 205 Meeus, Jean (1998) Astronomical Algorithms (2nd Edition). Willmann-Bell, 206 Virginia. p. 63 207 208 """ 209 210 # based on redate.py by David Finlayson. 211 212 if JD < 0: 213 raise ValueError, 'Julian Day must be positive' 214 215 dayofwk = int(math.fmod(int(JD + 1.5),7)) 216 (F, Z) = math.modf(JD + 0.5) 217 Z = int(Z) 218 if calendar in ['standard','gregorian']: 219 if JD < 2299160.5: 220 A = Z 221 else: 222 alpha = int((Z - 1867216.25)/36524.25) 223 A = Z + 1 + alpha - int(alpha/4) 224 225 elif calendar == 'proleptic_gregorian': 226 alpha = int((Z - 1867216.25)/36524.25) 227 A = Z + 1 + alpha - int(alpha/4) 228 elif calendar == 'julian': 229 A = Z 230 else: 231 raise ValueError, 'unknown calendar, must be one of julian,standard,gregorian,proleptic_gregorian, got %s' % calendar 232 233 B = A + 1524 234 C = int((B - 122.1)/365.25) 235 D = int(365.25 * C) 236 E = int((B - D)/30.6001) 237 238 # Convert to date 239 day = B - D - int(30.6001 * E) + F 240 nday = B-D-123 241 if nday <= 305: 242 dayofyr = nday+60 243 else: 244 dayofyr = nday-305 245 if E < 14: 246 month = E - 1 247 else: 248 month = E - 13 249 250 if month > 2: 251 year = C - 4716 252 else: 253 year = C - 4715 254 255 # a leap year? 256 leap = 0 257 if year % 4 == 0: 258 leap = 1 259 if calendar == 'proleptic_gregorian' or \ 260 (calendar in ['standard','gregorian'] and JD >= 2299160.5): 261 if year % 100 == 0 and year % 400 != 0: 262 print year % 100, year % 400 263 leap = 0 264 if leap and month > 2: 265 dayofyr = dayofyr + leap 266 267 # Convert fractions of a day to time 268 (dfrac, days) = math.modf(day/1.0) 269 (hfrac, hours) = math.modf(dfrac * 24.0) 270 (mfrac, minutes) = math.modf(hfrac * 60.0) 271 seconds = round(mfrac * 60.0) # seconds are rounded 272 273 if seconds > 59: 274 seconds = 0 275 minutes = minutes + 1 276 if minutes > 59: 277 minutes = 0 278 hours = hours + 1 279 if hours > 23: 280 hours = 0 281 days = days + 1 282 283 # return a 'real' datetime instance if calendar is gregorian. 284 if calendar == 'proleptic_gregorian' or \ 285 (calendar in ['standard','gregorian'] and JD >= 2299160.5): 286 return real_datetime(year,month,int(days),int(hours),int(minutes),int(seconds)) 287 else: 288 # or else, return a 'datetime-like' instance. 289 return datetime(year,month,int(days),int(hours),int(minutes),int(seconds),dayofwk,dayofyr)
290
291 -def _DateFromNoLeapDay(JD):
292 """ 293 294 returns a 'datetime-like' object given Julian Day for a calendar with no leap 295 days. Julian Day is a fractional day with a resolution of 1 second. 296 297 """ 298 299 # based on redate.py by David Finlayson. 300 301 if JD < 0: 302 raise ValueError, 'Julian Day must be positive' 303 304 dayofwk = int(math.fmod(int(JD + 1.5),7)) 305 (F, Z) = math.modf(JD + 0.5) 306 Z = int(Z) 307 A = Z 308 B = A + 1524 309 C = int((B - 122.1)/365.) 310 D = int(365. * C) 311 E = int((B - D)/30.6001) 312 313 # Convert to date 314 day = B - D - int(30.6001 * E) + F 315 nday = B-D-123 316 if nday <= 305: 317 dayofyr = nday+60 318 else: 319 dayofyr = nday-305 320 if E < 14: 321 month = E - 1 322 else: 323 month = E - 13 324 325 if month > 2: 326 year = C - 4716 327 else: 328 year = C - 4715 329 330 # Convert fractions of a day to time 331 (dfrac, days) = math.modf(day/1.0) 332 (hfrac, hours) = math.modf(dfrac * 24.0) 333 (mfrac, minutes) = math.modf(hfrac * 60.0) 334 seconds = round(mfrac * 60.0) # seconds are rounded 335 336 if seconds > 59: 337 seconds = 0 338 minutes = minutes + 1 339 if minutes > 59: 340 minutes = 0 341 hours = hours + 1 342 if hours > 23: 343 hours = 0 344 days = days + 1 345 346 return datetime(year,month,int(days),int(hours),int(minutes),int(seconds), dayofwk, dayofyr)
347
348 -def _DateFromAllLeap(JD):
349 """ 350 351 returns a 'datetime-like' object given Julian Day for a calendar where all 352 years have 366 days. 353 Julian Day is a fractional day with a resolution of 1 second. 354 355 """ 356 357 # based on redate.py by David Finlayson. 358 359 if JD < 0: 360 raise ValueError, 'Julian Day must be positive' 361 362 dayofwk = int(math.fmod(int(JD + 1.5),7)) 363 (F, Z) = math.modf(JD + 0.5) 364 Z = int(Z) 365 A = Z 366 B = A + 1524 367 C = int((B - 122.1)/366.) 368 D = int(366. * C) 369 E = int((B - D)/30.6001) 370 371 # Convert to date 372 day = B - D - int(30.6001 * E) + F 373 nday = B-D-123 374 if nday <= 305: 375 dayofyr = nday+60 376 else: 377 dayofyr = nday-305 378 if E < 14: 379 month = E - 1 380 else: 381 month = E - 13 382 if month > 2: 383 dayofyr = dayofyr+1 384 385 if month > 2: 386 year = C - 4716 387 else: 388 year = C - 4715 389 390 # Convert fractions of a day to time 391 (dfrac, days) = math.modf(day/1.0) 392 (hfrac, hours) = math.modf(dfrac * 24.0) 393 (mfrac, minutes) = math.modf(hfrac * 60.0) 394 seconds = round(mfrac * 60.0) # seconds are rounded 395 396 if seconds > 59: 397 seconds = 0 398 minutes = minutes + 1 399 if minutes > 59: 400 minutes = 0 401 hours = hours + 1 402 if hours > 23: 403 hours = 0 404 days = days + 1 405 406 return datetime(year,month,int(days),int(hours),int(minutes),int(seconds), dayofwk, dayofyr)
407
408 -def _DateFrom360Day(JD):
409 """ 410 411 returns a 'datetime-like' object given Julian Day for a calendar where all 412 months have 30 days. 413 Julian Day is a fractional day with a resolution of 1 second. 414 415 """ 416 417 if JD < 0: 418 raise ValueError, 'Julian Day must be positive' 419 420 #jd = int(360. * (year + 4716)) + int(30. * (month - 1)) + day 421 (F, Z) = math.modf(JD) 422 year = int((Z-0.5)/360.) - 4716 423 dayofyr = Z - (year+4716)*360 424 month = int((dayofyr-0.5)/30)+1 425 day = dayofyr - (month-1)*30 + F 426 427 # Convert fractions of a day to time 428 (dfrac, days) = math.modf(day/1.0) 429 (hfrac, hours) = math.modf(dfrac * 24.0) 430 (mfrac, minutes) = math.modf(hfrac * 60.0) 431 seconds = round(mfrac * 60.0) # seconds are rounded 432 433 if seconds > 59: 434 seconds = 0 435 minutes = minutes + 1 436 if minutes > 59: 437 minutes = 0 438 hours = hours + 1 439 if hours > 23: 440 hours = 0 441 days = days + 1 442 443 return datetime(year,month,int(days),int(hours),int(minutes),int(seconds),-1, int(dayofyr))
444
445 -def _dateparse(timestr):
446 """parse a string of the form time-units since yyyy-mm-dd hh:mm:ss 447 return a tuple (units, datetimeinstance)""" 448 timestr_split = timestr.split() 449 units = timestr_split[0].lower() 450 if units not in _units: 451 raise ValueError,"units must be one of 'seconds', 'minutes', 'hours' or 'days' (or singular version of these), got '%s'" % units 452 if timestr_split[1].lower() != 'since': 453 raise ValueError,"no 'since' in unit_string" 454 # parse the date string. 455 n = timestr.find('since')+6 456 year,month,day,hour,minute,second,utc_offset = _parse_date(timestr[n:]) 457 return units, utc_offset, datetime(year, month, day, hour, minute, second)
458
459 -class utime:
460 """ 461 Performs conversions of netCDF time coordinate 462 data to/from datetime objects. 463 464 To initialize: C{t = utime(unit_string,calendar='standard')} 465 466 where 467 468 B{C{unit_string}} is a string of the form 469 C{'time-units since <time-origin>'} defining the time units. 470 471 Valid time-units are days, hours, minutes and seconds (the singular forms 472 are also accepted). An example unit_string would be C{'hours 473 since 0001-01-01 00:00:00'}. 474 475 The B{C{calendar}} keyword describes the calendar used in the time calculations. 476 All the values currently defined in the U{CF metadata convention 477 <http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.1/cf-conventions.html#time-coordinate>} 478 are accepted. The default is C{'standard'}, which corresponds to the mixed 479 Gregorian/Julian calendar used by the C{udunits library}. Valid calendars 480 are: 481 482 C{'gregorian'} or C{'standard'} (default): 483 484 Mixed Gregorian/Julian calendar as defined by udunits. 485 486 C{'proleptic_gregorian'}: 487 488 A Gregorian calendar extended to dates before 1582-10-15. That is, a year 489 is a leap year if either (i) it is divisible by 4 but not by 100 or (ii) 490 it is divisible by 400. 491 492 C{'noleap'} or C{'365_day'}: 493 494 Gregorian calendar without leap years, i.e., all years are 365 days long. 495 all_leap or 366_day Gregorian calendar with every year being a leap year, 496 i.e., all years are 366 days long. 497 498 C{'360_day'}: 499 500 All years are 360 days divided into 30 day months. 501 502 C{'julian'}: 503 504 Proleptic Julian calendar, extended to dates after 1582-10-5. A year is a 505 leap year if it is divisible by 4. 506 507 The C{L{num2date}} and C{L{date2num}} class methods can used to convert datetime 508 instances to/from the specified time units using the specified calendar. 509 510 The datetime instances returned by C{num2date} are 'real' python datetime 511 objects if the date falls in the Gregorian calendar (i.e. 512 C{calendar='proleptic_gregorian', 'standard'} or C{'gregorian'} and 513 the date is after 1582-10-15). Otherwise, they are 'phony' datetime 514 objects which are actually instances of C{L{netcdftime.datetime}}. This is 515 because the python datetime module cannot handle the weird dates in some 516 calendars (such as C{'360_day'} and C{'all_leap'}) which don't exist in any real 517 world calendar. 518 519 520 Example usage: 521 522 >>> from netcdftime import utime 523 >>> from datetime import datetime 524 >>> cdftime = utime('hours since 0001-01-01 00:00:00') 525 >>> date = datetime.now() 526 >>> print date 527 2006-03-17 16:04:02.561678 528 >>> 529 >>> t = cdftime.date2num(date) 530 >>> print t 531 17577328.0672 532 >>> 533 >>> date = cdftime.num2date(t) 534 >>> print date 535 2006-03-17 16:04:02 536 >>> 537 538 The resolution of the transformation operation is 1 second. 539 540 Warning: Dates between 1582-10-5 and 1582-10-15 do not exist in the 541 C{'standard'} or C{'gregorian'} calendars. An exception will be raised if you pass 542 a 'datetime-like' object in that range to the C{L{date2num}} class method. 543 544 Words of Wisdom from the British MetOffice concerning reference dates: 545 546 "udunits implements the mixed Gregorian/Julian calendar system, as 547 followed in England, in which dates prior to 1582-10-15 are assumed to use 548 the Julian calendar. Other software cannot be relied upon to handle the 549 change of calendar in the same way, so for robustness it is recommended 550 that the reference date be later than 1582. If earlier dates must be used, 551 it should be noted that udunits treats 0 AD as identical to 1 AD." 552 553 @ivar origin: datetime instance defining the origin of the netCDF time variable. 554 @ivar calendar: the calendar used (as specified by the C{calendar} keyword). 555 @ivar unit_string: a string defining the the netCDF time variable. 556 @ivar units: the units part of C{unit_string} (i.e. 'days', 'hours', 'seconds'). 557 """
558 - def __init__(self,unit_string,calendar='standard'):
559 """ 560 @param unit_string: a string of the form 561 C{'time-units since <time-origin>'} defining the time units. 562 563 Valid time-units are days, hours, minutes and seconds (the singular forms 564 are also accepted). An example unit_string would be C{'hours 565 since 0001-01-01 00:00:00'}. 566 567 @keyword calendar: describes the calendar used in the time calculations. 568 All the values currently defined in the U{CF metadata convention 569 <http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.1/cf-conventions.html#time-coordinate>} 570 are accepted. The default is C{'standard'}, which corresponds to the mixed 571 Gregorian/Julian calendar used by the C{udunits library}. Valid calendars 572 are: 573 - C{'gregorian'} or C{'standard'} (default): 574 Mixed Gregorian/Julian calendar as defined by udunits. 575 - C{'proleptic_gregorian'}: 576 A Gregorian calendar extended to dates before 1582-10-15. That is, a year 577 is a leap year if either (i) it is divisible by 4 but not by 100 or (ii) 578 it is divisible by 400. 579 - C{'noleap'} or C{'365_day'}: 580 Gregorian calendar without leap years, i.e., all years are 365 days long. 581 - C{'all_leap'} or C{'366_day'}: 582 Gregorian calendar with every year being a leap year, i.e., 583 all years are 366 days long. 584 -C{'360_day'}: 585 All years are 360 days divided into 30 day months. 586 -C{'julian'}: 587 Proleptic Julian calendar, extended to dates after 1582-10-5. A year is a 588 leap year if it is divisible by 4. 589 590 @returns: A class instance which may be used for converting times from netCDF 591 units to datetime objects. 592 """ 593 if calendar in _calendars: 594 self.calendar = calendar 595 else: 596 raise ValueError, "calendar must be one of %s, got '%s'" % (str(_calendars),calendar) 597 units, tzoffset, self.origin = _dateparse(unit_string) 598 self.tzoffset = tzoffset # time zone offset in minutes 599 self.units = units 600 self.unit_string = unit_string 601 if self.calendar in ['noleap','365_day'] and self.origin.month == 2 and self.origin.day == 29: 602 raise ValueError, 'cannot specify a leap day as the reference time with the noleap calendar' 603 if self.calendar == '360_day' and self.origin.day > 30: 604 raise ValueError, 'there are only 30 days in every month with the 360_day calendar' 605 if self.calendar in ['noleap','365_day']: 606 self._jd0 = _NoLeapDayFromDate(self.origin) 607 elif self.calendar in ['all_leap','366_day']: 608 self._jd0 = _AllLeapFromDate(self.origin) 609 elif self.calendar == '360_day': 610 self._jd0 = _360DayFromDate(self.origin) 611 else: 612 self._jd0 = JulianDayFromDate(self.origin,calendar=self.calendar)
613
614 - def date2num(self,date):
615 """ 616 Returns C{time_value} in units described by L{unit_string}, using 617 the specified L{calendar}, given a 'datetime-like' object. 618 619 The datetime object must represent UTC with no time-zone offset. 620 If there is a time-zone offset implied by L{unit_string}, it will 621 be applied to the returned numeric values. 622 623 Resolution is 1 second. 624 625 If C{calendar = 'standard'} or C{'gregorian'} (indicating 626 that the mixed Julian/Gregorian calendar is to be used), an 627 exception will be raised if the 'datetime-like' object describes 628 a date between 1582-10-5 and 1582-10-15. 629 630 Works for scalars, sequences and numpy arrays. 631 Returns a scalar if input is a scalar, else returns a numpy array. 632 """ 633 isscalar = False 634 try: 635 date[0] 636 except: 637 isscalar = True 638 if not isscalar: 639 date = numpy.array(date) 640 shape = date.shape 641 if self.calendar in ['julian','standard','gregorian','proleptic_gregorian']: 642 if isscalar: 643 jdelta = JulianDayFromDate(date,self.calendar)-self._jd0 644 else: 645 jdelta = [JulianDayFromDate(d,self.calendar)-self._jd0 for d in date.flat] 646 elif self.calendar in ['noleap','365_day']: 647 if isscalar: 648 if date.month == 2 and date.day == 29: 649 raise ValueError, 'there is no leap day in the noleap calendar' 650 jdelta = _NoLeapDayFromDate(date) - self._jd0 651 else: 652 jdelta = [] 653 for d in date.flat: 654 if d.month == 2 and d.day == 29: 655 raise ValueError, 'there is no leap day in the noleap calendar' 656 jdelta.append(_NoLeapDayFromDate(d)-self._jd0) 657 elif self.calendar in ['all_leap','366_day']: 658 if isscalar: 659 jdelta = _AllLeapFromDate(date) - self._jd0 660 else: 661 jdelta = [_AllLeapFromDate(d)-self._jd0 for d in date.flat] 662 elif self.calendar == '360_day': 663 if self.calendar == '360_day' and date.day > 30: 664 raise ValueError, 'there are only 30 days in every month with the 360_day calendar' 665 if isscalar: 666 jdelta = _360DayFromDate(date) - self._jd0 667 else: 668 jdelta = [_360DayFromDate(d)-self._jd0 for d in date.flat] 669 if not isscalar: 670 jdelta = numpy.array(jdelta) 671 # convert to desired units, add time zone offset. 672 if self.units in ['second','seconds']: 673 jdelta = jdelta*86400. + self.tzoffset*60. 674 elif self.units in ['minute','minutes']: 675 jdelta = jdelta*1440. + self.tzoffset 676 elif self.units in ['hour','hours']: 677 jdelta = jdelta*24. + self.tzoffset/60. 678 elif self.units in ['day','days']: 679 jdelta = jdelta + self.tzoffset/1440. 680 if isscalar: 681 return jdelta 682 else: 683 return numpy.reshape(jdelta,shape)
684
685 - def num2date(self,time_value):
686 """ 687 Return a 'datetime-like' object given a C{time_value} in units 688 described by L{unit_string}, using L{calendar}. 689 690 dates are in UTC with no offset, even if L{unit_string} contains 691 a time zone offset from UTC. 692 693 Resolution is 1 second. 694 695 Works for scalars, sequences and numpy arrays. 696 Returns a scalar if input is a scalar, else returns a numpy array. 697 698 The datetime instances returned by C{num2date} are 'real' python datetime 699 objects if the date falls in the Gregorian calendar (i.e. 700 C{calendar='proleptic_gregorian'}, or C{calendar = 'standard'/'gregorian'} and 701 the date is after 1582-10-15). Otherwise, they are 'phony' datetime 702 objects which are actually instances of netcdftime.datetime. This is 703 because the python datetime module cannot handle the weird dates in some 704 calendars (such as C{'360_day'} and C{'all_leap'}) which 705 do not exist in any real world calendar. 706 """ 707 isscalar = False 708 try: 709 time_value[0] 710 except: 711 isscalar = True 712 ismasked = False 713 if hasattr(time_value,'mask'): 714 mask = time_value.mask 715 ismasked = True 716 if not isscalar: 717 time_value = numpy.array(time_value, dtype='d') 718 shape = time_value.shape 719 # convert to desired units, remove time zone offset. 720 if self.units in ['second','seconds']: 721 jdelta = time_value/86400. - self.tzoffset/1440. 722 elif self.units in ['minute','minutes']: 723 jdelta = time_value/1440. - self.tzoffset/1440. 724 elif self.units in ['hour','hours']: 725 jdelta = time_value/24. - self.tzoffset/1440. 726 elif self.units in ['day','days']: 727 jdelta = time_value - self.tzoffset/1440. 728 jd = self._jd0 + jdelta 729 if self.calendar in ['julian','standard','gregorian','proleptic_gregorian']: 730 if not isscalar: 731 if ismasked: 732 date = [] 733 for j,m in zip(jd.flat, mask.flat): 734 if not m: 735 date.append(DateFromJulianDay(j,self.calendar)) 736 else: 737 date.append(None) 738 else: 739 date = [DateFromJulianDay(j,self.calendar) for j in jd.flat] 740 else: 741 if ismasked and mask.item(): 742 date = None 743 else: 744 date = DateFromJulianDay(jd,self.calendar) 745 elif self.calendar in ['noleap','365_day']: 746 if not isscalar: 747 date = [_DateFromNoLeapDay(j) for j in jd.flat] 748 else: 749 date = _DateFromNoLeapDay(jd) 750 elif self.calendar in ['all_leap','366_day']: 751 if not isscalar: 752 date = [_DateFromAllLeap(j) for j in jd.flat] 753 else: 754 date = _DateFromAllLeap(jd) 755 elif self.calendar == '360_day': 756 if not isscalar: 757 date = [_DateFrom360Day(j) for j in jd.flat] 758 else: 759 date = _DateFrom360Day(jd) 760 if isscalar: 761 return date 762 else: 763 return numpy.reshape(numpy.array(date),shape)
764
765 -def _parse_timezone(tzstring):
766 """Parses ISO 8601 time zone specs into tzinfo offsets 767 768 Adapted from pyiso8601 (http://code.google.com/p/pyiso8601/) 769 """ 770 if tzstring == "Z": 771 return 0 772 # This isn't strictly correct, but it's common to encounter dates without 773 # timezones so I'll assume the default (which defaults to UTC). 774 if tzstring is None: 775 return 0 776 m = TIMEZONE_REGEX.match(tzstring) 777 prefix, hours, minutes = m.groups() 778 hours, minutes = int(hours), int(minutes) 779 if prefix == "-": 780 hours = -hours 781 minutes = -minutes 782 return minutes + hours*60.
783
784 -def _parse_date(datestring):
785 """Parses ISO 8601 dates into datetime objects 786 787 The timezone is parsed from the date string, assuming UTC 788 by default. 789 790 Adapted from pyiso8601 (http://code.google.com/p/pyiso8601/) 791 """ 792 if not isinstance(datestring, basestring): 793 raise ValueError("Expecting a string %r" % datestring) 794 m = ISO8601_REGEX.match(datestring) 795 if not m: 796 raise ValueError("Unable to parse date string %r" % datestring) 797 groups = m.groupdict() 798 tzoffset_mins = _parse_timezone(groups["timezone"]) 799 if groups["hour"] is None: 800 groups["hour"]=0 801 if groups["minute"] is None: 802 groups["minute"]=0 803 if groups["second"] is None: 804 groups["second"]=0 805 #if groups["fraction"] is None: 806 # groups["fraction"] = 0 807 #else: 808 # groups["fraction"] = int(float("0.%s" % groups["fraction"]) * 1e6) 809 return int(groups["year"]), int(groups["month"]), int(groups["day"]),\ 810 int(groups["hour"]), int(groups["minute"]), int(groups["second"]),\ 811 tzoffset_mins
812 813 _illegal_s = re.compile(r"((^|[^%])(%%)*%s)") 814
815 -def _findall(text, substr):
816 # Also finds overlaps 817 sites = [] 818 i = 0 819 while 1: 820 j = text.find(substr, i) 821 if j == -1: 822 break 823 sites.append(j) 824 i=j+1 825 return sites
826 827 # Every 28 years the calendar repeats, except through century leap 828 # years where it's 6 years. But only if you're using the Gregorian 829 # calendar. ;) 830
831 -def _strftime(dt, fmt):
832 if _illegal_s.search(fmt): 833 raise TypeError("This strftime implementation does not handle %s") 834 # don't use strftime method at all. 835 #if dt.year > 1900: 836 # return dt.strftime(fmt) 837 838 year = dt.year 839 # For every non-leap year century, advance by 840 # 6 years to get into the 28-year repeat cycle 841 delta = 2000 - year 842 off = 6*(delta // 100 + delta // 400) 843 year = year + off 844 845 # Move to around the year 2000 846 year = year + ((2000 - year)//28)*28 847 timetuple = dt.timetuple() 848 s1 = time.strftime(fmt, (year,) + timetuple[1:]) 849 sites1 = _findall(s1, str(year)) 850 851 s2 = time.strftime(fmt, (year+28,) + timetuple[1:]) 852 sites2 = _findall(s2, str(year+28)) 853 854 sites = [] 855 for site in sites1: 856 if site in sites2: 857 sites.append(site) 858 859 s = s1 860 syear = "%4d" % (dt.year,) 861 for site in sites: 862 s = s[:site] + syear + s[site+4:] 863 return s
864
865 -def date2num(dates,units,calendar='standard'):
866 """ 867 date2num(dates,units,calendar='standard') 868 869 Return numeric time values given datetime objects. The units 870 of the numeric time values are described by the L{units} argument 871 and the L{calendar} keyword. The datetime objects must 872 be in UTC with no time-zone offset. If there is a 873 time-zone offset in C{units}, it will be applied to the 874 returned numeric values. 875 876 Like the matplotlib C{date2num} function, except that it allows 877 for different units and calendars. Behaves the same if 878 C{units = 'days since 0001-01-01 00:00:00'} and 879 C{calendar = 'proleptic_gregorian'}. 880 881 @param dates: A datetime object or a sequence of datetime objects. 882 The datetime objects should not include a time-zone offset. 883 884 @param units: a string of the form C{'B{time units} since B{reference time}}' 885 describing the time units. B{C{time units}} can be days, hours, minutes 886 or seconds. B{C{reference time}} is the time origin. A valid choice 887 would be units=C{'hours since 1800-01-01 00:00:00 -6:00'}. 888 889 @param calendar: describes the calendar used in the time calculations. 890 All the values currently defined in the U{CF metadata convention 891 <http://cf-pcmdi.llnl.gov/documents/cf-conventions/>} are supported. 892 Valid calendars C{'standard', 'gregorian', 'proleptic_gregorian' 893 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'}. 894 Default is C{'standard'}, which is a mixed Julian/Gregorian calendar. 895 896 @return: a numeric time value, or an array of numeric time values. 897 898 The maximum resolution of the numeric time values is 1 second. 899 """ 900 cdftime = utime(units,calendar=calendar) 901 return cdftime.date2num(dates)
902
903 -def num2date(times,units,calendar='standard'):
904 """ 905 num2date(times,units,calendar='standard') 906 907 Return datetime objects given numeric time values. The units 908 of the numeric time values are described by the C{units} argument 909 and the C{calendar} keyword. The returned datetime objects represent 910 UTC with no time-zone offset, even if the specified 911 C{units} contain a time-zone offset. 912 913 Like the matplotlib C{num2date} function, except that it allows 914 for different units and calendars. Behaves the same if 915 C{units = 'days since 001-01-01 00:00:00'} and 916 C{calendar = 'proleptic_gregorian'}. 917 918 @param times: numeric time values. Maximum resolution is 1 second. 919 920 @param units: a string of the form C{'B{time units} since B{reference time}}' 921 describing the time units. B{C{time units}} can be days, hours, minutes 922 or seconds. B{C{reference time}} is the time origin. A valid choice 923 would be units=C{'hours since 1800-01-01 00:00:00 -6:00'}. 924 925 @param calendar: describes the calendar used in the time calculations. 926 All the values currently defined in the U{CF metadata convention 927 <http://cf-pcmdi.llnl.gov/documents/cf-conventions/>} are supported. 928 Valid calendars C{'standard', 'gregorian', 'proleptic_gregorian' 929 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'}. 930 Default is C{'standard'}, which is a mixed Julian/Gregorian calendar. 931 932 @return: a datetime instance, or an array of datetime instances. 933 934 The datetime instances returned are 'real' python datetime 935 objects if the date falls in the Gregorian calendar (i.e. 936 C{calendar='proleptic_gregorian'}, or C{calendar = 'standard'} or C{'gregorian'} 937 and the date is after 1582-10-15). Otherwise, they are 'phony' datetime 938 objects which support some but not all the methods of 'real' python 939 datetime objects. This is because the python datetime module cannot 940 the uses the C{'proleptic_gregorian'} calendar, even before the switch 941 occured from the Julian calendar in 1582. The datetime instances 942 do not contain a time-zone offset, even if the specified C{units} 943 contains one. 944 """ 945 cdftime = utime(units,calendar=calendar) 946 return cdftime.num2date(times)
947
948 -def _check_index(indices, dates, nctime, calendar):
949 """Return True if the time indices given correspond to the given dates, 950 False otherwise. 951 952 Parameters: 953 954 indices : sequence of integers 955 Positive integers indexing the time variable. 956 957 dates : sequence of datetime objects 958 Reference dates. 959 960 nctime : netCDF Variable object 961 NetCDF time object. 962 963 calendar : string 964 Calendar of nctime. 965 """ 966 if (indices <0).any(): 967 return False 968 969 if (indices >= nctime.shape[0]).any(): 970 return False 971 972 t = nctime[indices] 973 # if fancy indexing not available, fall back on this. 974 # t=[] 975 # for ind in indices: 976 # t.append(nctime[ind]) 977 return numpy.all( num2date(t, nctime.units, calendar) == dates)
978 979 980
981 -def date2index(dates, nctime, calendar=None, select='exact'):
982 """ 983 date2index(dates, nctime, calendar=None, select='exact') 984 985 Return indices of a netCDF time variable corresponding to the given dates. 986 987 @param dates: A datetime object or a sequence of datetime objects. 988 The datetime objects should not include a time-zone offset. 989 990 @param nctime: A netCDF time variable object. The nctime object must have a 991 C{units} attribute. The entries are assumed to be stored in increasing 992 order. 993 994 @param calendar: Describes the calendar used in the time calculation. 995 Valid calendars C{'standard', 'gregorian', 'proleptic_gregorian' 996 'noleap', '365_day', '360_day', 'julian', 'all_leap', '366_day'}. 997 Default is C{'standard'}, which is a mixed Julian/Gregorian calendar 998 If C{calendar} is None, its value is given by C{nctime.calendar} or 999 C{standard} if no such attribute exists. 1000 1001 @param select: C{'exact', 'before', 'after', 'nearest'} 1002 The index selection method. C{exact} will return the indices perfectly 1003 matching the dates given. C{before} and C{after} will return the indices 1004 corresponding to the dates just before or just after the given dates if 1005 an exact match cannot be found. C{nearest} will return the indices that 1006 correpond to the closest dates. 1007 """ 1008 # Setting the calendar. 1009 if calendar == None: 1010 calendar = getattr(nctime, 'calendar', 'standard') 1011 1012 num = numpy.atleast_1d(date2num(dates, nctime.units, calendar)) 1013 N = len(nctime) 1014 1015 # Trying to infer the correct index from the starting time and the stride. 1016 # This assumes that the times are increasing uniformly. 1017 t0, t1 = nctime[:2] 1018 dt = t1 - t0 1019 index = numpy.array((num-t0)/dt, int) 1020 1021 # Checking that the index really corresponds to the given date. 1022 # If the times do not correspond, then it means that the times 1023 # are not increasing uniformly and we try the bisection method. 1024 if not _check_index(index, dates, nctime, calendar): 1025 1026 # Use the bisection method. Assumes the dates are ordered. 1027 import bisect 1028 index = numpy.array([bisect.bisect_left(nctime, n) for n in num], int) 1029 1030 after = index == N 1031 before = index == 0 1032 1033 if select in ['before', 'exact'] and numpy.any(before): 1034 raise ValueError, 'At least one of the dates given is before the first date in `nctime`.' 1035 1036 if select in ['after', 'exact'] and numpy.any(after): 1037 raise ValueError, 'At least one of the dates given is after the last date in `nctime`.' 1038 1039 1040 # Find the dates for which the match is not perfect. 1041 # Use list comprehension instead of the simpler `nctime[index]` since 1042 # not all time objects support numpy integer indexing (eg dap). 1043 index[after] = N-1 1044 ncnum = numpy.squeeze([nctime[i] for i in index]) 1045 mismatch = numpy.nonzero(ncnum != num)[0] 1046 1047 if select == 'exact': 1048 if len(mismatch) > 0: 1049 raise ValueError, 'Some of the dates specified were not found in the `nctime` variable.' 1050 1051 elif select == 'before': 1052 index[after] = N 1053 index[mismatch] -= 1 1054 1055 elif select == 'after': 1056 pass 1057 1058 elif select == 'nearest': 1059 nearest_to_left = num[mismatch] < numpy.array( [nctime[i-1] + nctime[i] for i in index[mismatch]]) / 2. 1060 index[mismatch] = index[mismatch] - 1 * nearest_to_left 1061 1062 else: 1063 raise ValueError("%s is not an option for the `select` argument."%select) 1064 1065 1066 # Correct for indices equal to -1 1067 index[before] = 0 1068 1069 # convert numpy scalars or single element arrays to python ints. 1070 return _toscalar(index)
1071 1072
1073 -def _toscalar(a):
1074 if a.shape in [(),(1,)]: 1075 return a.item() 1076 else: 1077 return a
1078