netcdftime.netcdftime

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