[Scipy-svn] r2809 - trunk/Lib/sandbox/timeseries

scipy-svn@scip... scipy-svn@scip...
Fri Mar 2 02:49:32 CST 2007


Author: pierregm
Date: 2007-03-02 02:49:29 -0600 (Fri, 02 Mar 2007)
New Revision: 2809

Added:
   trunk/Lib/sandbox/timeseries/tdates.py
Log:
tdates : oops, it disappeared...

Added: trunk/Lib/sandbox/timeseries/tdates.py
===================================================================
--- trunk/Lib/sandbox/timeseries/tdates.py	2007-03-02 08:48:36 UTC (rev 2808)
+++ trunk/Lib/sandbox/timeseries/tdates.py	2007-03-02 08:49:29 UTC (rev 2809)
@@ -0,0 +1,1064 @@
+"""
+Classes definition for the support of individual dates and array of dates.
+
+:author: Pierre GF Gerard-Marchant & Matt Knox
+:contact: pierregm_at_uga_dot_edu - mattknox_ca_at_hotmail_dot_com
+:version: $Id$
+"""
+__author__ = "Pierre GF Gerard-Marchant & Matt Knox ($Author$)"
+__version__ = '1.0'
+__revision__ = "$Revision$"
+__date__     = '$Date$'
+
+import datetime as dt
+
+import itertools
+import warnings
+import types
+
+
+import numpy
+from numpy import bool_, float_, int_, object_
+from numpy import ndarray
+import numpy.core.numeric as numeric
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numerictypes as ntypes
+from numpy.core.numerictypes import generic
+
+import maskedarray as MA
+
+try:
+    from mx.DateTime import DateTimeType
+except ImportError:
+    class DateTimeType: pass
+
+from parser import DateFromString, DateTimeFromString    
+
+import tcore as corelib
+import cseries
+
+
+
+__all__ = [
+'Date', 'DateArray','isDate','isDateArray',
+'DateError', 'ArithmeticDateError', 'FrequencyDateError','InsufficientDateError',
+'datearray','date_array', 'date_array_fromlist', 'date_array_fromrange',
+'day_of_week','day_of_year','day','month','quarter','year','hour','minute','second',
+'truncateDate','monthToQuarter','thisday','today','prevbusday','asfreq',
+'period_break'
+           ]
+
+
+#####---------------------------------------------------------------------------
+#---- --- Date Info ---
+#####---------------------------------------------------------------------------
+
+OriginDate = dt.datetime(1970, 1, 1)
+secondlyOriginDate = OriginDate - dt.timedelta(seconds=1)
+minutelyOriginDate = OriginDate - dt.timedelta(minutes=1)
+hourlyOriginDate = OriginDate - dt.timedelta(hours=1)
+
+
+#####---------------------------------------------------------------------------
+#---- --- Date Exceptions ---
+#####---------------------------------------------------------------------------
+class DateError(Exception):
+    """Defines a generic DateArrayError."""
+    def __init__ (self, args=None):
+        "Create an exception"
+        Exception.__init__(self)
+        self.args = args
+    def __str__(self):
+        "Calculate the string representation"
+        return str(self.args)
+    __repr__ = __str__
+    
+class InsufficientDateError(DateError):
+    """Defines the exception raised when there is not enough information 
+    to create a Date object."""
+    def __init__(self, msg=None):
+        if msg is None:
+            msg = "Insufficient parameters given to create a date at the given frequency"
+        DateError.__init__(self, msg)
+        
+class FrequencyDateError(DateError):
+    """Defines the exception raised when the frequencies are incompatible."""
+    def __init__(self, msg, freql=None, freqr=None):
+        msg += " : Incompatible frequencies!"
+        if not (freql is None or freqr is None):
+            msg += " (%s<>%s)" % (freql, freqr)
+        DateError.__init__(self, msg)
+        
+class ArithmeticDateError(DateError):
+    """Defines the exception raised when dates are used in arithmetic expressions."""
+    def __init__(self, msg=''):
+        msg += " Cannot use dates for arithmetics!"
+        DateError.__init__(self, msg)
+
+#####---------------------------------------------------------------------------
+#---- --- Date Class ---
+#####---------------------------------------------------------------------------
+
+class Date:
+    """Defines a Date object, as the combination of a date and a frequency.
+    Several options are available to construct a Date object explicitly:
+
+    - Give appropriate values to the `year`, `month`, `day`, `quarter`, `hours`, 
+      `minutes`, `seconds` arguments.
+      
+      >>> td.Date(freq='Q',year=2004,quarter=3)
+      >>> td.Date(freq='D',year=2001,month=1,day=1)
+      
+    - Use the `string` keyword. This method calls the `mx.DateTime.Parser`
+      submodule, more information is available in its documentation.
+      
+      >>> ts.Date('D', '2007-01-01')
+      
+    - Use the `datetime` keyword with an existing datetime.datetime object.
+
+      >>> td.Date('D', datetime=datetime.datetime.now())
+      """
+    default_fmtstr = {'A': "%Y",
+                      'Q': "%YQ%q",
+                      'M': "%b-%Y",
+                      'W': "%d-%b-%Y",
+                      'B': "%d-%b-%Y",
+                      'D': "%d-%b-%Y",
+                      'U': "%d-%b-%Y",
+                      'H': "%d-%b-%Y %H:00",
+                      'T': "%d-%b-%Y %H:%M",
+                      'S': "%d-%b-%Y %H:%M:%S"
+                      }
+      
+    def __init__(self, freq, value=None, string=None,
+                 year=None, month=None, day=None, quarter=None, 
+                 hour=None, minute=None, second=None, 
+                 datetime=None):
+        
+        if hasattr(freq, 'freq'):
+            self.freq = corelib.check_freq(freq.freq)
+        else:
+            self.freq = corelib.check_freq(freq)
+        self.freqstr = corelib.freq_tostr(self.freq)
+        
+        
+        if value is not None:
+            if isinstance(value, ndarray):
+                value = int(value)
+        
+            if isinstance(value, str):
+                if self.freqstr in ('H', 'T', 'S'):
+                    self.datetime = DateTimeFromString(value)
+                else:
+                    self.datetime = DateFromString(value)
+            elif self.freqstr == 'A':
+                self.datetime = dt.datetime(value, 1, 1)
+            elif self.freqstr == 'B':
+                valtmp = (value - 1)//5
+                self.datetime = dt.datetime.fromordinal(value + valtmp*2)
+            elif self.freqstr in ['D','U']:
+                self.datetime = dt.datetime.fromordinal(value)
+            elif self.freqstr == 'H':
+                self.datetime = hourlyOriginDate + dt.timedelta(hours=value)
+            elif self.freqstr == 'M':
+                year = (value - 1)//12 + 1
+                month = value - (year - 1)*12
+                self.datetime = dt.datetime(year, month, 1)
+            elif self.freqstr == 'Q':
+                year = (value - 1)//4 + 1
+                month = (value - (year - 1)*4)*3
+                self.datetime = dt.datetime(year, month, 1)
+            elif self.freqstr == 'S':
+                self.datetime = secondlyOriginDate + dt.timedelta(seconds=value)
+            elif self.freqstr == 'T':
+                self.datetime = minutelyOriginDate + dt.timedelta(minutes=value)
+            elif self.freqstr == 'W':
+                self.datetime = dt.datetime(1,1,7) + \
+                                dt.timedelta(days=(value-1)*7)
+        
+        elif string is not None:
+            if self.freqstr in ('H', 'T', 'S'):
+                self.datetime = DateTimeFromString(string)
+            else:
+                self.datetime = DateFromString(string)
+            
+        elif datetime is not None:
+            if isinstance(datetime, DateTimeType):
+                datetime = mx_to_datetime(datetime)
+            self.datetime = truncateDate(self.freq, datetime)
+            
+        else:
+            # First, some basic checks.....
+            if year is None:
+                raise InsufficientDateError            
+            if self.freqstr in 'BDWU':
+                if month is None or day is None: 
+                    raise InsufficientDateError
+            elif self.freqstr == 'M':
+                if month is None: 
+                    raise InsufficientDateError
+                day = 1
+            elif self.freqstr == 'Q':
+                if quarter is None: 
+                    raise InsufficientDateError
+                month = quarter * 3
+                day = 1
+            elif self.freqstr == 'A':
+                month = 1
+                day = 1
+            elif self.freqstr == 'S':
+                if month is None or day is None or second is None: 
+                    raise InsufficientDateError
+                
+            if self.freqstr in ['A','B','D','M','Q','W']:
+                self.datetime = truncateDate(self.freq, dt.datetime(year, month, day))
+                if self.freqstr == 'B':
+                    if self.datetime.isoweekday() in [6,7]:
+                        raise ValueError("Weekend passed as business day")
+            elif self.freqstr in 'HTS':
+                if hour is None:
+                    if minute is None:
+                        if second is None:
+                            hour = 0
+                        else:
+                            hour = second//3600
+                    else:
+                        hour = minute // 60
+                if minute is None:
+                    if second is None:
+                        minute = 0
+                    else:
+                        minute = (second-hour*3600)//60
+                if second is None:
+                    second = 0
+                else:
+                    second = second % 60
+                self.datetime = truncateDate(self.freqstr,
+                                             dt.datetime(year, month, day, 
+                                                         hour, minute, second))
+        self.value = self.__value()
+
+    def __getitem__(self, indx):
+        return self
+
+    @property
+    def day(self):          
+        "Returns the day of month."
+        return self.__getdateinfo__('D')
+    @property
+    def day_of_week(self):  
+        "Returns the day of week."
+        return self.__getdateinfo__('W')
+    @property
+    def day_of_year(self):  
+        "Returns the day of year."
+        return self.__getdateinfo__('R')
+    @property
+    def month(self):        
+        "Returns the month."
+        return self.__getdateinfo__('M')
+    @property
+    def quarter(self):   
+        "Returns the quarter."   
+        return self.__getdateinfo__('Q')
+    @property
+    def year(self):         
+        "Returns the year."
+        return self.__getdateinfo__('Y')
+    @property
+    def second(self):    
+        "Returns the seconds."  
+        return self.__getdateinfo__('S')
+    @property
+    def minute(self):     
+        "Returns the minutes."  
+        return self.__getdateinfo__('T')
+    @property
+    def hour(self):         
+        "Returns the hour."
+        return self.__getdateinfo__('H')
+    @property
+    def week(self):
+        "Returns the week."
+        return self.__getdateinfo__('I')
+        
+    def __getdateinfo__(self, info):
+        return int(cseries.getDateInfo(numpy.asarray(self.value), 
+                                       self.freq, info))
+    __getDateInfo = __getdateinfo__
+ 
+    def __add__(self, other):
+        if isinstance(other, Date):
+            raise FrequencyDateError("Cannot add dates", 
+                                     self.freqstr, other.freqstr)
+        return Date(freq=self.freq, value=int(self) + other)
+    
+    def __radd__(self, other): 
+        return self+other
+    
+    def __sub__(self, other):
+        if isinstance(other, Date):
+            if self.freq != other.freq:
+                raise FrequencyDateError("Cannot subtract dates", \
+                                         self.freqstr, other.freqstr)
+            else:
+                return int(self) - int(other) 
+        else:
+            return self + (-1) * int(other)
+    
+    def __eq__(self, other):
+        if not hasattr(other, 'freq'):
+            return False
+        elif self.freq != other.freq:
+            raise FrequencyDateError("Cannot compare dates", \
+                                     self.freqstr, other.freqstr)
+        return int(self) == int(other) 
+    
+    def __cmp__(self, other): 
+        if not hasattr(other, 'freq'):
+            return False
+        elif self.freq != other.freq:
+            raise FrequencyDateError("Cannot compare dates", \
+                                     self.freqstr, other.freqstr)
+        return int(self)-int(other)    
+        
+    def __hash__(self): 
+        return hash(int(self)) ^ hash(self.freq)
+    
+    def __int__(self):
+        return self.value
+    
+    def __float__(self):
+        return float(self.value)
+    
+    def __value(self):   
+        "Converts the date to an integer, depending on the current frequency."
+        # Annual .......
+        if self.freqstr == 'A':
+            val = self.datetime.year
+        # Quarterly.....
+        elif self.freqstr == 'Q':
+            val = (self.datetime.year-1)*4 + self.datetime.month//3
+        # Monthly.......
+        elif self.freqstr == 'M':
+            val = (self.datetime.year-1)*12 + self.datetime.month
+        # Weekly........
+        elif self.freqstr == 'W':
+            val = self.datetime.toordinal()//7
+        # Business days.
+        elif self.freqstr == 'B':
+            days = self.datetime.toordinal()
+            weeks = days // 7
+            val = days - weeks*2  
+        # Daily/undefined
+        elif self.freqstr in 'DU':
+            val = self.datetime.toordinal()
+        # Hourly........
+        elif self.freqstr == 'H':
+            delta = (self.datetime - hourlyOriginDate)
+            val = delta.days*24 + delta.seconds/(3600)
+        # Minutely......
+        elif self.freqstr == 'T':
+            delta = (self.datetime - minutelyOriginDate)
+            val = delta.days*1440 + delta.seconds/(60)
+        # Secondly......
+        elif self.freqstr == 'S':
+            delta = (self.datetime - secondlyOriginDate)
+            val = delta.days*86400 + delta.seconds
+        return int(val)
+    #......................................................        
+    def strfmt(self, fmt):
+        "Formats the date"
+        if fmt is None:
+            fmt = self.default_fmtstr[self.freqstr]
+        return cseries.strfmt(self.datetime, fmt)
+            
+    def __str__(self):
+        return self.strfmt(self.default_fmtstr[self.freqstr])
+
+    def __repr__(self): 
+        return "<%s : %s>" % (str(self.freqstr), str(self))
+    #......................................................
+    def toordinal(self):
+        "Returns the date as an ordinal."
+        return self.datetime.toordinal()
+
+    def fromordinal(self, ordinal):
+        "Returns the date as an ordinal."
+        return Date(self.freq, datetime=dt.datetime.fromordinal(ordinal))
+    
+    def tostring(self):
+        "Returns the date as a string."
+        return str(self)
+    
+    def toobject(self):
+        "Returns the date as itself."
+        return self
+    
+    def isvalid(self):
+        "Returns whether the DateArray is valid: no missing/duplicated dates."
+        # A date is always valid by itself, but we need the object to support the function
+        # when we're working with singletons.
+        return True
+    #......................................................
+        
+    
+#####---------------------------------------------------------------------------
+#---- --- Functions ---
+#####---------------------------------------------------------------------------
+
+def mx_to_datetime(mxDate):
+    microsecond = 1000000*(mxDate.second % 1)
+    return dt.datetime(mxDate.year, mxDate.month,
+                       mxDate.day, mxDate.hour,
+                       mxDate.minute,
+                       int(mxDate.second), microsecond)
+
+
+def truncateDate(freq, datetime):
+    "Chops off the irrelevant information from the datetime object passed in."
+    freqstr = corelib.check_freqstr(freq)
+    if freqstr == 'A':
+        return dt.datetime(datetime.year, 1, 1)
+    elif freqstr == 'Q':
+        return dt.datetime(datetime.year, monthToQuarter(datetime.month)*3, 1)
+    elif freqstr == 'M':
+        return dt.datetime(datetime.year, datetime.month, 1)
+    elif freqstr == 'W':
+        d = datetime.toordinal()
+        return dt.datetime.fromordinal(d + (7 - d % 7) % 7)
+    elif freqstr in 'BD':
+        if freq == 'B' and datetime.isoweekday() in (6,7):
+            raise ValueError("Weekend passed as business day")
+        return dt.datetime(datetime.year, datetime.month, datetime.day)
+    elif freqstr == 'H':
+        return dt.datetime(datetime.year, datetime.month, datetime.day, \
+                           datetime.hour)
+    elif freqstr == 'T':
+        return dt.datetime(datetime.year, datetime.month, datetime.day, \
+                           datetime.hour, datetime.minute)
+    else:
+        return datetime
+    
+def monthToQuarter(monthNum):
+    """Returns the quarter corresponding to the month `monthnum`.
+    For example, December is the 4th quarter, Januray the first."""
+    return int((monthNum-1)/3)+1
+
+def thisday(freq):
+    "Returns today's date, at the given frequency `freq`."
+    freqstr = corelib.check_freqstr(freq)
+    tempDate = dt.datetime.now()
+    # if it is Saturday or Sunday currently, freq==B, then we want to use Friday
+    if freqstr == 'B' and tempDate.isoweekday() >= 6:
+        tempDate = tempDate - dt.timedelta(days=(tempDate.isoweekday() - 5))
+    if freqstr in ('B','D','H','S','T','W','U'):
+        return Date(freq, datetime=tempDate)
+    elif freqstr == 'M':
+        return Date(freq, year=tempDate.year, month=tempDate.month)
+    elif freqstr == 'Q':
+        return Date(freq, year=tempDate.year, quarter=monthToQuarter(tempDate.month))
+    elif freqstr == 'A':
+        return Date(freq, year=tempDate.year)
+today = thisday
+
+def prevbusday(day_end_hour=18, day_end_min=0):
+    "Returns the previous business day."
+    tempDate = dt.datetime.now()
+    dateNum = tempDate.hour + float(tempDate.minute)/60
+    checkNum = day_end_hour + float(day_end_min)/60
+    if dateNum < checkNum: 
+        return thisday('B') - 1
+    else: 
+        return thisday('B')
+                
+def asfreq(date, toFreq, relation="BEFORE"):
+    """Returns a date converted to another frequency `toFreq`, according to the
+    relation `relation` ."""
+    tofreq = corelib.check_freq(toFreq)
+    _rel = relation.upper()[0]
+    if _rel not in ['B', 'A']:
+        msg = "Invalid relation '%s': Should be in ['before', 'after']"
+        raise ValueError, msg % relation
+
+    if not isinstance(date, Date):
+        raise DateError, "Date should be a valid Date instance!"
+
+    if date.freqstr == 'U':
+        warnings.warn("Undefined frequency: assuming daily!")
+        fromfreq = corelib.freq_revdict['D']
+    else:
+        fromfreq = date.freq
+    
+    if fromfreq == tofreq:
+        return date
+    else:
+        value = cseries.asfreq(numeric.asarray(date.value), fromfreq, tofreq, _rel)
+        if value > 0:
+            return Date(freq=tofreq, value=value)
+        else:
+            return None
+Date.asfreq = asfreq
+            
+def isDate(data):
+    "Returns whether `data` is an instance of Date."
+    return isinstance(data, Date) or \
+           (hasattr(data,'freq') and hasattr(data,'value'))
+
+            
+#####---------------------------------------------------------------------------
+#---- --- DateArray ---
+#####--------------------------------------------------------------------------- 
+ufunc_dateOK = ['add','subtract',
+                'equal','not_equal','less','less_equal', 'greater','greater_equal',
+                'isnan']
+
+class _datearithmetics(object):
+    """Defines a wrapper for arithmetic methods.
+Instead of directly calling a ufunc, the corresponding method of  the `array._data` 
+object is called instead.
+If `asdates` is True, a DateArray object is returned , else a regular ndarray
+is returned.
+    """
+    def __init__ (self, methodname, asdates=True):
+        """
+:Parameters:
+    - `methodname` (String) : Method name.
+        """
+        self.methodname = methodname
+        self._asdates = asdates
+        self.__doc__ = getattr(methodname, '__doc__')
+        self.obj = None
+    #
+    def __get__(self, obj, objtype=None):
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, other, *args, **kwargs):
+        "Execute the call behavior."
+        instance = self.obj
+        freq = instance.freq
+        if 'context' not in kwargs:
+            kwargs['context'] = 'DateOK'
+        method = getattr(super(DateArray,instance), self.methodname)
+        if isinstance(other, DateArray):
+            if other.freq != freq:
+                raise FrequencyDateError("Cannot operate on dates", \
+                                         freq, other.freq)
+        elif isinstance(other, Date):
+            if other.freq != freq:
+                raise FrequencyDateError("Cannot operate on dates", \
+                                         freq, other.freq)
+            other = other.value
+        elif isinstance(other, ndarray):
+            if other.dtype.kind not in ['i','f']:
+                raise ArithmeticDateError
+        if self._asdates:
+            return instance.__class__(method(other, *args), 
+                                      freq=freq)
+        else:
+            return method(other, *args)
+
+class DateArray(ndarray):  
+    """Defines a ndarray of dates, as ordinals.
+    
+When viewed globally (array-wise), DateArray is an array of integers.
+When viewed element-wise, DateArray is a sequence of dates.
+For example, a test such as :
+>>> DateArray(...) = value
+will be valid only if value is an integer, not a Date
+However, a loop such as :
+>>> for d in DateArray(...):
+accesses the array element by element. Therefore, `d` is a Date object.    
+    """
+    _defcachedinfo = dict(toobj=None, tostr=None, toord=None, 
+                          steps=None, full=None, hasdups=None)
+    def __new__(cls, dates=None, freq=None, copy=False):
+        # Get the frequency ......
+        if freq is None:
+            _freq = getattr(dates, 'freq', -9999)
+        else:
+            _freq = corelib.check_freq(freq)
+        cls._defaultfreq = corelib.check_freq(_freq)
+        # Get the dates ..........
+        _dates = numeric.array(dates, copy=copy, dtype=int_, subok=1)
+        if _dates.ndim == 0:
+            _dates.shape = (1,)
+        _dates = _dates.view(cls)
+        _dates.freq = _freq
+        return _dates
+    
+    def __array_wrap__(self, obj, context=None):
+        if context is None:
+            return self
+        elif context[0].__name__ not in ufunc_dateOK:
+            raise ArithmeticDateError, "(function %s)" % context[0].__name__
+    
+    def __array_finalize__(self, obj):
+        self.freq = getattr(obj, 'freq', -9999)
+        self._cachedinfo = dict(toobj=None, tostr=None, toord=None, 
+                                steps=None, full=None, hasdups=None)
+        if hasattr(obj,'_cachedinfo'):
+            self._cachedinfo.update(obj._cachedinfo)
+        return
+    
+    def __getitem__(self, indx):
+        if isinstance(indx, Date):
+            indx = self.find_dates(indx)
+        elif numeric.asarray(indx).dtype.kind == 'O':
+            try:
+                indx = self.find_dates(indx)       
+            except AttributeError:
+                pass     
+        r = ndarray.__getitem__(self, indx)
+        if isinstance(r, (generic, int)):
+            return Date(self.freq, value=r)
+        elif hasattr(r, 'size') and r.size == 1:
+            # need to check if it has a size attribute for situations
+            # like when the datearray is the data for a maskedarray
+            # or some other subclass of ndarray with wierd getitem
+            # behaviour
+            return Date(self.freq, value=r.item())
+        else:
+            if hasattr(r, '_cachedinfo'):
+                r._cachedinfo.update(dict(steps=None, full=None, hasdups=None))
+                for attr in ('tostr','toobj','toord'):
+                    if r._cachedinfo[attr] is not None:
+                        r._cachedinfo[attr] = r._cachedinfo[attr][indx]
+            return r
+        
+    def __repr__(self):
+        return ndarray.__repr__(self)
+    #......................................................
+    __add__ = _datearithmetics('__add__', asdates=True)
+    __radd__ = _datearithmetics('__add__', asdates=True)
+    __sub__ = _datearithmetics('__sub__', asdates=True)
+    __rsub__ = _datearithmetics('__rsub__', asdates=True)
+    __le__ = _datearithmetics('__le__', asdates=False)
+    __lt__ = _datearithmetics('__lt__', asdates=False)
+    __ge__ = _datearithmetics('__ge__', asdates=False)
+    __gt__ = _datearithmetics('__gt__', asdates=False)
+    __eq__ = _datearithmetics('__eq__', asdates=False)
+    __ne__ = _datearithmetics('__ne__', asdates=False)
+    #......................................................
+    @property
+    def freqstr(self):
+        "Returns the frequency string code."
+        return corelib.freq_tostr(self.freq)
+    @property
+    def day(self):          
+        "Returns the day of month."
+        return self.__getdateinfo__('D')
+    @property
+    def day_of_week(self):  
+        "Returns the day of week."
+        return self.__getdateinfo__('W')
+    @property
+    def day_of_year(self):  
+        "Returns the day of year."
+        return self.__getdateinfo__('R')
+    @property
+    def month(self):        
+        "Returns the month."
+        return self.__getdateinfo__('M')
+    @property
+    def quarter(self):   
+        "Returns the quarter."   
+        return self.__getdateinfo__('Q')
+    @property
+    def year(self):         
+        "Returns the year."
+        return self.__getdateinfo__('Y')
+    @property
+    def second(self):    
+        "Returns the seconds."  
+        return self.__getdateinfo__('S')
+    @property
+    def minute(self):     
+        "Returns the minutes."  
+        return self.__getdateinfo__('T')
+    @property
+    def hour(self):         
+        "Returns the hour."
+        return self.__getdateinfo__('H')
+    @property
+    def week(self):
+        "Returns the week."
+        return self.__getdateinfo__('I')
+
+    days = day
+    weekdays = day_of_week
+    yeardays = day_of_year
+    months = month
+    quarters = quarter
+    years = year
+    seconds = second
+    minutes = minute
+    hours = hour
+    weeks = week
+    
+    def __getdateinfo__(self, info):
+        return numeric.asarray(cseries.getDateInfo(numeric.asarray(self), 
+                                                   self.freq, info), 
+                               dtype=int_)
+    __getDateInfo = __getdateinfo__    
+    #.... Conversion methods ....................
+    #
+    def tovalue(self):
+        "Converts the dates to integer values."
+        return numeric.asarray(self)
+    #
+    def toordinal(self):
+        "Converts the dates from values to ordinals."
+        # Note: we better try to cache the result
+        if self._cachedinfo['toord'] is None:
+#            diter = (Date(self.freq, value=d).toordinal() for d in self)
+            diter = (d.toordinal() for d in self)
+            toord = numeric.fromiter(diter, dtype=float_)
+            self._cachedinfo['toord'] = toord
+        return self._cachedinfo['toord']
+    #
+    def tostring(self):
+        "Converts the dates to strings."
+        # Note: we better cache the result
+        if self._cachedinfo['tostr'] is None:
+            firststr = str(self[0])
+            if self.size > 0:
+                ncharsize = len(firststr)
+                tostr = numpy.fromiter((str(d) for d in self),
+                                        dtype='|S%i' % ncharsize)
+            else:
+                tostr = firststr
+            self._cachedinfo['tostr'] = tostr
+        return self._cachedinfo['tostr']
+    #   
+    def asfreq(self, freq=None, relation="BEFORE"):
+        "Converts the dates to another frequency."
+        # Note: As we define a new object, we don't need caching
+        if freq is None or freq == -9999:
+            return self
+        tofreq = corelib.check_freq(freq)
+        if tofreq == self.freq:
+            return self        
+        fromfreq = self.freq
+        _rel = relation.upper()[0]
+        new = cseries.asfreq(numeric.asarray(self), fromfreq, tofreq, _rel)
+        return DateArray(new, freq=freq)
+    #......................................................
+    def find_dates(self, *dates):
+        "Returns the indices corresponding to given dates, as an array."
+        ifreq = self.freq
+        c = numpy.zeros(self.shape, bool_)
+        for d in corelib.flatargs(*dates):
+            if d.freq != ifreq:
+                d = d.asfreq(ifreq)
+            c += (self == d.value)
+        c = c.nonzero()
+        if fromnumeric.size(c) == 0:
+            raise IndexError, "Date out of bounds!"
+        return c  
+
+    def date_to_index(self, date):
+        "Returns the index corresponding to one given date, as an integer."
+        if self.isvalid():
+            index = date.value - self[0].value
+            if index < 0 or index > self.size:
+                raise IndexError, "Date out of bounds!"
+            return index
+        else:
+            index_asarray = (self == date.value).nonzero()
+            if fromnumeric.size(index_asarray) == 0:
+                raise IndexError, "Date out of bounds!" 
+            return index_asarray[0][0]
+    #......................................................        
+    def get_steps(self):
+        """Returns the time steps between consecutive dates.
+    The timesteps have the same unit as the frequency of the series."""
+        if self.freq == 'U':
+            warnings.warn("Undefined frequency: assuming integers!")
+        if self._cachedinfo['steps'] is None:
+            _cached = self._cachedinfo
+            val = numeric.asarray(self).ravel()
+            if val.size > 1:
+                steps = val[1:] - val[:-1]
+                if _cached['full'] is None:
+                    _cached['full'] = (steps.max() == 1)
+                if _cached['hasdups'] is None:
+                    _cached['hasdups'] = (steps.min() == 0)
+            else:
+                _cached['full'] = True
+                _cached['hasdups'] = False
+                steps = numeric.array([], dtype=int_)
+            self._cachedinfo['steps'] = steps
+        return self._cachedinfo['steps']
+    
+    def has_missing_dates(self):
+        "Returns whether the DateArray have missing dates."
+        if self._cachedinfo['full'] is None:
+            steps = self.get_steps()
+        return not(self._cachedinfo['full'])
+    
+    def isfull(self):
+        "Returns whether the DateArray has no missing dates."
+        if self._cachedinfo['full'] is None:
+            steps = self.get_steps()
+        return self._cachedinfo['full']
+    
+    def has_duplicated_dates(self):
+        "Returns whether the DateArray has duplicated dates."
+        if self._cachedinfo['hasdups'] is None:
+            steps = self.get_steps()
+        return self._cachedinfo['hasdups']
+    
+    def isvalid(self):
+        "Returns whether the DateArray is valid: no missing/duplicated dates."
+        return  (self.isfull() and not self.has_duplicated_dates())
+    #......................................................
+
+#............................
+
+
+#####---------------------------------------------------------------------------
+#---- --- DateArray functions ---
+#####---------------------------------------------------------------------------  
+def isDateArray(a):
+    "Tests whether an array is a DateArray object."
+    return isinstance(a,DateArray)
+
+def guess_freq(dates):
+    """Tries to estimate the frequency of a list of dates, by checking the steps
+    between consecutive dates The steps should be in days.
+    Returns a frequency code (alpha character)."""
+    ddif = numeric.asarray(numpy.diff(dates))
+    ddif.sort()
+    if ddif[0] == ddif[-1] == 1.:
+        fcode = 'D'
+    elif (ddif[0] == 1.) and (ddif[-1] == 3.):
+        fcode = 'B'
+    elif (ddif[0] > 3.) and  (ddif[-1] == 7.):
+        fcode = 'W'
+    elif (ddif[0] >= 28.) and (ddif[-1] <= 31.):
+        fcode = 'M'
+    elif (ddif[0] >= 90.) and (ddif[-1] <= 92.):
+        fcode = 'Q'
+    elif (ddif[0] >= 365.) and (ddif[-1] <= 366.):
+        fcode = 'A'
+    elif numpy.abs(24.*ddif[0] - 1) <= 1e-5 and \
+         numpy.abs(24.*ddif[-1] - 1) <= 1e-5:
+        fcode = 'H'
+    elif numpy.abs(1440.*ddif[0] - 1) <= 1e-5 and \
+         numpy.abs(1440.*ddif[-1] - 1) <= 1e-5:
+        fcode = 'T'
+    elif numpy.abs(86400.*ddif[0] - 1) <= 1e-5 and \
+         numpy.abs(86400.*ddif[-1] - 1) <= 1e-5:
+        fcode = 'S'
+    else:
+        warnings.warn("Unable to estimate the frequency! %.3f<>%.3f" %\
+                      (ddif[0], ddif[-1]))
+        fcode = 'U'
+    return fcode
+
+
+def _listparser(dlist, freq=None):
+    "Constructs a DateArray from a list."
+    dlist = numeric.asarray(dlist)
+    dlist.sort()
+    if dlist.ndim == 0:
+        dlist.shape = (1,)
+    # Case #1: dates as strings .................
+    if dlist.dtype.kind == 'S':
+        #...construct a list of ordinals
+        ords = numpy.fromiter((DateTimeFromString(s).toordinal() for s in dlist),
+                               float_)
+        ords += 1
+        #...try to guess the frequency
+        if freq is None:
+            freq = guess_freq(ords)
+        #...construct a list of dates
+        dates = [Date(freq, string=s) for s in dlist]
+    # Case #2: dates as numbers .................
+    elif dlist.dtype.kind in 'if':
+        #...hopefully, they are values
+        if freq is None:
+            freq = guess_freq(dlist)
+        dates = dlist
+    # Case #3: dates as objects .................
+    elif dlist.dtype.kind == 'O':
+        template = dlist[0]
+        #...as Date objects
+        if isinstance(template, Date):
+            dates = numpy.fromiter((d.value for d in dlist), int_)
+        #...as mx.DateTime objects
+        elif hasattr(template,'absdays'):
+            # no freq given: try to guess it from absdays
+            if freq is None:
+                ords = numpy.fromiter((s.absdays for s in dlist), float_)
+                ords += 1
+                freq = guess_freq(ords)
+            dates = [Date(freq, datetime=m) for m in dlist]
+        #...as datetime objects
+        elif hasattr(template, 'toordinal'):
+            ords = numpy.fromiter((d.toordinal() for d in dlist), float_)
+            if freq is None:
+                freq = guess_freq(ords)
+            dates = [Date(freq, datetime=dt.datetime.fromordinal(a)) for a in ords]
+    #
+    result = DateArray(dates, freq)
+    return result
+
+
+def date_array(dlist=None, start_date=None, end_date=None, length=None, 
+               include_last=True, freq=None):
+    """Constructs a DateArray from:
+    - a starting date and either an ending date or a given length.
+    - a list of dates.
+    """
+    freq = corelib.check_freq(freq)
+    # Case #1: we have a list ...................
+    if dlist is not None:
+        # Already a DateArray....................
+        if isinstance(dlist, DateArray):
+            if (freq is not None) and (dlist.freq != corelib.check_freq(freq)):
+                return dlist.asfreq(freq)
+            else:
+                return dlist
+        # Make sure it's a sequence, else that's a start_date
+        if hasattr(dlist,'__len__'):
+            return _listparser(dlist, freq)
+        elif start_date is not None:
+            if end_date is not None:
+                dmsg = "What starting date should be used ? '%s' or '%s' ?"
+                raise DateError, dmsg % (dlist, start_date)
+            else:
+                (start_date, end_date) = (dlist, start_date)
+        else:
+            start_date = dlist
+    # Case #2: we have a starting date ..........
+    if start_date is None:
+        if length == 0:
+            return DateArray([], freq=freq)
+        raise InsufficientDateError
+    if not isDate(start_date):
+        dmsg = "Starting date should be a valid Date instance! "
+        dmsg += "(got '%s' instead)" % type(start_date)
+        raise DateError, dmsg
+    # Check if we have an end_date
+    if end_date is None:
+        if length is None:
+#            raise ValueError,"No length precised!"
+            length = 1
+    else:
+        if not isDate(end_date):
+            raise DateError, "Ending date should be a valid Date instance!"
+        length = int(end_date - start_date)
+        if include_last:
+            length += 1
+#    dlist = [(start_date+i).value for i in range(length)]
+    dlist = numeric.arange(length, dtype=int_)
+    dlist += start_date.value
+    if freq is None:
+        freq = start_date.freq
+    return DateArray(dlist, freq=freq)
+datearray = date_array
+
+def date_array_fromlist(dlist, freq=None):
+    "Constructs a DateArray from a list of dates."
+    return date_array(dlist=dlist, freq=freq)
+
+def date_array_fromrange(start_date, end_date=None, length=None, 
+                         include_last=True, freq=None):
+    """Constructs a DateArray from a starting date and either an ending date or 
+    a length."""
+    return date_array(start_date=start_date, end_date=end_date, 
+                      length=length, include_last=include_last, freq=freq)    
+
+#####---------------------------------------------------------------------------
+#---- --- Definition of functions from the corresponding methods ---
+#####---------------------------------------------------------------------------
+class _frommethod(object):
+    """Defines functions from existing MaskedArray methods.
+:ivar _methodname (String): Name of the method to transform.
+    """
+    def __init__(self, methodname):
+        self._methodname = methodname
+        self.__doc__ = self.getdoc()
+    def getdoc(self):
+        "Returns the doc of the function (from the doc of the method)."
+        try:
+            return getattr(DateArray, self._methodname).__doc__
+        except AttributeError:
+            return "???"
+    #
+    def __call__(self, caller, *args, **params):
+        if hasattr(caller, self._methodname):
+            method = getattr(caller, self._methodname)
+            # If method is not callable, it's a property, and don't call it
+            if hasattr(method, '__call__'):
+                return method.__call__(*args, **params)
+            return method
+        method = getattr(fromnumeric.asarray(caller), self._methodname)
+        try:
+            return method(*args, **params)
+        except SystemError:
+            return getattr(numpy,self._methodname).__call__(caller, *args, **params)
+#............................
+day_of_week = _frommethod('day_of_week')
+day_of_year = _frommethod('day_of_year')
+year = _frommethod('year')
+quarter = _frommethod('quarter')
+month = _frommethod('month')
+day = _frommethod('day')
+hour = _frommethod('hour')
+minute = _frommethod('minute')
+second = _frommethod('second')
+
+
+def period_break(dates, period):
+    """Returns the indices where the given period changes.
+
+:Parameters:
+    dates : DateArray
+        Array of dates to monitor.
+    period : string
+        Name of the period to monitor.
+    """
+    current = getattr(dates, period)
+    previous = getattr(dates-1, period)
+    return (current - previous).nonzero()[0]
+
+
+################################################################################
+
+if __name__ == '__main__':
+    import maskedarray.testutils
+    from maskedarray.testutils import assert_equal
+    if 1:
+        dlist = ['2007-%02i' % i for i in range(1,5)+range(7,13)]
+        mdates = date_array_fromlist(dlist, 'M')
+        # Using an integer
+        assert_equal(mdates[0].value, 24073)
+        assert_equal(mdates[-1].value, 24084)
+        # Using a date
+        lag = mdates.find_dates(mdates[0])
+        print mdates[lag]
+        assert_equal(mdates[lag], mdates[0])
+    if 1:
+        hodie = today('D')
+        D = DateArray(today('D'))
+        assert_equal(D.freq, 6000)
+    
+    if 1:
+        freqs = [x[0] for x in corelib.freq_dict.values() if x[0] != 'U']
+        print freqs
+        for f in freqs:
+            print f
+            today = thisday(f)
+            assert(Date(freq=f, value=today.value) == today)
+    
+    if 1:
+        D = date_array(start_date=thisday('D'), length=5)
+        Dstr = D.tostring()
+        assert_equal(D.tostring(), Dstr)
+        DL = D[[0,-1]]
+        assert_equal(DL.tostring(), Dstr[[0,-1]])
+    
\ No newline at end of file


Property changes on: trunk/Lib/sandbox/timeseries/tdates.py
___________________________________________________________________
Name: svn:keywords
   + Date 
Author 
Revision
Id



More information about the Scipy-svn mailing list