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

Reply via email to