Revision: 4580
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=4580&view=rev
Author: jswhit
Date: 2007-12-04 09:39:31 -0800 (Tue, 04 Dec 2007)
Log Message:
-----------
add netcdftime module
Added Paths:
-----------
trunk/toolkits/basemap/lib/netcdftime/
trunk/toolkits/basemap/lib/netcdftime/__init__.py
trunk/toolkits/basemap/lib/netcdftime/netcdftime.py
trunk/toolkits/basemap/lib/netcdftime/strftime.py
trunk/toolkits/basemap/lib/netcdftime/strptime.py
Added: trunk/toolkits/basemap/lib/netcdftime/__init__.py
===================================================================
--- trunk/toolkits/basemap/lib/netcdftime/__init__.py
(rev 0)
+++ trunk/toolkits/basemap/lib/netcdftime/__init__.py 2007-12-04 17:39:31 UTC
(rev 4580)
@@ -0,0 +1,2 @@
+from netcdftime import __doc__, __version__
+from netcdftime import *
Added: trunk/toolkits/basemap/lib/netcdftime/netcdftime.py
===================================================================
--- trunk/toolkits/basemap/lib/netcdftime/netcdftime.py
(rev 0)
+++ trunk/toolkits/basemap/lib/netcdftime/netcdftime.py 2007-12-04 17:39:31 UTC
(rev 4580)
@@ -0,0 +1,737 @@
+"""
+Performs conversions of netCDF time coordinate data to/from datetime objects.
+"""
+import math
+import numpy
+from datetime import datetime as real_datetime
+from strptime import strptime
+from strftime import strftime
+
+_units = ['days','hours','minutes','seconds','day','hour','minute','second']
+_calendars =
['standard','gregorian','proleptic_gregorian','noleap','julian','all_leap','365_day','366_day','360_day']
+
+__version__ = '0.5.1'
+
+class datetime:
+ """
+Phony datetime object which mimics the python datetime object,
+but allows for dates that don't exist in the proleptic gregorian calendar.
+Doesn't do timedelta operations, doesn't overload + and -.
+
+Has strftime, timetuple and __repr__ methods. The format
+of the string produced by __repr__ is controlled by self.format
+(default %Y-%m-%d %H:%M:%S).
+
+Instance variables are year,month,day,hour,minute,second,dayofwk,dayofyr
+and format.
+ """
+ def
__init__(self,year,month,day,hour=0,minute=0,second=0,dayofwk=-1,dayofyr=1):
+ """dayofyr set to 1 by default - otherwise time.strftime will
complain"""
+ self.year=year
+ self.month=month
+ self.day=day
+ self.hour=hour
+ self.minute=minute
+ self.dayofwk=dayofwk
+ self.dayofyr=dayofyr
+ self.second=second
+ self.format='%Y-%m-%d %H:%M:%S'
+ def strftime(self,format=None):
+ if format is None:
+ format = self.format
+ return strftime(self,format)
+ def timetuple(self):
+ return
(self.year,self.month,self.day,self.hour,self.minute,self.second,self.dayofwk,self.dayofyr,-1)
+ def __repr__(self):
+ return self.strftime(self.format)
+
+def JulianDayFromDate(date,calendar='standard'):
+
+ """
+
+creates a Julian Day from a 'datetime-like' object. Returns the fractional
+Julian Day (resolution 1 second).
+
+if calendar='standard' or 'gregorian' (default), Julian day follows Julian
+Calendar on and before 1582-10-5, Gregorian calendar after 1582-10-15.
+
+if calendar='proleptic_gregorian', Julian Day follows gregorian calendar.
+
+if calendar='julian', Julian Day follows julian calendar.
+
+Algorithm:
+
+Meeus, Jean (1998) Astronomical Algorithms (2nd Edition). Willmann-Bell,
+Virginia. p. 63
+
+ """
+
+ # based on redate.py by David Finlayson.
+
+ year=date.year; month=date.month; day=date.day
+ hour=date.hour; minute=date.minute; second=date.second
+ # Convert time to fractions of a day
+ day = day + hour/24.0 + minute/1440.0 + second/86400.0
+
+ # Start Meeus algorithm (variables are in his notation)
+ if (month < 3):
+ month = month + 12
+ year = year - 1
+
+ A = int(year/100)
+
+ jd = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + \
+ day - 1524.5
+
+ # optionally adjust the jd for the switch from
+ # the Julian to Gregorian Calendar
+ # here assumed to have occurred the day after 1582 October 4
+ if calendar in ['standard','gregorian']:
+ if jd >= 2299170.5:
+ # 1582 October 15 (Gregorian Calendar)
+ B = 2 - A + int(A/4)
+ elif jd < 2299160.5:
+ # 1582 October 5 (Julian Calendar)
+ B = 0
+ else:
+ raise ValueError, 'impossible date (falls in gap between end of
Julian calendar and beginning of Gregorian calendar'
+ elif calendar == 'proleptic_gregorian':
+ B = 2 - A + int(A/4)
+ elif calendar == 'julian':
+ B = 0
+ else:
+ raise ValueError, 'unknown calendar, must be one of
julian,standard,gregorian,proleptic_gregorian, got %s' % calendar
+
+ # adjust for Julian calendar if necessary
+ jd = jd + B
+
+ return jd
+
+def _NoLeapDayFromDate(date):
+
+ """
+
+creates a Julian Day for a calendar with no leap years from a datetime
+instance. Returns the fractional Julian Day (resolution 1 second).
+
+ """
+
+ year=date.year; month=date.month; day=date.day
+ hour=date.hour; minute=date.minute; second=date.second
+ # Convert time to fractions of a day
+ day = day + hour/24.0 + minute/1440.0 + second/86400.0
+
+ # Start Meeus algorithm (variables are in his notation)
+ if (month < 3):
+ month = month + 12
+ year = year - 1
+
+ jd = int(365. * (year + 4716)) + int(30.6001 * (month + 1)) + \
+ day - 1524.5
+
+ return jd
+
+def _AllLeapFromDate(date):
+
+ """
+
+creates a Julian Day for a calendar where all years have 366 days from
+a 'datetime-like' object.
+Returns the fractional Julian Day (resolution 1 second).
+
+ """
+
+ year=date.year; month=date.month; day=date.day
+ hour=date.hour; minute=date.minute; second=date.second
+ # Convert time to fractions of a day
+ day = day + hour/24.0 + minute/1440.0 + second/86400.0
+
+ # Start Meeus algorithm (variables are in his notation)
+ if (month < 3):
+ month = month + 12
+ year = year - 1
+
+ jd = int(366. * (year + 4716)) + int(30.6001 * (month + 1)) + \
+ day - 1524.5
+
+ return jd
+
+def _360DayFromDate(date):
+
+ """
+
+creates a Julian Day for a calendar where all months have 30 daysfrom
+a 'datetime-like' object.
+Returns the fractional Julian Day (resolution 1 second).
+
+ """
+
+ year=date.year; month=date.month; day=date.day
+ hour=date.hour; minute=date.minute; second=date.second
+ # Convert time to fractions of a day
+ day = day + hour/24.0 + minute/1440.0 + second/86400.0
+
+ jd = int(360. * (year + 4716)) + int(30. * (month - 1)) + day
+
+ return jd
+
+def DateFromJulianDay(JD,calendar='standard'):
+ """
+
+returns a 'datetime-like' object given Julian Day. Julian Day is a
+fractional day with a resolution of 1 second.
+
+if calendar='standard' or 'gregorian' (default), Julian day follows Julian
+Calendar on and before 1582-10-5, Gregorian calendar after 1582-10-15.
+
+if calendar='proleptic_gregorian', Julian Day follows gregorian calendar.
+
+if calendar='julian', Julian Day follows julian calendar.
+
+The datetime object is a 'real' datetime object if the date falls in
+the Gregorian calendar (i.e. calendar='proleptic_gregorian', or
+calendar = 'standard'/'gregorian' and the date is after 1582-10-15).
+Otherwise, it's a 'phony' datetime object which is actually an instance
+of netcdftime.datetime.
+
+
+Algorithm:
+
+Meeus, Jean (1998) Astronomical Algorithms (2nd Edition). Willmann-Bell,
+Virginia. p. 63
+
+ """
+
+ # based on redate.py by David Finlayson.
+
+ if JD < 0:
+ raise ValueError, 'Julian Day must be positive'
+
+ dayofwk = int(math.fmod(int(JD + 1.5),7))
+ (F, Z) = math.modf(JD + 0.5)
+ Z = int(Z)
+ if calendar in ['standard','gregorian']:
+ if JD < 2299160.5:
+ A = Z
+ else:
+ alpha = int((Z - 1867216.25)/36524.25)
+ A = Z + 1 + alpha - int(alpha/4)
+
+ elif calendar == 'proleptic_gregorian':
+ alpha = int((Z - 1867216.25)/36524.25)
+ A = Z + 1 + alpha - int(alpha/4)
+ elif calendar == 'julian':
+ A = Z
+ else:
+ raise ValueError, 'unknown calendar, must be one of
julian,standard,gregorian,proleptic_gregorian, got %s' % calendar
+
+ B = A + 1524
+ C = int((B - 122.1)/365.25)
+ D = int(365.25 * C)
+ E = int((B - D)/30.6001)
+
+ # Convert to date
+ day = B - D - int(30.6001 * E) + F
+ nday = B-D-123
+ if nday <= 305:
+ dayofyr = nday+60
+ else:
+ dayofyr = nday-305
+ if E < 14:
+ month = E - 1
+ else:
+ month = E - 13
+
+ if month > 2:
+ year = C - 4716
+ else:
+ year = C - 4715
+
+ # a leap year?
+ leap = 0
+ if year % 4 == 0:
+ leap = 1
+ if calendar == 'proleptic_gregorian' or \
+ (calendar in ['standard','gregorian'] and JD >= 2299160.5):
+ if year % 100 == 0 and year % 400 != 0:
+ print year % 100, year % 400
+ leap = 0
+ if leap and month > 2:
+ dayofyr = dayofyr + leap
+
+ # Convert fractions of a day to time
+ (dfrac, days) = math.modf(day/1.0)
+ (hfrac, hours) = math.modf(dfrac * 24.0)
+ (mfrac, minutes) = math.modf(hfrac * 60.0)
+ seconds = round(mfrac * 60.0) # seconds are rounded
+
+ if seconds > 59:
+ seconds = 0
+ minutes = minutes + 1
+ if minutes > 59:
+ minutes = 0
+ hours = hours + 1
+ if hours > 23:
+ hours = 0
+ days = days + 1
+
+ # return a 'real' datetime instance if calendar is gregorian.
+ if calendar == 'proleptic_gregorian' or \
+ (calendar in ['standard','gregorian'] and JD >= 2299160.5):
+ return
real_datetime(year,month,int(days),int(hours),int(minutes),int(seconds))
+ else:
+ # or else, return a 'datetime-like' instance.
+ return
datetime(year,month,int(days),int(hours),int(minutes),int(seconds),dayofwk,dayofyr)
+
+def _DateFromNoLeapDay(JD):
+ """
+
+returns a 'datetime-like' object given Julian Day for a calendar with no leap
+days. Julian Day is a fractional day with a resolution of 1 second.
+
+ """
+
+ # based on redate.py by David Finlayson.
+
+ if JD < 0:
+ raise ValueError, 'Julian Day must be positive'
+
+ dayofwk = int(math.fmod(int(JD + 1.5),7))
+ (F, Z) = math.modf(JD + 0.5)
+ Z = int(Z)
+ A = Z
+ B = A + 1524
+ C = int((B - 122.1)/365.)
+ D = int(365. * C)
+ E = int((B - D)/30.6001)
+
+ # Convert to date
+ day = B - D - int(30.6001 * E) + F
+ nday = B-D-123
+ if nday <= 305:
+ dayofyr = nday+60
+ else:
+ dayofyr = nday-305
+ if E < 14:
+ month = E - 1
+ else:
+ month = E - 13
+
+ if month > 2:
+ year = C - 4716
+ else:
+ year = C - 4715
+
+ # Convert fractions of a day to time
+ (dfrac, days) = math.modf(day/1.0)
+ (hfrac, hours) = math.modf(dfrac * 24.0)
+ (mfrac, minutes) = math.modf(hfrac * 60.0)
+ seconds = round(mfrac * 60.0) # seconds are rounded
+
+ if seconds > 59:
+ seconds = 0
+ minutes = minutes + 1
+ if minutes > 59:
+ minutes = 0
+ hours = hours + 1
+ if hours > 23:
+ hours = 0
+ days = days + 1
+
+ return datetime(year,month,int(days),int(hours),int(minutes),int(seconds),
dayofwk, dayofyr)
+
+def _DateFromAllLeap(JD):
+ """
+
+returns a 'datetime-like' object given Julian Day for a calendar where all
+years have 366 days.
+Julian Day is a fractional day with a resolution of 1 second.
+
+ """
+
+ # based on redate.py by David Finlayson.
+
+ if JD < 0:
+ raise ValueError, 'Julian Day must be positive'
+
+ dayofwk = int(math.fmod(int(JD + 1.5),7))
+ (F, Z) = math.modf(JD + 0.5)
+ Z = int(Z)
+ A = Z
+ B = A + 1524
+ C = int((B - 122.1)/366.)
+ D = int(366. * C)
+ E = int((B - D)/30.6001)
+
+ # Convert to date
+ day = B - D - int(30.6001 * E) + F
+ nday = B-D-123
+ if nday <= 305:
+ dayofyr = nday+60
+ else:
+ dayofyr = nday-305
+ if E < 14:
+ month = E - 1
+ else:
+ month = E - 13
+ if month > 2:
+ dayofyr = dayofyr+1
+
+ if month > 2:
+ year = C - 4716
+ else:
+ year = C - 4715
+
+ # Convert fractions of a day to time
+ (dfrac, days) = math.modf(day/1.0)
+ (hfrac, hours) = math.modf(dfrac * 24.0)
+ (mfrac, minutes) = math.modf(hfrac * 60.0)
+ seconds = round(mfrac * 60.0) # seconds are rounded
+
+ if seconds > 59:
+ seconds = 0
+ minutes = minutes + 1
+ if minutes > 59:
+ minutes = 0
+ hours = hours + 1
+ if hours > 23:
+ hours = 0
+ days = days + 1
+
+ return datetime(year,month,int(days),int(hours),int(minutes),int(seconds),
dayofwk, dayofyr)
+
+def _DateFrom360Day(JD):
+ """
+
+returns a 'datetime-like' object given Julian Day for a calendar where all
+months have 30 days.
+Julian Day is a fractional day with a resolution of 1 second.
+
+ """
+
+ if JD < 0:
+ raise ValueError, 'Julian Day must be positive'
+
+ #jd = int(360. * (year + 4716)) + int(30. * (month - 1)) + day
+ (F, Z) = math.modf(JD)
+ year = int((Z-0.5)/360.) - 4716
+ dayofyr = JD - (year+4716)*360
+ month = int((dayofyr-0.5)/30)+1
+ day = dayofyr - (month-1)*30 + F
+
+ # Convert fractions of a day to time
+ (dfrac, days) = math.modf(day/1.0)
+ (hfrac, hours) = math.modf(dfrac * 24.0)
+ (mfrac, minutes) = math.modf(hfrac * 60.0)
+ seconds = round(mfrac * 60.0) # seconds are rounded
+
+ if seconds > 59:
+ seconds = 0
+ minutes = minutes + 1
+ if minutes > 59:
+ minutes = 0
+ hours = hours + 1
+ if hours > 23:
+ hours = 0
+ days = days + 1
+
+ return
datetime(year,month,int(days),int(hours),int(minutes),int(seconds),-1,
int(dayofyr))
+
+def _dateparse(timestr,format='%Y-%m-%d %H:%M:%S'):
+ """parse a string of the form time-units since yyyy-mm-dd hh:mm:ss
+ return a tuple (units, datetimeinstance)"""
+ timestr_split = timestr.split()
+ units = timestr_split[0].lower()
+ if units not in _units:
+ raise ValueError,"units must be one of 'seconds', 'minutes', 'hours'
or 'days' (or singular version of these), got '%s'" % units
+ if timestr_split[1].lower() != 'since':
+ raise ValueError,"no 'since' in unit_string"
+ # use strptime to parse the date string.
+ n = timestr.find('since')+6
+ year,month,day,hour,minute,second,daywk,dayyr,tz =
strptime(timestr[n:],format)
+ if dayyr == -1: dayyr=1 # must have valid day of year for strftime to work
+ return units, datetime(year, month, day, hour, minute, second, daywk,
dayyr)
+
+class utime:
+ """
+Performs conversions of netCDF time coordinate
+data to/from datetime objects.
+
+To initialize: C{t = utime(unit_string,format='%Y-%m-%d
%H:%M:%S',calendar='standard')}
+
+where
+
+B{C{unit_string}} is a string of the form
+C{'time-units since <format>'} defining the time units.
+
+B{C{format}} is a string describing a reference time. This string is converted
+to a year,month,day,hour,minute,second tuple by strptime. The default
+format is C{'%Y-%m-%d %H:%M:%S'}. See the C{time.strptime} docstring for other
+valid formats.
+
+Valid time-units are days, hours, minutes and seconds (the singular forms
+are also accepted). An example unit_string would be C{'hours
+since 0001-01-01 00:00:00'}.
+
+The B{C{calendar}} keyword describes the calendar used in the time
calculations.
+All the values currently defined in the U{CF metadata convention
+<http://www.cgd.ucar.edu/cms/eaton/cf-metadata/CF-1.0.html#time>} are
+accepted. The default is C{'standard'}, which corresponds to the mixed
+Gregorian/Julian calendar used by the C{udunits library}. Valid calendars
+are:
+
+C{'gregorian'} or C{'standard'} (default):
+
+Mixed Gregorian/Julian calendar as defined by udunits.
+
+C{'proleptic_gregorian'}:
+
+A Gregorian calendar extended to dates before 1582-10-15. That is, a year
+is a leap year if either (i) it is divisible by 4 but not by 100 or (ii)
+it is divisible by 400.
+
+C{'noleap'} or C{'365_day'}:
+
+Gregorian calendar without leap years, i.e., all years are 365 days long.
+all_leap or 366_day Gregorian calendar with every year being a leap year,
+i.e., all years are 366 days long.
+
+C{'360_day'}:
+
+All years are 360 days divided into 30 day months.
+
+C{'julian'}:
+
+Proleptic Julian calendar, extended to dates after 1582-10-5. A year is a
+leap year if it is divisible by 4.
+
+The C{L{num2date}} and C{L{date2num}} class methods can used to convert
datetime
+instances to/from the specified time units using the specified calendar.
+
+The datetime instances returned by C{num2date} are 'real' python datetime
+objects if the date falls in the Gregorian calendar (i.e.
+C{calendar='proleptic_gregorian', 'standard'} or C{'gregorian'} and
+the date is after 1582-10-15). Otherwise, they are 'phony' datetime
+objects which are actually instances of C{L{netcdftime.datetime}}. This is
+because the python datetime module cannot handle the weird dates in some
+calendars (such as C{'360_day'} and C{'all_leap'}) which don't exist in any
real
+world calendar.
+
+
+Example usage:
+
+>>> from netcdftime import utime
+>>> from datetime import datetime
+>>> cdftime = utime('hours since 0001-01-01 00:00:00')
+>>> date = datetime.now()
+>>> print date
+2006-03-17 16:04:02.561678
+>>>
+>>> t = cdftime.date2num(date)
+>>> print t
+17577328.0672
+>>>
+>>> date = cdftime.num2date(t)
+>>> print date
+2006-03-17 16:04:02
+>>>
+
+The resolution of the transformation operation is 1 second.
+
+Warning: Dates between 1582-10-5 and 1582-10-15 do not exist in the
+C{'standard'} or C{'gregorian'} calendars. An exception will be raised if you
pass
+a 'datetime-like' object in that range to the C{L{date2num}} class method.
+
+Words of Wisdom from the British MetOffice concerning reference dates
+U{http://www.metoffice.com/research/hadleycentre/models/GDT/ch26.html}:
+
+"udunits implements the mixed Gregorian/Julian calendar system, as
+followed in England, in which dates prior to 1582-10-15 are assumed to use
+the Julian calendar. Other software cannot be relied upon to handle the
+change of calendar in the same way, so for robustness it is recommended
+that the reference date be later than 1582. If earlier dates must be used,
+it should be noted that udunits treats 0 AD as identical to 1 AD."
+
[EMAIL PROTECTED] origin: datetime instance defining the origin of the netCDF
time variable.
[EMAIL PROTECTED] calendar: the calendar used (as specified by the C{calendar}
keyword).
[EMAIL PROTECTED] unit_string: a string defining the the netCDF time variable.
[EMAIL PROTECTED] units: the units part of C{unit_string} (i.e. 'days',
'hours', 'seconds').
+ """
+ def __init__(self,unit_string,format='%Y-%m-%d
%H:%M:%S',calendar='standard'):
+ """
[EMAIL PROTECTED] unit_string: a string of the form
+C{'time-units since <format>'} defining the time units.
+
[EMAIL PROTECTED] format: a string describing a reference time. This string is
converted
+to a year,month,day,hour,minute,second tuple by strptime. The default
+format is C{'%Y-%m-%d %H:%M:%S'}. See the C{time.strptime} docstring for other
+valid formats.
+Valid time-units are days, hours, minutes and seconds (the singular forms
+are also accepted). An example unit_string would be C{'hours
+since 0001-01-01 00:00:00'}.
+
[EMAIL PROTECTED] calendar: describes the calendar used in the time
calculations.
+All the values currently defined in the U{CF metadata convention
+<http://www.cgd.ucar.edu/cms/eaton/cf-metadata/CF-1.0.html#time>} are
+accepted. The default is C{'standard'}, which corresponds to the mixed
+Gregorian/Julian calendar used by the C{udunits library}. Valid calendars
+are:
+ - C{'gregorian'} or C{'standard'} (default):
+ Mixed Gregorian/Julian calendar as defined by udunits.
+ - C{'proleptic_gregorian'}:
+ A Gregorian calendar extended to dates before 1582-10-15. That is, a year
+ is a leap year if either (i) it is divisible by 4 but not by 100 or (ii)
+ it is divisible by 400.
+ - C{'noleap'} or C{'365_day'}:
+ Gregorian calendar without leap years, i.e., all years are 365 days long.
+ all_leap or 366_day Gregorian calendar with every year being a leap year,
+ i.e., all years are 366 days long.
+ -C{'360_day'}:
+ All years are 360 days divided into 30 day months.
+ -C{'julian'}:
+ Proleptic Julian calendar, extended to dates after 1582-10-5. A year is a
+ leap year if it is divisible by 4.
+
[EMAIL PROTECTED]: A class instance which may be used for converting times from
netCDF
+units to datetime objects.
+ """
+ if calendar in _calendars:
+ self.calendar = calendar
+ else:
+ raise ValueError, "calendar must be one of %s, got '%s'" %
(str(_calendars),calendar)
+ units, self.origin = _dateparse(unit_string,format=format)
+ self.units = units
+ self.unit_string = unit_string
+ if self.calendar in ['noleap','365_day'] and self.origin.month == 2
and self.origin.day == 29:
+ raise ValueError, 'cannot specify a leap day as the reference time
with the noleap calendar'
+ if self.calendar == '360_day' and self.origin.day > 30:
+ raise ValueError, 'there are only 30 days in every month with the
360_day calendar'
+ if self.calendar in ['noleap','365_day']:
+ self._jd0 = _NoLeapDayFromDate(self.origin)
+ elif self.calendar in ['all_leap','366_day']:
+ self._jd0 = _AllLeapFromDate(self.origin)
+ elif self.calendar == '360_day':
+ self._jd0 = _360DayFromDate(self.origin)
+ else:
+ self._jd0 = JulianDayFromDate(self.origin,calendar=self.calendar)
+
+ def date2num(self,date):
+ """
+Returns C{time_value} in units described by L{unit_string}, using
+the specified L{calendar}, given a 'datetime-like' object.
+
+Resolution is 1 second.
+
+If C{calendar = 'standard'} or C{'gregorian'} (indicating
+that the mixed Julian/Gregorian calendar is to be used), an
+exception will be raised if the 'datetime-like' object describes
+a date between 1582-10-5 and 1582-10-15.
+
+Works for scalars, sequences and numpy arrays.
+Returns a scalar if input is a scalar, else returns a numpy array.
+ """
+ isscalar = False
+ try:
+ date[0]
+ except:
+ isscalar = True
+ if not isscalar:
+ date = numpy.array(date)
+ shape = date.shape
+ if self.calendar in
['julian','standard','gregorian','proleptic_gregorian']:
+ if isscalar:
+ jdelta = JulianDayFromDate(date,self.calendar)-self._jd0
+ else:
+ jdelta = [JulianDayFromDate(d,self.calendar)-self._jd0 for d
in date.flat]
+ elif self.calendar in ['noleap','365_day']:
+ if date.month == 2 and date.day == 29:
+ raise ValueError, 'there is no leap day in the noleap calendar'
+ if isscalar:
+ jdelta = _NoLeapDayFromDate(date) - self._jd0
+ else:
+ jdelta = [_NoLeapDayFromDate(d)-self._jd0 for d in date.flat]
+ elif self.calendar in ['all_leap','366_day']:
+ if isscalar:
+ jdelta = _AllLeapFromDate(date) - self._jd0
+ else:
+ jdelta = [_AllLeapFromDate(d)-self._jd0 for d in date.flat]
+ elif self.calendar == '360_day':
+ if self.calendar == '360_day' and date.day > 30:
+ raise ValueError, 'there are only 30 days in every month with
the 360_day calendar'
+ if isscalar:
+ jdelta = _360DayFromDate(date) - self._jd0
+ else:
+ jdelta = [_360DayFromDate(d)-self._jd0 for d in date.flat]
+ if not isscalar:
+ jdelta = numpy.array(jdelta)
+ if self.units in ['second','seconds']:
+ jdelta = jdelta*86400.
+ elif self.units in ['minute','minutes']:
+ jdelta = jdelta*1440.
+ elif self.units in ['hours','hours']:
+ jdelta = jdelta*24.
+ if isscalar:
+ return jdelta
+ else:
+ return numpy.reshape(jdelta,shape)
+
+ def num2date(self,time_value):
+ """
+Return a 'datetime-like' object given a C{time_value} in units
+described by L{unit_string}, using L{calendar}.
+
+Resolution is 1 second.
+
+Works for scalars, sequences and numpy arrays.
+Returns a scalar if input is a scalar, else returns a numpy array.
+
+The datetime instances returned by C{num2date} are 'real' python datetime
+objects if the date falls in the Gregorian calendar (i.e.
+C{calendar='proleptic_gregorian'}, or C{calendar = 'standard'/'gregorian'} and
+the date is after 1582-10-15). Otherwise, they are 'phony' datetime
+objects which are actually instances of netcdftime.datetime. This is
+because the python datetime module cannot handle the weird dates in some
+calendars (such as C{'360_day'} and C{'all_leap'}) which don't exist in any
real
+world calendar.
+ """
+ isscalar = False
+ try:
+ time_value[0]
+ except:
+ isscalar = True
+ if not isscalar:
+ time_value = numpy.array(time_value)
+ shape = time_value.shape
+ if self.units in ['second','seconds']:
+ jdelta = time_value/86400.
+ elif self.units in ['minute','minutes']:
+ jdelta = time_value/1440.
+ elif self.units in ['hours','hours']:
+ jdelta = time_value/24.
+ elif self.units in ['day','days']:
+ jdelta = time_value
+ jd = self._jd0 + jdelta
+ if self.calendar in
['julian','standard','gregorian','proleptic_gregorian']:
+ if not isscalar:
+ date = [DateFromJulianDay(j,self.calendar) for j in jd.flat]
+ else:
+ date = DateFromJulianDay(jd,self.calendar)
+ elif self.calendar in ['noleap','365_day']:
+ if not isscalar:
+ date = [_DateFromNoLeapDay(j) for j in jd.flat]
+ else:
+ date = _DateFromNoLeapDay(jd)
+ elif self.calendar in ['all_leap','366_day']:
+ if not isscalar:
+ date = [_DateFromAllLeap(j) for j in jd.flat]
+ else:
+ date = _DateFromAllLeap(jd)
+ elif self.calendar == '360_day':
+ if not isscalar:
+ date = [_DateFrom360Day(j) for j in jd.flat]
+ else:
+ date = _DateFrom360Day(jd)
+ if isscalar:
+ return date
+ else:
+ return numpy.reshape(numpy.array(date),shape)
Added: trunk/toolkits/basemap/lib/netcdftime/strftime.py
===================================================================
--- trunk/toolkits/basemap/lib/netcdftime/strftime.py
(rev 0)
+++ trunk/toolkits/basemap/lib/netcdftime/strftime.py 2007-12-04 17:39:31 UTC
(rev 4580)
@@ -0,0 +1,66 @@
+# Format a datetime through its full proleptic Gregorian date range.
+#
+# >>> strftime(datetime.date(1850, 8, 2), "%Y/%M/%d was a %A")
+# '1850/00/02 was a Friday'
+# >>>
+
+import re, time
+
+# remove the unsupposed "%s" command. But don't
+# do it if there's an even number of %s before the s
+# because those are all escaped. Can't simply
+# remove the s because the result of
+# %sY
+# should be %Y if %s isn't supported, not the
+# 4 digit year.
+_illegal_s = re.compile(r"((^|[^%])(%%)*%s)")
+
+def _findall(text, substr):
+ # Also finds overlaps
+ sites = []
+ i = 0
+ while 1:
+ j = text.find(substr, i)
+ if j == -1:
+ break
+ sites.append(j)
+ i=j+1
+ return sites
+
+# Every 28 years the calendar repeats, except through century leap
+# years where it's 6 years. But only if you're using the Gregorian
+# calendar. ;)
+
+def strftime(dt, fmt):
+ if _illegal_s.search(fmt):
+ raise TypeError("This strftime implementation does not handle %s")
+ # don't use strftime method at all.
+ #if dt.year > 1900:
+ # return dt.strftime(fmt)
+
+ year = dt.year
+ # For every non-leap year century, advance by
+ # 6 years to get into the 28-year repeat cycle
+ delta = 2000 - year
+ off = 6*(delta // 100 + delta // 400)
+ year = year + off
+
+ # Move to around the year 2000
+ year = year + ((2000 - year)//28)*28
+ timetuple = dt.timetuple()
+ s1 = time.strftime(fmt, (year,) + timetuple[1:])
+ sites1 = _findall(s1, str(year))
+
+ s2 = time.strftime(fmt, (year+28,) + timetuple[1:])
+ sites2 = _findall(s2, str(year+28))
+
+ sites = []
+ for site in sites1:
+ if site in sites2:
+ sites.append(site)
+
+ s = s1
+ syear = "%4d" % (dt.year,)
+ for site in sites:
+ s = s[:site] + syear + s[site+4:]
+ return s
Added: trunk/toolkits/basemap/lib/netcdftime/strptime.py
===================================================================
--- trunk/toolkits/basemap/lib/netcdftime/strptime.py
(rev 0)
+++ trunk/toolkits/basemap/lib/netcdftime/strptime.py 2007-12-04 17:39:31 UTC
(rev 4580)
@@ -0,0 +1,438 @@
+"""Implementation of time.strptime().
+
+This version was written to work in Jython 2.1 . If you are running CPython
+2.2.x or newer, please use the version of Modules/timemodule.c and
+Lib/_strptime.py as found in CVS for CPython 2.3.x (CPython 2.2.0 requires
+defining ``True = 1; False = 0``).
+
+The main changes from the version in CVS for CPython 2.3.x is a caching
+mechanism which improves performance dramatically. LocaleTime().timezone is
+also a true set and thus does not require all the tweaking of using a version
+of Python lacking iterators. List comprehensions were also removed from the
+code. All date-calculating code was made conditional based on requiring
+datetime. If you need the calculations you can find the code in the Python CVS
+repository in the datetime code or in the dead-tree version of the 'Python
+Cookbook' under the recipe for strptime() (last recipe in the book).
+Subclassing of object and dict were also removed.
+
+$Last Edit: 2003-09-15 $
+
+"""
+import time
+try:
+ import locale
+except ImportError:
+ class FakeLocale:
+
+ """Faked locale module (for Jython compatibility)."""
+
+ LC_TIME = None
+
+ def getlocale(self, whatever):
+ return (whatever, whatever)
+
+ locale = FakeLocale()
+
+import calendar
+from re import compile as re_compile
+from re import IGNORECASE
+# Get datetime from Python's CVS: /python/nondist/sandbox/datetime/
+#try:
+# from datetime import date as datetime_date
+#except ImportError:
+datetime_date = None
+from thread import allocate_lock as _thread_allocate_lock
+
+__author__ = "Brett Cannon"
+__email__ = "[EMAIL PROTECTED]"
+
+__all__ = ['strptime']
+
+# ----- START Code to replace Python 2.3 functionality -----
+True, False = 1, 0
+
+def sets_ImmutableSet(iterable):
+ sets_dict = {}
+ for item in iterable:
+ sets_dict[item] = None
+ return sets_dict
+
+def enumerate(iterable):
+ """Python 2.1-compatible enumerate function.
+
+ """
+ enum_list = []
+ count = 0
+ for item in iterable:
+ enum_list.append((count, item))
+ count += 1
+ return enum_list
+
+# ----- END Code to replace Python 2.3 functionality -----
+
+def _getlang():
+ # Figure out what the current language is set to.
+ return locale.getlocale(locale.LC_TIME)
+
+class LocaleTime:
+ """Stores and handles locale-specific information related to time.
+
+ ATTRIBUTES:
+ f_weekday -- full weekday names (7-item list)
+ a_weekday -- abbreviated weekday names (7-item list)
+ f_month -- full month names (13-item list; dummy value in [0], which
+ is added by code)
+ a_month -- abbreviated month names (13-item list, dummy value in
+ [0], which is added by code)
+ am_pm -- AM/PM representation (2-item list)
+ LC_date_time -- format string for date/time representation (string)
+ LC_date -- format string for date representation (string)
+ LC_time -- format string for time representation (string)
+ timezone -- daylight- and non-daylight-savings timezone representation
+ (2-item list of sets)
+ lang -- Language used by instance (2-item tuple)
+ """
+
+ def __init__(self):
+ """Set all attributes.
+
+ Order of methods called matters for dependency reasons.
+
+ The locale language is set at the offset and then checked again before
+ exiting. This is to make sure that the attributes were not set with a
+ mix of information from more than one locale. This would most likely
+ happen when using threads where one thread calls a locale-dependent
+ function while another thread changes the locale while the function in
+ the other thread is still running. Proper coding would call for
+ locks to prevent changing the locale while locale-dependent code is
+ running. The check here is done in case someone does not think about
+ doing this.
+
+ Only other possible issue is if someone changed the timezone and did
+ not call tz.tzset . That is an issue for the programmer, though,
+ since changing the timezone is worthless without that call.
+
+ """
+ self.lang = _getlang()
+ self.__calc_weekday()
+ self.__calc_month()
+ self.__calc_am_pm()
+ self.__calc_timezone()
+ self.__calc_date_time()
+ if _getlang() != self.lang:
+ raise ValueError("locale changed during initialization")
+
+ def __pad(self, seq, front):
+ # Add '' to seq to either the front (is True), else the back.
+ seq = list(seq)
+ if front:
+ seq.insert(0, '')
+ else:
+ seq.append('')
+ return seq
+
+ def __calc_weekday(self):
+ # Set self.a_weekday and self.f_weekday using the calendar
+ # module.
+ a_weekday = [calendar.day_abbr[i].lower() for i in range(7)]
+ f_weekday = [calendar.day_name[i].lower() for i in range(7)]
+ self.a_weekday = a_weekday
+ self.f_weekday = f_weekday
+
+ def __calc_month(self):
+ # Set self.f_month and self.a_month using the calendar module.
+ a_month = [calendar.month_abbr[i].lower() for i in range(13)]
+ f_month = [calendar.month_name[i].lower() for i in range(13)]
+ self.a_month = a_month
+ self.f_month = f_month
+
+ def __calc_am_pm(self):
+ # Set self.am_pm by using time.strftime().
+
+ # The magic date (1999,3,17,hour,44,55,2,76,0) is not really that
+ # magical; just happened to have used it everywhere else where a
+ # static date was needed.
+ am_pm = []
+ for hour in (01,22):
+ time_tuple = (1999,3,17,hour,44,55,2,76,0)
+ am_pm.append(time.strftime("%p", time_tuple).lower())
+ self.am_pm = am_pm
+
+ def __calc_date_time(self):
+ # Set self.date_time, self.date, & self.time by using
+ # time.strftime().
+
+ # Use (1999,3,17,22,44,55,2,76,0) for magic date because the amount of
+ # overloaded numbers is minimized. The order in which searches for
+ # values within the format string is very important; it eliminates
+ # possible ambiguity for what something represents.
+ time_tuple = (1999,3,17,22,44,55,2,76,0)
+ date_time = [None, None, None]
+ date_time[0] = time.strftime("%c", time_tuple).lower()
+ date_time[1] = time.strftime("%x", time_tuple).lower()
+ date_time[2] = time.strftime("%X", time_tuple).lower()
+ replacement_pairs = [('%', '%%'), (self.f_weekday[2], '%A'),
+ (self.f_month[3], '%B'), (self.a_weekday[2], '%a'),
+ (self.a_month[3], '%b'), (self.am_pm[1], '%p'),
+ ('1999', '%Y'), ('99', '%y'), ('22', '%H'),
+ ('44', '%M'), ('55', '%S'), ('76', '%j'),
+ ('17', '%d'), ('03', '%m'), ('3', '%m'),
+ # '3' needed for when no leading zero.
+ ('2', '%w'), ('10', '%I')]
+ for tz_values in self.timezone:
+ for tz in tz_values.keys():
+ replacement_pairs.append((tz, "%Z"))
+ for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')):
+ current_format = date_time[offset]
+ for old, new in replacement_pairs:
+ # Must deal with possible lack of locale info
+ # manifesting itself as the empty string (e.g., Swedish's
+ # lack of AM/PM info) or a platform returning a tuple of empty
+ # strings (e.g., MacOS 9 having timezone as ('','')).
+ if old:
+ current_format = current_format.replace(old, new)
+ time_tuple = (1999,1,3,1,1,1,6,3,0)
+ if time.strftime(directive, time_tuple).find('00'):
+ U_W = '%U'
+ else:
+ U_W = '%W'
+ date_time[offset] = current_format.replace('11', U_W)
+ self.LC_date_time = date_time[0]
+ self.LC_date = date_time[1]
+ self.LC_time = date_time[2]
+
+ def __calc_timezone(self):
+ # Set self.timezone by using time.tzname.
+ # Do not worry about possibility of time.tzname[0] == timetzname[1]
+ # and time.daylight; handle that in strptime .
+ try:
+ time.tzset()
+ except AttributeError:
+ pass
+ no_saving = sets_ImmutableSet(["utc", "gmt", time.tzname[0].lower()])
+ if time.daylight:
+ has_saving = sets_ImmutableSet([time.tzname[1].lower()])
+ else:
+ has_saving = sets_ImmutableSet()
+ self.timezone = (no_saving, has_saving)
+
+
+import UserDict
+class TimeRE(UserDict.UserDict):
+ """Handle conversion from format directives to regexes."""
+
+ def __init__(self, locale_time=None):
+ """Create keys/values.
+
+ Order of execution is important for dependency reasons.
+
+ """
+ if locale_time:
+ self.locale_time = locale_time
+ else:
+ self.locale_time = LocaleTime()
+ base = UserDict.UserDict
+ base.__init__(self, {
+ # The " \d" part of the regex is to make %c from ANSI C work
+ 'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
+ 'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
+ 'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])",
+'j':
r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])",
+ 'm': r"(?P<m>1[0-2]|0[1-9]|[1-9])",
+ 'M': r"(?P<M>[0-5]\d|\d)",
+ 'S': r"(?P<S>6[0-1]|[0-5]\d|\d)",
+ 'U': r"(?P<U>5[0-3]|[0-4]\d|\d)",
+ 'w': r"(?P<w>[0-6])",
+ # W is set below by using 'U'
+ 'y': r"(?P<y>\d\d)",
+ #XXX: Does 'Y' need to worry about having less or more than
+ # 4 digits?
+ 'Y': r"(?P<Y>\d\d\d\d)",
+ 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
+ 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
+ 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
+ 'b': self.__seqToRE(self.locale_time.a_month[1:], 'b'),
+ 'p': self.__seqToRE(self.locale_time.am_pm, 'p'),
+ '%': '%'})
+ temp_list = []
+ for tz_names in self.locale_time.timezone:
+ for tz in tz_names.keys():
+ temp_list.append(tz)
+ base.__setitem__(self, 'Z', self.__seqToRE(temp_list, 'Z'))
+ base.__setitem__(self, 'W', base.__getitem__(self, 'U'))
+ base.__setitem__(self, 'c',
self.pattern(self.locale_time.LC_date_time))
+ base.__setitem__(self, 'x', self.pattern(self.locale_time.LC_date))
+ base.__setitem__(self, 'X', self.pattern(self.locale_time.LC_time))
+
+ def __seqToRE(self, to_convert, directive):
+ """Convert a list to a regex string for matching a directive.
+
+ Want possible matching values to be from longest to shortest. This
+ prevents the possibility of a match occuring for a value that also
+ a substring of a larger value that should have matched (e.g., 'abc'
+ matching when 'abcdef' should have been the match).
+
+ """
+ for value in to_convert:
+ if value != '':
+ break
+ else:
+ return ''
+ to_sort = [(len(item), item) for item in to_convert]
+ to_sort.sort()
+ to_sort.reverse()
+ to_convert = [item for length, item in to_sort]
+ regex = '|'.join(to_convert)
+ regex = '(?P<%s>%s' % (directive, regex)
+ return '%s)' % regex
+
+ def pattern(self, format):
+ """Return regex pattern for the format string.
+
+ Need to make sure that any characters that might be interpreted as
+ regex syntax are escaped.
+
+ """
+ processed_format = ''
+ # The sub() call escapes all characters that might be misconstrued
+ # as regex syntax.
+ regex_chars = re_compile(r"([\\.^$*+?{}\[\]|])")
+ format = regex_chars.sub(r"\\\1", format)
+ whitespace_replacement = re_compile('\s+')
+ format = whitespace_replacement.sub('\s*', format)
+ while format.find('%') != -1:
+ directive_index = format.index('%')+1
+ processed_format = "%s%s%s" % (processed_format,
+ format[:directive_index-1],
+ self[format[directive_index]])
+ format = format[directive_index+1:]
+ return "%s%s" % (processed_format, format)
+
+ def compile(self, format):
+ """Return a compiled re object for the format string."""
+ return re_compile(self.pattern(format), IGNORECASE)
+
+_cache_lock = _thread_allocate_lock()
+# DO NOT modify _TimeRE_cache or _regex_cache without acquiring the cache lock
+# first!
+_TimeRE_cache = TimeRE()
+_CACHE_MAX_SIZE = 5 # Max number of regexes stored in _regex_cache
+_regex_cache = {}
+
+def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
+ """Return a time struct based on the input string and the format string."""
+ global _TimeRE_cache
+ _cache_lock.acquire()
+ try:
+ time_re = _TimeRE_cache
+ locale_time = time_re.locale_time
+ if _getlang() != locale_time.lang:
+ _TimeRE_cache = TimeRE()
+ if len(_regex_cache) > _CACHE_MAX_SIZE:
+ _regex_cache.clear()
+ format_regex = _regex_cache.get(format)
+ if not format_regex:
+ format_regex = time_re.compile(format)
+ _regex_cache[format] = format_regex
+ finally:
+ _cache_lock.release()
+ found = format_regex.match(data_string)
+ if not found:
+ raise ValueError("time data did not match format: data=%s fmt=%s" %
+ (data_string, format))
+ if len(data_string) != found.end():
+ raise ValueError("unconverted data remains: %s" %
+ data_string[found.end():])
+ year = 1900
+ month = day = 1
+ hour = minute = second = 0
+ tz = -1
+ # weekday and julian defaulted to -1 so as to signal need to calculate
values
+ weekday = julian = -1
+ found_dict = found.groupdict()
+ for group_key in found_dict.keys():
+ if group_key == 'y':
+ year = int(found_dict['y'])
+ # Open Group specification for strptime() states that a %y
+ #value in the range of [00, 68] is in the century 2000, while
+ #[69,99] is in the century 1900
+ if year <= 68:
+ year += 2000
+ else:
+ year += 1900
+ elif group_key == 'Y':
+ year = int(found_dict['Y'])
+ elif group_key == 'm':
+ month = int(found_dict['m'])
+ elif group_key == 'B':
+ month = locale_time.f_month.index(found_dict['B'].lower())
+ elif group_key == 'b':
+ month = locale_time.a_month.index(found_dict['b'].lower())
+ elif group_key == 'd':
+ day = int(found_dict['d'])
+ elif group_key == 'H':
+ hour = int(found_dict['H'])
+ elif group_key == 'I':
+ hour = int(found_dict['I'])
+ ampm = found_dict.get('p', '').lower()
+ # If there was no AM/PM indicator, we'll treat this like AM
+ if ampm in ('', locale_time.am_pm[0]):
+ # We're in AM so the hour is correct unless we're
+ # looking at 12 midnight.
+ # 12 midnight == 12 AM == hour 0
+ if hour == 12:
+ hour = 0
+ elif ampm == locale_time.am_pm[1]:
+ # We're in PM so we need to add 12 to the hour unless
+ # we're looking at 12 noon.
+ # 12 noon == 12 PM == hour 12
+ if hour != 12:
+ hour += 12
+ elif group_key == 'M':
+ minute = int(found_dict['M'])
+ elif group_key == 'S':
+ second = int(found_dict['S'])
+ elif group_key == 'A':
+ weekday = locale_time.f_weekday.index(found_dict['A'].lower())
+ elif group_key == 'a':
+ weekday = locale_time.a_weekday.index(found_dict['a'].lower())
+ elif group_key == 'w':
+ weekday = int(found_dict['w'])
+ if weekday == 0:
+ weekday = 6
+ else:
+ weekday -= 1
+ elif group_key == 'j':
+ julian = int(found_dict['j'])
+ elif group_key == 'Z':
+ # Since -1 is default value only need to worry about setting tz if
+ # it can be something other than -1.
+ found_zone = found_dict['Z'].lower()
+ for value, tz_values in enumerate(locale_time.timezone):
+ if found_zone in tz_values:
+ # Deal with bad locale setup where timezone names are the
+ # same and yet time.daylight is true; too ambiguous to
+ # be able to tell what timezone has daylight savings
+ if time.tzname[0] == time.tzname[1] and \
+ time.daylight:
+ break
+ else:
+ tz = value
+ break
+ # Cannot pre-calculate datetime_date() since can change in Julian
+ #calculation and thus could have different value for the day of the week
+ #calculation
+ if datetime_date:
+ if julian == -1:
+ # Need to add 1 to result since first day of the year is 1, not 0.
+ julian = datetime_date(year, month, day).toordinal() - \
+ datetime_date(year, 1, 1).toordinal() + 1
+ else: # Assume that if they bothered to include Julian day it will
+ #be accurate
+ datetime_result = datetime_date.fromordinal((julian - 1) +
datetime_date(year, 1, 1).toordinal())
+ year = datetime_result.year
+ month = datetime_result.month
+ day = datetime_result.day
+ if weekday == -1:
+ weekday = datetime_date(year, month, day).weekday()
+ return (year, month, day, hour, minute, second, weekday, julian, tz)
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
SF.Net email is sponsored by: The Future of Linux Business White Paper
from Novell. From the desktop to the data center, Linux is going
mainstream. Let it simplify your IT future.
http://altfarm.mediaplex.com/ad/ck/8857-50307-18918-4
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins