[Scipy-svn] r2552 - in trunk/Lib/sandbox/timeseries: . build build/lib.linux-x86_64-2.4 build/lib.linux-x86_64-2.4/timeseries build/temp.linux-x86_64-2.4 examples src tests

scipy-svn at scipy.org scipy-svn at scipy.org
Sun Jan 14 16:39:29 CST 2007


Author: pierregm
Date: 2007-01-14 16:39:15 -0600 (Sun, 14 Jan 2007)
New Revision: 2552

Added:
   trunk/Lib/sandbox/timeseries/.project
   trunk/Lib/sandbox/timeseries/CHANGELOG
   trunk/Lib/sandbox/timeseries/MANIFEST
   trunk/Lib/sandbox/timeseries/build/
   trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/
   trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/
   trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/__init__.py
   trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tcore.py
   trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tdates.py
   trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tseries.py
   trunk/Lib/sandbox/timeseries/build/temp.linux-x86_64-2.4/
   trunk/Lib/sandbox/timeseries/build/temp.linux-x86_64-2.4/src/
   trunk/Lib/sandbox/timeseries/tcore.py
   trunk/Lib/sandbox/timeseries/tdates.py
   trunk/Lib/sandbox/timeseries/tests/test_dates.py
   trunk/Lib/sandbox/timeseries/tests/test_timeseries.py
   trunk/Lib/sandbox/timeseries/tseries.py
Removed:
   trunk/Lib/sandbox/timeseries/corelib.py
   trunk/Lib/sandbox/timeseries/mtimeseries/
   trunk/Lib/sandbox/timeseries/timeseries.py
   trunk/Lib/sandbox/timeseries/tsdate.py
Modified:
   trunk/Lib/sandbox/timeseries/
   trunk/Lib/sandbox/timeseries/__init__.py
   trunk/Lib/sandbox/timeseries/examples/
   trunk/Lib/sandbox/timeseries/examples/example.py
   trunk/Lib/sandbox/timeseries/src/
Log:
cf changelog


Property changes on: trunk/Lib/sandbox/timeseries
___________________________________________________________________
Name: svn:ignore
   + sandbox


Added: trunk/Lib/sandbox/timeseries/.project
===================================================================
--- trunk/Lib/sandbox/timeseries/.project	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/.project	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>scipy_svn_timeseries</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.python.pydev.PyDevBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.python.pydev.pythonNature</nature>
+	</natures>
+</projectDescription>

Copied: trunk/Lib/sandbox/timeseries/CHANGELOG (from rev 2488, trunk/Lib/sandbox/timeseries/mtimeseries/CHANGELOG)
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/CHANGELOG	2007-01-04 16:42:30 UTC (rev 2488)
+++ trunk/Lib/sandbox/timeseries/CHANGELOG	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,15 @@
+#2007-01-14 : Code reorganization:
+#           : - Moved Matt's initial version to archived_version
+#           : - Moved Pierre's version to base
+#           : tdates
+#           : - Fixed a bug w/ definition of months and weeks
+#           : - Renamed dateOf to asfreq + use cseries
+#2007-01-04 : tsdate
+#           : - Corrected a bug w/ Date.__init__ and 'B' freq
+#2007-01-03 : tseries 
+#           : - Allowed endpoints adjustment after convert
+#           : - Put the estimation of the data length in its own function.
+#           : - Added a timestep compatibility check.
+#           : - The variables in a multi-series correspond now to the last axis.
+#           : - Blocked transpose/swapaxes, temporarily.
+#           : - Speed-up fill_missing_dates
\ No newline at end of file

Added: trunk/Lib/sandbox/timeseries/MANIFEST
===================================================================
--- trunk/Lib/sandbox/timeseries/MANIFEST	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/MANIFEST	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,8 @@
+setup.py
+./__init__.py
+./corelib.py
+./cseries.c
+./shiftingarray.py
+./timeseries.py
+./tsdate.py
+./examples/example.py

Modified: trunk/Lib/sandbox/timeseries/__init__.py
===================================================================
--- trunk/Lib/sandbox/timeseries/__init__.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/__init__.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,3 +0,0 @@
-from timeseries import *
-from tsdate import *
-from corelib import *


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

Added: trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/__init__.py
===================================================================

Added: trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tcore.py
===================================================================
--- trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tcore.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tcore.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,157 @@
+import numpy
+import maskedarray as MA
+
+
+#####---------------------------------------------------------------------------
+#---- --- Generic functions ---
+#####---------------------------------------------------------------------------
+def first_unmasked_val(a):
+    "Returns the first unmasked value in a 1d maskedarray."
+    (i,j) = MA.extras.flatnotmasked_edges(a)
+    return a[i]
+
+def last_unmasked_val(a):
+    "Returns the last unmasked value in a 1d maskedarray."
+    (i,j) = MA.extras.flatnotmasked_edges(a)
+    return a[j]
+
+def reverse_dict(d):
+    "Reverses the keys and values of a dictionary."
+    alt = []
+    tmp = [alt.extend([(w,k) for w in v]) for (k,v) in d.iteritems()]
+    return dict(alt)
+
+
+
+#####---------------------------------------------------------------------------
+#---- --- Option conversion ---
+#####---------------------------------------------------------------------------
+obs_dict = {"UNDEFINED":None,
+            "UNDEF":None,
+            "BEGIN": first_unmasked_val,
+            "BEGINNING": first_unmasked_val,
+            "END": last_unmasked_val,
+            "ENDING": last_unmasked_val,
+            "AVERAGED": MA.average,
+            "AVERAGE": MA.average,
+            "MEAN": MA.average,
+            "SUMMED": MA.sum,
+            "SUM": MA.sum,
+            "MAXIMUM": MA.maximum,
+            "MAX": MA.maximum,
+            "MINIMUM": MA.minimum,
+            "MIN": MA.minimum,
+            }
+obsDict = obs_dict
+#
+def fmtObserv(obStr):
+    "Converts a possible 'Observed' string into acceptable values."
+    if obStr is None:
+        return None
+    elif obStr.upper() in obs_dict.keys():
+        return obStr.upper()    
+    else:
+        raise ValueError("Invalid value for observed attribute: %s " % str(obStr))
+
+
+fmtfreq_dict = {'A': ['ANNUAL','ANNUALLY','YEAR','YEARLY'],
+                'B': ['BUSINESS','BUSINESSLYT'],
+                'D': ['DAY','DAILY',],
+                'H': ['HOUR','HOURLY',],
+                'M': ['MONTH','MONTHLY',],
+                'Q': ['QUARTER','QUARTERLY',],
+                'S': ['SECOND','SECONDLY',],
+                'T': ['MINUTE','MINUTELY',],
+                'W': ['WEEK','WEEKLY',],
+                'U': ['UNDEF','UNDEFINED'],
+                }
+fmtfreq_revdict = reverse_dict(fmtfreq_dict)
+
+def fmtFreq (freqStr):
+    "Converts a possible 'frequency' string to acceptable values."
+    if freqStr is None:
+        return None    
+    elif freqStr.upper() in fmtfreq_dict.keys():
+        return freqStr[0].upper()
+    elif freqStr.upper() in fmtfreq_revdict.keys():
+        return fmtfreq_revdict[freqStr.upper()]
+    else:
+        raise ValueError("Invalid frequency: %s " % str(freqStr))
+        
+class DateSpec:
+    "Fake data type for date variables."
+    def __init__(self, freq):
+        self.freq = fmtFreq(freq)
+        
+    def __hash__(self): 
+        return hash(self.freq)
+    
+    def __eq__(self, other):
+        if hasattr(other, "freq"): 
+            return self.freq == other.freq
+        else: 
+            return False
+    def __str__(self): 
+        return "Date(%s)" % str(self.freq)
+    
+    
+
+# define custom numpy types.
+# Note: A more robust approach would register these as actual valid numpy types
+# this is just a hack for now
+numpy.dateA = DateSpec("Annual")
+numpy.dateB = DateSpec("Business")
+numpy.dateD = DateSpec("Daily")
+numpy.dateH = DateSpec("Hourly")
+numpy.dateM = DateSpec("Monthly")
+numpy.dateQ = DateSpec("Quarterly")
+numpy.dateS = DateSpec("Secondly")
+numpy.dateT = DateSpec("Minutely")
+numpy.dateW = DateSpec("Weekly")
+numpy.dateU = DateSpec("Undefined")
+
+
+freq_type_mapping = {'A': numpy.dateA,
+                     'B': numpy.dateB,
+                     'D': numpy.dateD,
+                     'H': numpy.dateH,
+                     'M': numpy.dateM,
+                     'Q': numpy.dateQ,
+                     'S': numpy.dateS,
+                     'T': numpy.dateT,
+                     'W': numpy.dateW,
+                     'U': numpy.dateU,
+                     }
+        
+def freqToType(freq):
+    return freq_type_mapping[fmtFreq(freq)]
+
+def isDateType(dtype):
+    #TODO: That looks messy. We should simplify that
+    if len([x for x in freq_type_mapping.values() if x == dtype]) > 0: 
+        return True
+    else: 
+        return False
+
+#####---------------------------------------------------------------------------
+#---- --- Misc functions ---
+#####---------------------------------------------------------------------------
+#http://aspn.activestate.com/ASPN/Mail/Message/python-tutor/2302348
+def flatten_sequence(iterable):
+    """Flattens a compound of nested iterables."""
+    itm = iter(iterable)
+    for elm in itm:
+        if hasattr(elm,'__iter__') and not isinstance(elm, basestring):
+            for f in flatten_sequence(elm):
+                yield f
+        else:
+            yield elm
+        
+def flatargs(*args):
+    "Flattens the arguments."
+    if not hasattr(args, '__iter__'):
+        return args
+    else:
+        return flatten_sequence(args)
+        
+

Added: trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tdates.py
===================================================================
--- trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tdates.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tdates.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,1089 @@
+"""
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: core.py 59 2006-12-22 23:58:11Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 59 $"
+__date__     = '$Date: 2006-12-22 18:58:11 -0500 (Fri, 22 Dec 2006) $'
+
+import datetime
+import itertools
+import warnings
+
+
+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 maskedarray as MA
+#reload(MA)
+
+import mx.DateTime as mxD
+from mx.DateTime.Parser import DateFromString as mxDFromString
+
+import tscore as corelib
+import cseries
+
+
+import logging
+logging.basicConfig(level=logging.DEBUG,
+                    format='%(name)-15s %(levelname)s %(message)s',)
+daflog = logging.getLogger('darray_from')
+dalog = logging.getLogger('DateArray')
+
+#####---------------------------------------------------------------------------
+#---- --- Date Info ---
+#####---------------------------------------------------------------------------
+OriginDate = mxD.Date(1970)
+secondlyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(seconds=1)
+minutelyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(minutes=1)
+hourlyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(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 `mxDate` keyword with an existing mx.DateTime.DateTime object, or 
+      even a datetime.datetime object.
+      
+      >>> td.Date('D', mxDate=mx.DateTime.now())
+      >>> td.Date('D', mxDate=datetime.datetime.now())
+      """
+    def __init__(self, freq, year=None, month=None, day=None, quarter=None, 
+                 hours=None, minutes=None, seconds=None, 
+                 mxDate=None, value=None, string=None):
+        
+        if hasattr(freq, 'freq'):
+            self.freq = corelib.fmtFreq(freq.freq)
+        else:
+            self.freq = corelib.fmtFreq(freq)
+        self.type = corelib.freqToType(self.freq)
+        
+        if value is not None:
+            if self.freq == 'A':
+                self.mxDate = mxD.Date(value, -1, -1)
+            elif self.freq == 'B':
+                valtmp = (value - 1)//5
+                self.mxDate = mxD.DateTimeFromAbsDateTime(value + valtmp*7 - valtmp*5)
+            elif self.freq in ['D','U']:
+                self.mxDate = mxD.DateTimeFromAbsDateTime(value)
+            elif self.freq == 'H':
+                self.mxDate = hourlyOriginDate + mxD.DateTimeDeltaFrom(hours=value)
+            elif self.freq == 'M':
+                self.mxDate = mxD.DateTimeFromAbsDateTime(1) + \
+                              mxD.RelativeDateTime(months=value-1, day=-1)
+            elif self.freq == 'Q':
+                self.mxDate = mxD.DateTimeFromAbsDateTime(1) + \
+                              mxD.RelativeDateTime(years=(value // 4), 
+                                                   month=((value * 3) % 12), day=-1)
+            elif self.freq == 'S':
+                self.mxDate = secondlyOriginDate + mxD.DateTimeDeltaFromSeconds(value)
+            elif self.freq == 'T':
+                self.mxDate = minutelyOriginDate + mxD.DateTimeDeltaFrom(minutes=value)
+            elif self.freq == 'W':
+                self.mxDate = mxD.Date(1,1,7) + \
+                              mxD.RelativeDateTime(weeks=value-1)
+#                              mxD.RelativeDateTime(weeks=value-5./7-1)
+        
+        elif string is not None:
+            self.mxDate = mxDFromString(string)  
+            
+        elif mxDate is not None:
+            if isinstance(mxDate, datetime.datetime):
+                mxDate = mxD.strptime(mxDate.isoformat()[:19], "%Y-%m-%dT%H:%M:%S")
+            self.mxDate = truncateDate(self.freq, mxDate)
+            
+        else:
+            # First, some basic checks.....
+            if year is None:
+                raise InsufficientDateError            
+            if self.freq in ('B', 'D', 'W'):
+                if month is None or day is None: 
+                    raise InsufficientDateError
+            elif self.freq == 'M':
+                if month is None: 
+                    raise InsufficientDateError
+                day = -1
+            elif self.freq == 'Q':
+                if quarter is None: 
+                    raise InsufficientDateError
+                month = quarter * 3
+                day = -1
+            elif self.freq == 'A':
+                month = -1
+                day = -1
+            elif self.freq == 'S':
+                if month is None or day is None or seconds is None: 
+                    raise InsufficientDateError
+                
+            if self.freq in ['A','B','D','M','Q','W']:
+                self.mxDate = truncateDate(self.freq, mxD.Date(year, month, day))
+                if self.freq == 'B':
+                    if self.mxDate.day_of_week in [5,6]:
+                        raise ValueError("Weekend passed as business day")
+            elif self.freq in ['H','S','T']:
+                if not hours:
+                    if not minutes:
+                        if not seconds:
+                            hours = 0
+                        else:
+                            hours = seconds//3600
+                    else:
+                        hours = minutes // 60
+                if not minutes:
+                    if not seconds:
+                        minutes = 0
+                    else:
+                        minutes = (seconds-hours*3600)//60
+                if not seconds:
+                    seconds = 0
+                else:
+                    seconds = seconds % 60
+                self.mxDate = truncateDate(self.freq,
+                                           mxD.Date(year, month, day, 
+                                                    hours, minutes, seconds))
+        self.value = self.__value()
+    # FIXME: Shall we set them as properties ?
+    def day(self):          
+        "Returns the day of month."
+        return self.mxDate.day
+    def day_of_week(self):  
+        "Returns the day of week."
+        return self.mxDate.day_of_week
+    def day_of_year(self):  
+        "Returns the day of year."
+        return self.mxDate.day_of_year
+    def month(self):        
+        "Returns the month."
+        return self.mxDate.month
+    def quarter(self):   
+        "Returns the quarter."   
+        return monthToQuarter(self.mxDate.month)
+    def year(self):         
+        "Returns the year."
+        return self.mxDate.year
+    def second(self):    
+        "Returns the seconds."  
+        return int(self.mxDate.second)
+    def minute(self):     
+        "Returns the minutes."  
+        return int(self.mxDate.minute)
+    def hour(self):         
+        "Returns the hour."
+        return int(self.mxDate.hour)
+    def week(self):
+        "Returns the week."
+        return self.mxDate.iso_week[1]
+ 
+    def __add__(self, other):
+        if isinstance(other, Date):
+            raise FrequencyDateError("Cannot add dates", self.freq, other.freq)
+        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.freq, other.freq)
+            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 subtract dates", \
+                                     self.freq, other.freq)
+        return int(self) == int(other) 
+    
+    def __cmp__(self, other): 
+        if not hasattr(other, 'freq'):
+            return False
+        elif self.freq != other.freq:
+            raise FrequencyDateError("Cannot subtract dates", \
+                                     self.freq, other.freq)
+        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.freq == 'A':
+            val = int(self.mxDate.year)
+        # Business days.
+        elif self.freq == 'B':
+            days = self.mxDate.absdate
+            weeks = days // 7
+#            val = (weeks*5) + (days - weeks*7)
+            val = days - weeks*2
+        # Daily/undefined
+        elif self.freq in ['D', 'U']:
+            val = self.mxDate.absdate
+        # Hourly........
+        elif self.freq == 'H':
+            val = (self.mxDate - hourlyOriginDate).hours
+        # Monthly.......
+        elif self.freq == 'M':
+            val = (self.mxDate.year-1)*12 + self.mxDate.month
+        # Quarterly.....
+        elif self.freq == 'Q':
+            val = (self.mxDate.year-1)*4 + self.mxDate.month//3
+        # Secondly......
+        elif self.freq == 'S':
+            val = (self.mxDate - secondlyOriginDate).seconds
+        # Minutely......
+        elif self.freq == 'T':
+            val = (self.mxDate - minutelyOriginDate).minutes
+        # Weekly........
+        elif self.freq == 'W':
+            #val = int(self.mxDate.year*365.25/7.-1) + self.mxDate.iso_week[1]
+            val = self.mxDate.absdate//7
+        return int(val)
+    #......................................................
+    def default_fmtstr(self):
+        "Defines the default formats for printing Dates."
+        if self.freq == "A":
+            fmt =  "%Y"
+        elif self.freq in ("B","D"):
+            fmt =  "%d-%b-%y"
+        elif self.freq == "M":
+            fmt =  "%b-%Y"
+        elif self.freq == "Q":
+            fmt =  "%YQ%q"
+        elif self.freq == 'H':
+            fmt = "%d-%b-%Y %H:00"
+        elif self.freq == 'T':
+            fmt = "%d-%b-%Y %H:%M"
+        elif self.freq == "S":
+            fmt =  "%d-%b-%Y %H:%M:%S"
+        elif self.freq == "W":
+            fmt =  "%YW%W"
+        else:
+            fmt = "%d-%b-%y"
+        return fmt
+        
+    def strfmt(self, fmt):
+        "Formats the date"
+        qFmt = fmt.replace("%q", "XXXX")
+        tmpStr = self.mxDate.strftime(qFmt)
+        return tmpStr.replace("XXXX", str(self.quarter()))
+            
+    def __str__(self):
+        return self.strfmt(self.default_fmtstr())
+
+    def __repr__(self): 
+        return "<%s : %s>" % (str(self.freq), str(self))
+    #......................................................
+    def toordinal(self):
+        "Returns the date as an ordinal."
+        return self.mxDate.absdays
+
+    def fromordinal(self, ordinal):
+        "Returns the date as an ordinal."
+        return Date(self.freq, mxDate=mxD.DateTimeFromAbsDays(ordinal))
+    
+    def tostring(self):
+        "Returns the date as a string."
+        return str(self)
+    
+    def toobject(self):
+        "Returns the date as itself."
+        return self
+    
+    def asfreq(self, toFreq, relation='before'):
+        """Converts the date to a new frequency."""
+        return asfreq(self, toFreq, relation)
+    
+    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 truncateDate(freq, mxDate):
+    """Chops off the irrelevant information from the mxDate passed in."""
+    freq = corelib.fmtFreq(freq)
+    if freq == 'A':
+        return mxD.Date(mxDate.year)
+    elif freq == 'Q':
+        return mxD.Date(mxDate.year, monthToQuarter(mxDate.month)*3)
+    elif freq == 'M':
+        return mxD.Date(mxDate.year, mxDate.month)
+    elif freq == 'W':
+        d = mxDate.absdate
+        return mxD.DateTimeFromAbsDateTime(d + (7 - d % 7) % 7 - 1)
+    elif freq in ('B', 'D'):
+        if freq == 'B' and mxDate.day_of_week in [5,6]:
+            raise ValueError("Weekend passed as business day")
+        return mxD.Date(mxDate.year, mxDate.month, mxDate.day)
+    elif freq == 'H':
+        return mxD.Date(mxDate.year, mxDate.month, mxDate.day, \
+                        mxDate.hour)
+    elif freq == 'T':
+        return mxD.Date(mxDate.year, mxDate.month, mxDate.day, \
+                        mxDate.hour, mxDate.minute)
+    else:
+        return mxDate
+    
+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`."
+    freq = corelib.fmtFreq(freq)
+    tempDate = mxD.now()
+    # if it is Saturday or Sunday currently, freq==B, then we want to use Friday
+    if freq == 'B' and tempDate.day_of_week >= 5:
+        tempDate -= (tempDate.day_of_week - 4)
+    if freq in ('B','D','H','S','T','W'):
+        return Date(freq, mxDate=tempDate)
+    elif freq == 'M':
+        return Date(freq, year=tempDate.year, month=tempDate.month)
+    elif freq == 'Q':
+        return Date(freq, year=tempDate.year, quarter=monthToQuarter(tempDate.month))
+    elif freq == '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 = mxD.localtime()
+    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.fmtFreq(toFreq)
+    _rel = relation.upper()[0]
+#    if _rel not in ['B', 'A']:
+#        msg = "Invalid relation '%s': Should be in ['before', 'after']"
+#        raise ValueError, msg % relation
+#    elif _rel == 'B':
+#        before = True
+#    else:
+#        before = False
+
+#    if not isDateType(date):
+    if not isinstance(date, Date):
+        raise DateError, "Date should be a valid Date instance!"
+
+    if date.freq == toFreq:
+        return date
+    else:
+        value = cseries.asfreq(numeric.asarray(date.value), date.freq, toFreq, _rel)
+        if value > 0:
+            return Date(freq=toFreq, value=value)
+        else:
+            return None
+#    # Convert to annual ....................
+#    elif toFreq == 'A':
+#        return Date(freq='A', year=date.year())
+#    # Convert to quarterly .................
+#    elif toFreq == 'Q':
+#        if date.freq == 'A':
+#            if before: 
+#                return Date(freq='A', year=date.year(), quarter=1)
+#            else: 
+#                return Date(freq='A', year=date.year(), quarter=4)
+#        else:
+#            return Date(freq='Q', year=date.year(), quarter=date.quarter())
+#    # Convert to monthly....................
+#    elif toFreq == 'M':
+#        if date.freq == 'A':
+#            if before: 
+#                return Date(freq='M', year=date.year(), month=1)
+#            else: 
+#                return Date(freq='M', year=date.year(), month=12)
+#        elif date.freq == 'Q':
+#            if before: 
+#                return dateOf(date-1, 'M', "AFTER")+1
+#            else: 
+#                return Date(freq='M', year=date.year(), month=date.month())
+#        else:
+#            return Date(freq='M', year=date.year(), month=date.month())
+#    # Convert to weekly ....................
+#    elif toFreq == 'W':
+#        if date.freq == 'A':
+#            if before: 
+#                return Date(freq='W', year=date.year(), month=1, day=1)
+#            else: 
+#                return Date(freq='W', year=date.year(), month=12, day=-1)
+#        elif date.freq in ['Q','M']:
+#            if before: 
+#                return dateOf(date-1, 'W', "AFTER")+1
+#            else: 
+#                return Date(freq='W', year=date.year(), month=date.month())
+#        else:
+#            val = date.weeks() + int(date.year()*365.25/7.-1)
+#            return Date(freq='W', value=val)
+#    # Convert to business days..............
+#    elif toFreq == 'B':
+#        if date.freq in ['A','Q','M','W']:
+#            if before: 
+#                return dateOf(dateOf(date, 'D'), 'B', "AFTER")
+#            else: 
+#                return dateOf(dateOf(date, 'D', "AFTER"), 'B', "BEFORE")
+#        elif date.freq == 'D':
+#            # BEFORE result: preceeding Friday if date is a weekend, same day otherwise
+#            # AFTER result: following Monday if date is a weekend, same day otherwise
+#            tempDate = date.mxDate
+#            if before:
+#                if tempDate.day_of_week >= 5: 
+#                    tempDate -= (tempDate.day_of_week - 4)
+#            else:
+#                if tempDate.day_of_week >= 5: 
+#                    tempDate += 7 - tempDate.day_of_week
+#            return Date(freq='B', mxDate=tempDate)
+#        else: 
+#            if before: 
+#                return dateOf(dateOf(date, 'D'), 'B', "BEFORE")
+#            else: 
+#                return dateOf(dateOf(date, 'D'), 'B', "AFTER")
+#    # Convert to day .......................
+#    elif toFreq == 'D':
+#        # ...from annual
+#        if date.freq == 'A':
+#            if before: 
+#                return Date(freq='D', year=date.year(), month=1, day=1)
+#            else: 
+#                return Date(freq='D', year=date.year(), month=12, day=31) 
+#        # ...from quarter
+#        elif date.freq == 'Q':
+#            if before: 
+#                return dateOf(date-1, 'D', "AFTER")+1
+#            else: 
+#                return Date(freq='D', year=date.year(), month=date.month(), 
+#                            day=date.day())
+#        # ...from month
+#        elif date.freq == 'M':
+#            if before:
+#                return Date(freq='D', year=date.year(), month=date.month(), day=1)
+#            else:
+#                (mm,yy) = (date.month(), date.year())
+#                if date.month() == 12:
+#                    (mm, yy) = (1, yy + 1)
+#                else:
+#                    mm = mm + 1
+#                return Date('D', year=yy, month=mm, day=1)-1
+#        # ...from week
+#        elif date.freq == 'W':
+#            if before:
+#                return Date(freq='D', year=date.year(), month=date.month(), 
+#                            day=date.day())
+#            else:
+#                ndate = date + 1 
+#                return Date(freq='D', year=ndate.year(), month=ndate.month(), 
+#                            day=ndate.day()) 
+#        # ...from a lower freq
+#        else:
+#            return Date('D', year=date.year(), month=date.month(), day=date.day())  
+#    #Convert to hour........................
+#    elif toFreq == 'H':
+#        if date.freq in ['A','Q','M','W']:
+#            if before: 
+#                return dateOf(dateOf(date, 'D', "BEFORE"), 'H', "BEFORE")
+#            else: 
+#                return dateOf(dateOf(date, 'D', "AFTER"), 'H', "AFTER")
+#        if date.freq in ['B','D']:
+#            if before: 
+#                return Date(freq='H', year=date.year(), month=date.month(), 
+#                            day=date.day(), hours=0)
+#            else: 
+#                return Date(freq='H', year=date.year(), month=date.month(), 
+#                            day=date.day(), hours=23)
+#        else: 
+#            return Date(freq='H', year=date.year(), month=date.month(), 
+#                        day=date.day(), hours=date.hour())
+#    #Convert to second......................
+#    elif toFreq == 'T':
+#        if date.freq in ['A','Q','M','W']:
+#            if before: 
+#                return dateOf(dateOf(date, 'D', "BEFORE"), 'T', "BEFORE")
+#            else: 
+#                return dateOf(dateOf(date, 'D', "AFTER"), 'T', "AFTER")
+#        elif date.freq in ['B','D','H']:
+#            if before: 
+#                return Date(freq='T', year=date.year(), month=date.month(), 
+#                            day=date.day(), minutes=0)
+#            else: 
+#                return Date(freq='T', year=date.year(), month=date.month(), 
+#                            day=date.day(), minutes=24*60-1)
+#        else:
+#            return Date(freq='H', year=date.year(), month=date.month(), 
+#                        day=date.day(), hours=date.hour(), minutes=date.minute())
+#    #Convert to minute......................
+#    elif toFreq == 'S':
+#        if date.freq in ['A','Q','M','W']:
+#            if before: 
+#                return dateOf(dateOf(date, 'D', "BEFORE"), 'S', "BEFORE")
+#            else: 
+#                return dateOf(dateOf(date, 'D', "AFTER"), 'S', "AFTER")
+#        elif date.freq in ['B','D']:
+#            if before: 
+#                return Date(freq='S', year=date.year(), month=date.month(), 
+#                            day=date.day(), seconds=0)
+#            else: 
+#                return Date(freq='S', year=date.year(), month=date.month(), 
+#                            day=date.day(), seconds=24*60*60-1)
+            
+def isDate(data):
+    "Returns whether `data` is an instance of Date."
+    return isinstance(data, Date)
+
+            
+#####---------------------------------------------------------------------------
+#---- --- DateArray ---
+#####--------------------------------------------------------------------------- 
+ufunc_dateOK = ['add','subtract',
+                'equal','not_equal','less','less_equal', 'greater','greater_equal',
+                'isnan']
+
+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.    
+    """
+    def __new__(cls, dates=None, freq='U', copy=False):
+        #dalog.info("__new__ received %s [%i]" % (type(dates), numpy.size(dates)))
+        if isinstance(dates, DateArray):
+            #dalog.info("__new__ sends %s as %s" % (type(dates), cls))
+            cls.__defaultfreq = dates.freq
+            if not copy:
+                return dates.view(cls)
+            return dates.copy().view(cls)
+        else:
+            _dates = numeric.asarray(dates, dtype=int_)
+            if copy:
+                _dates = _dates.copy()
+            #dalog.info("__new__ sends %s as %s" % (type(_dates), cls))
+            if freq is None:
+                freq = 'U'
+            cls.__defaultfreq = corelib.fmtFreq(freq)
+            (cls.__toobj, cls.__toord, cls.__tostr) = (None, None, None)
+            (cls.__steps, cls.__full, cls.__hasdups) = (None, None, None)
+            return _dates.view(cls)
+    
+    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):
+        #dalog.info("__array_finalize__ received %s" % type(obj))
+        if hasattr(obj, 'freq'):
+            self.freq = obj.freq
+        else:
+            self.freq = self.__defaultfreq
+        #dalog.info("__array_finalize__ sends %s" % type(self))
+    
+    def __getitem__(self, index):
+        #dalog.info("__getitem__ got  index %s (%s)"%(index, type(index)))
+        if isinstance(index, Date):
+            index = self.find_dates(index)
+        elif numeric.asarray(index).dtype.kind == 'O':
+            try:
+                index = self.find_dates(index)       
+            except AttributeError:
+                pass     
+        r = ndarray.__getitem__(self, index)
+        if r.size == 1:
+            # Only one element, and it's not a scalar: we have a DateArray of size 1
+            if len(r.shape) > 0:
+                r = r.item()
+            return Date(self.freq, value=r)
+        else:
+            return r
+        
+    def __repr__(self):
+        return ndarray.__repr__(self)
+    #......................................................
+    @property
+    def years(self):
+        "Returns the years."
+        return numeric.asarray([d.year() for d in self], dtype=int_)
+    @property
+    def months(self):
+        "Returns the months."
+        return numeric.asarray([d.month() for d in self], dtype=int_)
+    @property
+    def day_of_year(self):
+        "Returns the days of years."
+        return numeric.asarray([d.day_of_year() for d in self], dtype=int_)
+    yeardays = day_of_year
+    @property
+    def day_of_week(self):
+        "Returns the days of week."
+        return numeric.asarray([d.day_of_week() for d in self], dtype=int_)
+    #.... Conversion methods ....................
+#    def toobject(self):
+#        "Converts the dates from ordinals to Date objects."
+#        # Note: we better try to cache the result
+#        if self.__toobj is None:
+##            toobj = numeric.empty(self.size, dtype=object_)
+##            toobj[:] = [Date(self.freq, value=d) for d in self]
+##            self.__toobj = toobj
+#            self.__toobj = self
+#        return self.__toobj
+    #
+    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.__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.__toord = toord
+        return self.__toord
+    #
+    def tostring(self):
+        "Converts the dates to strings."
+        # Note: we better cache the result
+        if self.__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.__tostr = tostr
+        return self.__tostr
+    #
+#    def asfreq_ini(self, freq=None):
+#        "Converts the dates to another frequency."
+#        # Note: As we define a new object, we don't need caching
+#        if freq is None:
+#            return self
+#        freq = corelib.fmtFreq(freq)
+#        if freq == self.freq:
+#            return self        
+#        if self.isvalid():
+#            new = numeric.arange(self.size, dtype=int_)
+#            new += self[0].asfreq(freq).value
+#        else:
+#            new = numpy.fromiter((d.asfreq(freq).value for d in self),
+#                                 dtype=float_)
+#        return DateArray(new, freq=freq)
+    
+    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:
+            return self
+        freq = corelib.fmtFreq(freq)
+        if freq == self.freq:
+            return self        
+        _rel = relation.upper()[0]
+        new = cseries.asfreq(numeric.asarray(self), self.freq, freq, _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 ValueError, "Date out of bounds!"
+        return c  
+#    def find_dates_alt(self, *dates):
+#        "Returns the indices corresponding to given dates, as an array."
+#        ifreq = self.freq
+#        c = numpy.zeros(self.shape, bool_)
+#        dates = date_array([d for d in corelib.flatargs(*dates)]).asfreq(ifreq)
+#        for d in numeric.asarray(dates):
+#            c += (self == d)
+#        c = c.nonzero()
+#        if fromnumeric.size(c) == 0:
+#            raise ValueError, "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 ValueError, "Date out of bounds!"
+            return index
+        else:
+            index_asarray = (self == date.value).nonzero()
+            if fromnumeric.size(index_asarray) == 0:
+                raise ValueError, "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 daily!")
+        if self.__steps is None:
+            val = numeric.asarray(self).ravel()
+            if val.size > 0:
+                steps = val[1:] - val[:-1]
+                if self.__full is None:
+                    self.__full = (steps.max() == 1)
+                if self.__hasdups is None:
+                    self.__hasdups = (steps.min() == 0)
+            else:
+                self.__full = True
+                self.__hasdups = False
+            self.__steps = steps
+        return self.__steps
+    
+    def has_missing_dates(self):
+        "Returns whether the DateArray have missing dates."
+        if self.__full is None:
+            steps = self.get_steps()
+        return not(self.__full)
+    
+    def isfull(self):
+        "Returns whether the DateArray has no missing dates."
+        if self.__full is None:
+            steps = self.get_steps()
+        return self.__full
+    
+    def has_duplicated_dates(self):
+        "Returns whether the DateArray has duplicated dates."
+        if self.__hasdups is None:
+            steps = self.get_steps()
+        return self.__hasdups
+    
+    def isvalid(self):
+        "Returns whether the DateArray is valid: no missing/duplicated dates."
+        return  (self.isfull() and not self.has_duplicated_dates())
+    #......................................................
+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
+        #dalog.info('__datearithmetics got method %s' % methodname)
+    #
+    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'
+        #dalog.info('__datearithmetics got other %s' % type(other))
+        method = getattr(super(DateArray,instance), self.methodname)
+        if isinstance(other, DateArray):
+            if other.freq != freq:
+                raise FrequencyDateError("Cannot operate on dates", \
+                                         freq, other.freq)
+#            other = 
+        elif isinstance(other, Date):
+            if other.freq != freq:
+                raise FrequencyDateError("Cannot operate on dates", \
+                                         freq, other.freq)
+            other = other.value
+            #dalog.info('__datearithmetics got other %s' % type(other))
+        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)
+#............................
+DateArray.__add__ = _datearithmetics('__add__', asdates=True)
+DateArray.__radd__ = _datearithmetics('__add__', asdates=True)
+DateArray.__sub__ = _datearithmetics('__sub__', asdates=True)
+DateArray.__rsub__ = _datearithmetics('__rsub__', asdates=True)
+DateArray.__le__ = _datearithmetics('__le__', asdates=False)
+DateArray.__lt__ = _datearithmetics('__lt__', asdates=False)
+DateArray.__ge__ = _datearithmetics('__ge__', asdates=False)
+DateArray.__gt__ = _datearithmetics('__gt__', asdates=False)
+DateArray.__eq__ = _datearithmetics('__eq__', asdates=False)
+DateArray.__ne__ = _datearithmetics('__ne__', asdates=False)
+
+#####---------------------------------------------------------------------------
+#---- --- 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()
+    # Case #1: dates as strings .................
+    if dlist.dtype.kind == 'S':
+        #...construct a list of ordinals
+        ords = numpy.fromiter((mxDFromString(s).absdays 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 ['i','f']:
+        #...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), float_)
+        #...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, mxDate=m) for m in dlist]
+        #...as datetime objects
+        elif hasattr(dlist[0], 'toordinal'):
+            ords = numpy.fromiter((d.toordinal() for d in dlist), float_)
+            if freq is None:
+                freq = guess_freq(ords)
+            dates = [Date(freq, mxDate=mxD.DateTimeFromAbsDays(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.fmtFreq(freq)
+    # Case #1: we have a list ...................
+    if dlist is not None:
+        # Already a DateArray....................
+        if isinstance(dlist, DateArray):
+            if freq != dlist.freq:
+                return dlist.asfreq(freq)
+            else:
+                return dlist
+        return _listparser(dlist, freq)
+    # Case #2: we have a starting date ..........
+    if start_date is None:
+        raise InsufficientDateError
+#    if not isDateType(start_date):
+    if not isinstance(start_date, Date):
+        raise DateError, "Starting date should be a valid Date instance!"
+    # Check if we have an end_date
+    if end_date is None:
+        if length is None:
+            raise ValueError,"No length precised!"
+    else:
+        if not isinstance(end_date, Date):
+            raise DateError, "Ending date should be a valid Date instance!"
+#        assert(isDateType(end_date),
+#               "Starting date should be a valid Date instance!")
+        length = 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:
+            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')
+
+
+################################################################################

Added: trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tseries.py
===================================================================
--- trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tseries.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/build/lib.linux-x86_64-2.4/timeseries/tseries.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,1254 @@
+# pylint: disable-msg=W0201, W0212
+"""
+Core classes for time/date related arrays.
+
+The `DateArray` class provides a base for the creation of date-based objects,
+using days as the base units. This class could be adapted easily to objects
+with a smaller temporal resolution (for example, using one hour, one second as the
+base unit).
+
+The `TimeSeries` class provides  a base for the definition of time series.
+A time series is defined here as the combination of two arrays:
+    
+    - an array storing the time information (as a `DateArray` instance);
+    - an array storing the data (as a `MaskedArray` instance.
+
+These two classes were liberally adapted from `MaskedArray` class.
+
+
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: core.py 59 2006-12-22 23:58:11Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 59 $"
+__date__     = '$Date: 2006-12-22 18:58:11 -0500 (Fri, 22 Dec 2006) $'
+
+
+import logging
+import weakref
+
+
+import numpy
+from numpy.core import bool_, float_, int_
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numeric as numeric
+import numpy.core.umath as umath
+from numpy import ndarray
+from numpy.core.records import recarray
+from numpy.core.records import fromarrays as recfromarrays
+
+#from cPickle import dump, dumps
+
+import maskedarray as MA
+#import numpy.core.ma as MA
+reload(MA)
+#MaskedArray = MA.MaskedArray
+from maskedarray.core import MaskedArray
+masked = MA.masked
+nomask = MA.nomask
+MAError = MA.MAError
+masked_array = MA.masked_array
+filled = MA.filled
+getmask = MA.getmask
+getmaskarray = MA.getmaskarray
+make_mask_none = MA.make_mask_none
+mask_or = MA.mask_or
+make_mask = MA.make_mask
+
+oldma = (MA.__name__ == 'numpy.core.ma')
+
+import tscore as corelib
+#reload(corelib)
+from tscore import *
+
+import tdates
+reload(tdates)
+from tdates import DateError, InsufficientDateError
+from tdates import Date, isDate, DateArray, isDateArray, \
+    date_array, date_array_fromlist, date_array_fromrange, thisday
+
+import cseries
+reload(cseries)
+
+#...............................................................................
+logging.basicConfig(level=logging.DEBUG,
+                    format='%(name)-15s %(levelname)s %(message)s',)
+talog = logging.getLogger('log.TimeArray')
+tslog = logging.getLogger('TimeSeries')
+btslog = logging.getLogger('BaseTimeSeries')
+
+ufunc_domain = {}
+ufunc_fills = {}
+
+#### --------------------------------------------------------------------------
+#--- ... TimeSeriesError class ...
+#### --------------------------------------------------------------------------
+class TimeSeriesError(Exception):
+    "Class for TS related errors."
+    def __init__ (self, args=None):
+        "Creates an exception."
+        Exception.__init__(self)
+        self.args = args
+    def __str__(self):
+        "Calculates the string representation."
+        return str(self.args)
+    __repr__ = __str__
+
+class TimeSeriesCompatibilityError(TimeSeriesError):
+    """Defines the exception raised when series are incompatible."""
+    def __init__(self, mode, first, second):
+        if mode == 'freq':
+            msg = "Incompatible time steps! (%s <> %s)"
+        elif mode == 'start_date':
+            msg = "Incompatible starting dates! (%s <> %s)"
+        elif mode == 'size':
+            msg = "Incompatible sizes! (%s <> %s)"
+        msg = msg % (first, second)
+        TimeSeriesError.__init__(self, msg)
+
+
+#def _compatibilitycheck(a, b):
+def _timeseriescompat(a, b):
+    """Checks the date compatibility of two TimeSeries object.
+    Returns True if everything's fine, or raises an exception."""
+    if not (hasattr(a,'freq') and hasattr(b, 'freq')):
+        return True
+    if a.freq != b.freq:
+        raise TimeSeriesCompatibilityError('freq', a.freq, b.freq)
+    elif a.start_date() != b.start_date():
+        raise TimeSeriesCompatibilityError('start_date', 
+                                           a.start_date(), b.start_date())
+    elif (a._dates.get_steps() != b._dates.get_steps()).any():
+        raise TimeSeriesCompatibilityError('time_steps', 
+                                           a._dates.get_steps(), b._dates.get_steps())
+    elif a.shape != b.shape:
+        raise TimeSeriesCompatibilityError('size', "1: %s" % str(a.shape), 
+                                                   "2: %s" % str(b.shape))
+    return True
+
+def _datadatescompat(data,dates):
+    """Checks the compatibility of dates and data at the creation of a TimeSeries.
+    Returns True if everything's fine, raises an exception otherwise."""    
+    # If there's only 1 element, the date is a Date object, which has no size...
+    tsize = numeric.size(dates)
+    dsize = data.size
+    # Only one data
+    if dsize == tsize:
+        return True
+    elif data.ndim > 1:
+        dsize = numeric.asarray(data.shape)[:-1].prod()
+        if dsize == tsize:
+            return True    
+    raise TimeSeriesCompatibilityError('size', "data: %s" % dsize, 
+                                               "dates: %s" % tsize)
+
+def _getdatalength(data):
+    "Estimates the length of a series (size/nb of variables)."
+    if numeric.ndim(data) >= 2:
+        return numeric.asarray(numeric.shape(data))[:-1].prod()
+    else:
+        return numeric.size(data)
+
+##### --------------------------------------------------------------------------
+##--- ... Time Series ...
+##### --------------------------------------------------------------------------
+#if oldma:
+#    parentclass = ndarray
+#else:
+#    parentclass = MaskedArray
+#
+class TimeSeries(MaskedArray, object):     
+    """Base class for the definition of time series.
+A time series is here defined as the combination of three arrays:
+    
+    - `series` : *[ndarray]*
+        Data part
+    - `mask` : *[ndarray]*
+        Mask part
+    - `dates` : *[DateArray]*
+        Date part
+    
+The combination of `series` and `dates` is the `data` part.
+    """
+    def __new__(cls, data, dates=None, mask=nomask, 
+                freq=None, observed=None, start_date=None, 
+                dtype=None, copy=False, fill_value=None,
+                keep_mask=True, small_mask=True, hard_mask=False):
+        #tslog.info("__new__: received data types %s, %s" % (type(data), data))
+        options = dict(copy=copy, dtype=dtype, fill_value=fill_value,
+                       keep_mask=keep_mask, small_mask=small_mask, 
+                       hard_mask=hard_mask, )
+        if isinstance(data, TimeSeries):
+            # Check dates ........
+            if dates is None:
+                newdates = data._dates
+            else:
+                if not hasattr(dates,'freq'):
+                    raise DateError, "Invalid Dates!"       
+                newdates = dates
+                data._dates = newdates
+                if hasattr(data, '_data') and hasattr(data._data, '_dates'):
+                    data._data._dates = newdates
+            cls._defaultdates = newdates    
+            # Check frequency......
+            if freq is not None:
+                freq = corelib.fmtFreq(freq)
+                if freq != newdates.freq:
+                    _dates = newdates.tofreq(freq)
+            else:
+                freq = newdates.freq
+            # Check observed.......
+            if observed is not None:
+                observed = data._observed
+            cls._defaultobserved = observed  
+            _data = data._series
+        else:
+            # Check dates ........
+            if dates is None:
+                length = _getdatalength(data)
+                newdates = date_array(start_date=start_date, length=length,
+                                      freq=freq)                 
+            elif not hasattr(dates, 'freq'):
+                newdates = date_array(dlist=dates, freq=freq)
+            else:
+                newdates = dates
+            _data = data
+            if hasattr(data, '_mask') :
+                mask = mask_or(data._mask, mask)
+            cls._defaultdates = newdates    
+            cls._defaultobserved = observed  
+#            if oldma:
+#                newdata = MaskedArray(data, mask=mask, dtype=dtype,
+#                                      copy=copy,fill_value=fill_value)
+#                cls._defaultmask = newdata._mask
+#                cls._defaulthardmask = True
+#                cls._fill_value = newdata._fill_value
+#                assert(_datadatescompat(newdata,dates))
+#                return ndarray.__new__(cls,shape=newdata.shape,dtype=newdata.dtype,
+#                                       buffer=newdata._data)
+#            _data = data
+#        newdata = MaskedArray.__new__(cls, data=_data, mask=mask, **options)
+        newdata = super(TimeSeries,cls).__new__(cls, _data, mask=mask,
+                                                **options)
+        assert(_datadatescompat(data,newdates))
+        return newdata
+            
+    #..................................
+    def __array_wrap__(self, obj, context=None):
+#        if oldma:
+#            tmpself = MaskedArray(self._data, mask=self._mask)
+#            return TimeSeries(MaskedArray.__array_wrap__(tmpself, obj, context),
+#                              dates=self._dates)
+#        print "__array_wrap__"
+        return TimeSeries(super(TimeSeries,self).__array_wrap__(obj, context),
+                          dates=self._dates)
+    #............................................
+    def __array_finalize__(self,obj):
+        #tslog.info("__array_finalize__ received %s" % type(obj))      
+        if isinstance(obj, TimeSeries):
+            self._dates = obj._dates
+            self._data = obj._series._data
+            self._mask = obj._series._mask
+            self._series = obj._series
+            self._hardmask = obj._series._hardmask
+            self.observed = obj.observed
+            self._fill_value = obj._fill_value
+        else:     
+            self._dates = self._defaultdates
+            self.observed = self._defaultobserved
+            self._series = MA.array(obj, mask=self._defaultmask, 
+                                    copy=False, hard_mask=self._defaulthardmask)
+            self._mask = self._defaultmask
+            self._data = obj
+            self._hardmask = self._defaulthardmask
+            self.fill_value = self._fill_value
+        self._mask =  self._series._mask
+        self._data = self._series._data
+        self._hardmask = self._series._hardmask
+        #tslog.info("__array_finalize__ sends %s" % type(self))
+        return
+    #............................................
+    def __getattribute__(self,attr):
+        "Returns a given attribute."
+        # Here, we need to be smart: _mask should call _series._mask...
+        if attr in ['_data','_mask','_hardmask']:
+            return getattr(self._series,attr)
+        return super(TimeSeries, self).__getattribute__(attr)
+    def __setattribute__(self,attr, value):
+        """Sets an attribute to a given value."""
+        # Same thing here: if we modify ._mask, we need to modify _series._mask
+        # ...as well
+        super(TimeSeries, self).__setattribute__(attr, value)
+        if attr in ['_data','_mask','_hardmask']:
+            super(self._series.__class__, self._series).__setattribute__(attr, value)
+            setattr(self._series, attr, value)
+    #............................................
+    def __checkindex(self, index):
+        "Checks the validity of an index."
+        if isinstance(index, int):
+            return index
+        if isinstance(index, str):
+            return self._dates.date_to_index(Date(self._dates.freq, string=index))
+        elif isDate(index) or isDateArray(index):
+            return self._dates.date_to_index(index)
+        elif isinstance(index,slice):
+            slice_start = self.__checkindex(index.start)
+            slice_stop = self.__checkindex(index.stop)
+            return slice(slice_start, slice_stop, index.step)
+        elif isTimeSeries(index):
+            index = index._series
+        if getmask(index) is not nomask:
+            msg = "Masked arrays must be filled before they can be used as indices!"
+            raise IndexError, msg
+        return index
+
+    def __getitem__(self, index):
+        """x.__getitem__(y) <==> x[y]
+Returns the item described by i. Not a copy as in previous versions.
+        """
+        index = self.__checkindex(index)
+        data = self._series[index]
+        date = self._dates[index]
+        m = self._mask
+        scalardata = (len(numeric.shape(data))==0)
+        # 
+        if m is nomask:
+            if scalardata:
+                return TimeSeries(data, dates=date)
+            else:
+                return TimeSeries(data, dates=date, mask=nomask, keep_mask=True,
+                                  copy=False)
+        #....
+        mi = m[index]
+        if mi.size == 1:
+            if mi:
+                return TimeSeries(data, dates=date, mask=True)
+            return TimeSeries(data, dates=date, mask=nomask)
+        else:
+            return TimeSeries(data, dates=date, mask=mi)
+    #........................
+    def __setitem__(self, index, value):
+        """x.__setitem__(i, y) <==> x[i]=y
+Sets item described by index. If value is masked, masks those locations.
+        """
+        if self is masked:
+            raise MAError, 'Cannot alter the masked element.'
+        index = self.__checkindex(index)
+        #....
+        if isinstance(value, TimeSeries):
+            assert(_timeseriescompat(self[index], value))
+            self._series[index] = value._series
+        else:
+            self._series[index] = value
+        # Don't forget to update the mask !
+        self._mask = self._series._mask
+        
+    #........................
+    def __getslice__(self, i, j):
+        "Gets slice described by i, j"
+        i = self.__checkindex(i)
+        j = self.__checkindex(j)
+        (data, date) = (self._series[i:j], self._dates[i:j])
+        return TimeSeries(data, dates=date, copy=False)
+    #....
+    def __setslice__(self, i, j, value):
+        "Gets item described by i. Not a copy as in previous versions."
+        i = self.__checkindex(i)
+        j = self.__checkindex(j)
+        #....
+        data = self._series[i:j]
+        if isinstance(value, TimeSeries):
+            assert(_timeseriescompat(self[i:j], value))
+            self._series[i:j] = value._series
+        else:
+            self._series[i:j] = value
+        # Don't forget to update the mask !
+        self._mask = self._series._mask
+    #......................................................
+    def __len__(self):
+        if self.ndim == 0:
+            return 0
+        return ndarray.__len__(self)
+    #......................................................
+    def __str__(self):
+        """Returns a string representation of self (w/o the dates...)"""
+        return str(self._series)
+    def __repr__(self):
+        """Calculates the repr representation, using masked for fill if
+           it is enabled. Otherwise fill with fill value.
+        """
+        desc = """\
+timeseries(data  =
+ %(data)s,
+           dates = 
+ %(time)s, 
+           freq  = %(freq)s)
+"""
+        desc_short = """\
+timeseries(data  = %(data)s,
+           dates = %(time)s,
+           freq  = %(freq)s)
+"""
+        if numeric.size(self._dates) > 2 and self.isvalid():
+            timestr = "[%s ... %s]" % (str(self._dates[0]),str(self._dates[-1]))
+        else:
+            timestr = str(self.dates)
+
+        if self.ndim <= 1:
+            return desc_short % {'data': str(self._series),
+                                 'time': timestr,
+                                 'freq': self.freq, }
+        return desc % {'data': str(self._series),
+                       'time': timestr,
+                       'freq': self.freq, }
+    #............................................
+    def _get_mask(self):
+        """Returns the current mask."""
+        return self._series._mask
+    def _set_mask(self, mask):
+        """Sets the mask to `mask`."""
+        mask = make_mask(mask, copy=False, small_mask=True)
+        if mask is not nomask:
+            if mask.size != self._data.size:
+                raise ValueError, "Inconsistent shape between data and mask!"
+            if mask.shape != self._data.shape:
+                mask.shape = self._data.shape
+            self._series._mask = mask  
+        else:
+            self._series._mask = nomask
+    mask = property(fget=_get_mask, fset=_set_mask, doc="Mask")
+ 
+    def ids (self):
+        """Return the ids of the data, dates and mask areas"""
+        return (id(self._series), id(self.dates),)
+        
+    def copy(self):
+        "Returns a copy of the TimeSeries."
+        return TimeSeries(self, copy=True)
+    
+    #------------------------------------------------------
+    @property
+    def series(self):
+        "Returns the series."
+        return self._series
+    @property
+    def dates(self):
+        """Returns the dates"""
+        return self._dates
+    @property
+    def freq(self):
+        """Returns the corresponding frequency."""
+        return self._dates.freq
+#    @property
+    def years(self):
+        """Returns the corresponding years."""
+        return self._dates.years
+#    @property
+    def months(self):
+        """Returns the corresponding months."""
+        return self._dates.months
+#    @property
+    def yeardays(self):
+        """Returns the corresponding days of year."""
+        return self._dates.yeardays
+    day_of_year = yeardays
+#    @property
+    def weekdays(self):
+        """Returns the corresponding days of weeks."""
+        return self._dates.day_of_week
+    day_of_week = weekdays
+    
+    def start_date(self):
+        """Returns the first date of the series."""
+        return self._dates[0]
+#
+    def end_date(self):
+        """Returns the last date of the series."""
+        return self._dates[-1]
+    
+    def isvalid(self):
+        """Returns whether the series has no duplicate/missing dates."""
+        return self._dates.isvalid()
+    
+    def has_missing_dates(self):
+        """Returns whether there's a date gap in the series."""
+        return self._dates.has_missing_dates()
+    
+    def isfull(self):
+        """Returns whether there's no date gap in the series."""
+        return self._dates.isfull()
+    
+    def has_duplicated_dates(self):
+        """Returns whether there are duplicated dates in the series."""
+        return self._dates.has_duplicated_dates()
+    
+    def date_to_index(self, date):
+        "Returns the index corresponding to a given date, as an integer."
+        return self._dates.date_to_index(date) 
+    #.....................................................
+    def asfreq(self, freq=None):
+        "Converts the dates to another frequency."
+        if freq is None:
+            return self
+        return TimeSeries(self._series, dates=self._dates.asfreq(freq))
+    
+    def convert(self, freq, func='auto', position='END', interp=None):
+        "Converts the dates to another frequency, and adapt the data."
+        return convert(self, freq, func=func, position=position, interp=interp)
+        
+##### --------------------------------------------------------------------------
+##--- ... Additional methods ...
+##### --------------------------------------------------------------------------
+class _inplacemethod(object):
+    """Defines a wrapper for inplace arithmetic array methods (iadd, imul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+    """
+    def __init__ (self, binop):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self.f = binop
+        self.obj = None
+    #
+    def __get__(self, obj, objtype=None):
+        "Gets the calling object."
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, other, *args):
+        "Execute the call behavior."
+        instance = self.obj
+        assert(_timeseriescompat(instance,other))
+        func = getattr(instance._series, self.f)    
+        func(other, *args)
+        return instance
+#......................................
+TimeSeries.__iadd__ = _inplacemethod('__iadd__')
+TimeSeries.__iand__ = _inplacemethod('__iand__')
+TimeSeries.__idiv__ = _inplacemethod('__idiv__')
+TimeSeries.__isub__ = _inplacemethod('__isub__')
+TimeSeries.__imul__ = _inplacemethod('__imul__')
+
+
+class _tsmathmethod(object):
+    """Defines a wrapper for arithmetic array methods (add, mul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+    """
+    def __init__ (self, binop):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self.f = binop
+    #
+    def __get__(self, obj, objtype=None):
+        "Gets the calling object."
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, other, *args):
+        "Execute the call behavior."
+        instance = self.obj
+        _dates = instance._dates
+        #tslog.info("_tsmathmethod: series: %s" % instance,)
+        #tslog.info("_tsmathmethod: other  : %s" % other,)
+        func = getattr(instance._series, self.f)    
+        if isinstance(other, TimeSeries):
+            assert(_timeseriescompat(instance, other))
+        return instance.__class__(func(other, *args), dates=_dates,)
+#......................................
+TimeSeries.__add__ = _tsmathmethod('__add__')
+TimeSeries.__radd__ = _tsmathmethod('__add__')
+TimeSeries.__sub__ = _tsmathmethod('__sub__')
+TimeSeries.__rsub__ = _tsmathmethod('__rsub__')
+TimeSeries.__pow__ = _tsmathmethod('__pow__')
+TimeSeries.__mul__ = _tsmathmethod('__mul__')
+TimeSeries.__rmul__ = _tsmathmethod('__mul__')
+TimeSeries.__div__ = _tsmathmethod('__div__')
+TimeSeries.__rdiv__ = _tsmathmethod('__rdiv__')
+TimeSeries.__truediv__ = _tsmathmethod('__truediv__')
+TimeSeries.__rtruediv__ = _tsmathmethod('__rtruediv__')
+TimeSeries.__floordiv__ = _tsmathmethod('__floordiv__')
+TimeSeries.__rfloordiv__ = _tsmathmethod('__rfloordiv__')
+TimeSeries.__eq__ = _tsmathmethod('__eq__')
+TimeSeries.__ne__ = _tsmathmethod('__ne__')
+TimeSeries.__lt__ = _tsmathmethod('__lt__')
+TimeSeries.__le__ = _tsmathmethod('__le__')
+TimeSeries.__gt__ = _tsmathmethod('__gt__')
+TimeSeries.__ge__ = _tsmathmethod('__ge__')
+#................................................
+class _tsarraymethod(object):
+    """Defines a wrapper for basic array methods.
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+If `ondates` is True, the same operation is performed on the `_dates`.
+If `ondates` is False, the `_dates` part remains unchanged.
+    """
+    def __init__ (self, methodname, ondates=False):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self._name = methodname
+        self._ondates = ondates
+    #
+    def __get__(self, obj, objtype=None):
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, *args):
+        "Execute the call behavior."
+        _name = self._name
+        instance = self.obj
+        func_series = getattr(instance._series, _name)
+        if self._ondates:
+            func_dates = getattr(instance._dates, _name)
+            return instance.__class__(func_series(*args), 
+                                      dates=func_dates(*args))
+        else:
+            return instance.__class__(func_series(*args), 
+                                      dates=instance._dates)  
+#TimeSeries.astype = _tsarraymethod('astype')
+TimeSeries.reshape = _tsarraymethod('reshape', ondates=True)
+TimeSeries.copy = _tsarraymethod('copy', ondates=True)
+TimeSeries.compress = _tsarraymethod('compress', ondates=True)
+TimeSeries.ravel = _tsarraymethod('ravel', ondates=True)
+TimeSeries.filled = _tsarraymethod('filled', ondates=False)
+TimeSeries.cumsum = _tsarraymethod('cumsum',ondates=False)
+TimeSeries.cumprod = _tsarraymethod('cumprod',ondates=False)
+TimeSeries.anom = _tsarraymethod('anom',ondates=False)
+
+#......................................
+class _tsaxismethod(object):
+    """Defines a wrapper for array methods working on an axis (mean...).
+When called, returns a ndarray, as the result of the method applied on the series.
+    """
+    def __init__ (self, methodname):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self._name = methodname
+    #
+    def __get__(self, obj, objtype=None):
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, *args, **params):
+        "Execute the call behavior."
+        (_dates, _series) = (self.obj._dates, self.obj._series)
+        func = getattr(_series, self._name)
+        result = func(*args, **params)
+        if _series.ndim < 2 or _dates.size == _series.size:
+            return result
+        else:
+            try:
+                axis = params.get('axis', args[0])
+                if axis in [-1, _series.ndim-1]:
+                    result = TimeSeries(result, dates=_dates)
+            except IndexError:
+                pass
+            return result
+#.......................................
+TimeSeries.sum = _tsaxismethod('sum')
+TimeSeries.prod = _tsaxismethod('prod')
+TimeSeries.mean = _tsaxismethod('mean')
+TimeSeries.var = _tsaxismethod('var')
+TimeSeries.varu = _tsaxismethod('varu')
+TimeSeries.std = _tsaxismethod('std')
+TimeSeries.stdu = _tsaxismethod('stdu')
+
+class _tsblockedmethods(object):
+    """Defines a wrapper for array methods that should be temporarily disabled.
+    """
+    def __init__ (self, methodname):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self._name = methodname
+    #
+    def __get__(self, obj, objtype=None):
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, *args, **params):
+        raise NotImplementedError
+TimeSeries.transpose = _tsarraymethod('transpose', ondates=True)
+TimeSeries.swapaxes = _tsarraymethod('swapaxes', ondates=True)
+
+
+#####---------------------------------------------------------------------------
+#---- --- 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(TimeSeries, self._methodname).__doc__
+        except:
+            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')
+#
+##### ---------------------------------------------------------------------------
+#---- ... Additional methods ...
+##### ---------------------------------------------------------------------------
+def tofile(self, output, sep='\t', format_dates=None):
+    """Writes the TimeSeries to a file.
+
+:Parameters:
+    - `output` (String) : Name or handle of the output file.
+    - `sep` (String) : Column separator *['\t']*.
+    - `format` (String) : Data format *['%s']*.
+    """
+    if not hasattr(output, 'writeline'):
+        ofile = open(output,'w')
+    else:
+        ofile = output
+    if format_dates is None:
+        format_dates = self.dates[0].default_fmtstr()
+    oformat = "%%s%s%s" % (sep,format_dates)
+    for (_dates,_data) in numpy.broadcast(self._dates.ravel().asstrings(), 
+                                          filled(self)):
+        ofile.write('%s\n' % sep.join([oformat % (_dates, _data) ]))
+    ofile.close()
+TimeSeries.tofile = tofile
+
+#............................................
+def asrecords(series):
+    """Returns the masked time series as a recarray.
+Fields are `_dates`, `_data` and _`mask`.
+        """
+    desctype = [('_dates',int_), ('_series',series.dtype), ('_mask', bool_)]
+    flat = series.ravel()
+    _dates = numeric.asarray(flat._dates)
+    if flat.size > 0:
+        return recfromarrays([_dates, flat._data, getmaskarray(flat)],
+                             dtype=desctype,
+                             shape = (flat.size,),  
+                             )
+    else:
+        return recfromarrays([[], [], []], dtype=desctype, 
+                             shape = (flat.size,),  
+                             )
+TimeSeries.asrecords = asrecords
+
+def flatten(series):
+    """Flattens a (multi-) time series to 1D series."""
+    shp_ini = series.shape
+    # Already flat time series....
+    if len(shp_ini) == 1:
+        return series
+    # Folded single time series ..
+    newdates = series._dates.ravel()
+    if series._dates.size == series._series.size:
+        newshape = (series._series.size,)
+    else:
+        newshape = (numeric.asarray(shp_ini[:-1]).prod(), shp_ini[-1])
+    newseries = series._series.reshape(newshape)
+    return time_series(newseries, newdates)
+TimeSeries.flatten = flatten
+
+
+
+#####---------------------------------------------------------------------------
+#---- --- Archiving ---
+#####---------------------------------------------------------------------------
+def _tsreconstruct(baseclass, datesclass, baseshape, basetype, fill_value):
+    """Internal function that builds a new TimeSeries from the information stored
+in a pickle."""
+#    raise NotImplementedError,"Please use timeseries.archive/unarchive instead."""
+    _series = ndarray.__new__(ndarray, baseshape, basetype)
+    _dates = ndarray.__new__(datesclass, baseshape, int_)
+    _mask = ndarray.__new__(ndarray, baseshape, bool_)
+    return baseclass.__new__(baseclass, _series, dates=_dates, mask=_mask, 
+                             dtype=basetype, fill_value=fill_value)
+#    
+def _tsgetstate(a):
+    "Returns the internal state of the TimeSeries, for pickling purposes."
+#    raise NotImplementedError,"Please use timeseries.archive/unarchive instead."""
+    records = a.asrecords()
+    state = (1,
+             a.shape, 
+             a.dtype,
+             a.freq,
+             records.flags.fnc,
+             a.fill_value,
+             records
+             )
+    return state
+#    
+def _tssetstate(a, state):
+    """Restores the internal state of the TimeSeries, for pickling purposes.
+`state` is typically the output of the ``__getstate__`` output, and is a 5-tuple:
+
+    - class name
+    - a tuple giving the shape of the data
+    - a typecode for the data
+    - a binary string for the data
+    - a binary string for the mask.
+    """
+    (ver, shp, typ, frq, isf, flv, rec) = state
+    a.fill_value = flv
+    a._dates = a._dates.__class__(rec['_dates'], freq=frq)
+    (a._dates).__tostr = None
+    _data = rec['_series'].view(typ)
+    _mask = rec['_mask'].view(MA.MaskType)
+    a._series = masked_array(_data, mask=_mask)
+#    a._data.shape = shp
+#    a._dates.shape = shp
+#    a._mask = rec['_mask'].view(MA.MaskType)
+#    a._mask.shape = shp
+#        
+def _tsreduce(a):
+    """Returns a 3-tuple for pickling a MaskedArray."""
+    return (_tsreconstruct,
+            (a.__class__, a.dates.__class__, (0,), 'b', -9999),
+            a.__getstate__())
+#    
+TimeSeries.__getstate__ = _tsgetstate
+TimeSeries.__setstate__ = _tssetstate
+TimeSeries.__reduce__ = _tsreduce
+#TimeSeries.__dump__ = dump
+#TimeSeries.__dumps__ = dumps
+
+
+##### -------------------------------------------------------------------------
+#---- --- TimeSeries creator ---
+##### -------------------------------------------------------------------------
+def time_series(data, dates=None, freq=None, observed=None,
+                start_date=None, end_date=None, length=None, include_last=True,
+                mask=nomask,
+                dtype=None, copy=False, fill_value=None,
+                keep_mask=True, small_mask=True, hard_mask=False):
+    """Creates a TimeSeries object
+    
+:Parameters:
+    `dates` : ndarray
+        Array of dates.
+    `data` : 
+        Array of data.
+    """
+    if dates is None:
+        length = _getdatalength(data)
+        dates = date_array(start_date=start_date, end_date=end_date,
+                           length=length, include_last=include_last, freq=freq)   
+    elif not isinstance(dates, DateArray):
+        dates = date_array(dlist=dates, freq=freq)
+    return TimeSeries(data=data, dates=dates, mask=mask, observed=observed,
+                      copy=copy, dtype=dtype, fill_value=fill_value,
+                      keep_mask=keep_mask, small_mask=small_mask, 
+                      hard_mask=hard_mask,)
+
+
+def isTimeSeries(series):
+    "Returns whether the series is a valid TimeSeries object."
+    return isinstance(series, TimeSeries)
+
+##### --------------------------------------------------------------------------
+#---- ... Additional functions ...
+##### --------------------------------------------------------------------------
+def mask_period(data, start_date=None, end_date=None, 
+                inside=True, include_edges=True, inplace=True):
+    """Returns x as an array masked where dates fall outside the selection period,
+as well as where data are initially missing (masked).
+
+:Parameters:
+    `data` : Timeseries
+        Data to process
+    `start_date` : Date *[None]*
+        Starting date. If None, uses the first date.
+    `end_date` : Date *[None]*
+        Ending date. If None, uses the last date.
+    `inside` : Boolean *[True]*
+        Whether the dates inside the range should be masked. If not, masks outside.
+    `include_edges` : Boolean *[True]*
+        Whether the starting and ending dates should be masked.
+    `inplace` : Boolean *[True]*
+        Whether the data mask should be modified in place. If not, returns a new
+        TimeSeries.
+"""
+    if not isTimeSeries(data):
+        raise ValueError,"Data should be a valid TimeSeries!"
+    # Check the starting date ..............
+    if start_date is None:
+        start_date = data._dates[0]
+    elif isinstance(start_date, str):
+        start_date = Date(data.freq, string=start_date)
+    elif not isDateType(start_date):
+        raise DateError,"Starting date should be a valid date!"
+    start_date = max(start_date, data.dates[0])
+    # Check the ending date ................
+    if end_date is None:
+        end_date = data._dates[-1]
+    elif isinstance(end_date, str):
+        end_date = Date(data.freq, string=end_date)
+    elif not isDateType(end_date):
+        raise DateError,"Starting date should be a valid date!"
+    end_date = min(end_date, data.dates[-1])
+    # Constructs the selection mask .........
+    if inside:
+        if include_edges:
+            selection = (data.dates >= start_date) & (data.dates <= end_date)
+        else:
+            selection = (data.dates > start_date) & (data.dates < end_date)
+    else:
+        if include_edges:
+            selection = (data.dates <= start_date) | (data.dates >= end_date)
+        else:
+            selection = (data.dates < start_date) | (data.dates > end_date)
+    # Process the data:
+    if inplace:
+        if data._mask is nomask:
+            data._mask = selection
+        else:
+            data._mask += selection
+    else:
+        return TimeSeries(data, mask=selection, keep_mask=True)
+    return data
+
+def mask_inside_period(data, start_date=None, end_date=None, 
+                       include_edges=True, inplace=True):
+    """Masks values falling inside a given range of dates."""
+    return mask_period(data, start_date=start_date, end_date=end_date, 
+                       inside=True, include_edges=include_edges, inplace=inplace)
+def mask_outside_period(data, start_date=None, end_date=None, 
+                       include_edges=True, inplace=True):
+    """Masks values falling outside a given range of dates."""
+    return mask_period(data, start_date=start_date, end_date=end_date, 
+                       inside=False, include_edges=include_edges, inplace=inplace)
+#..........................................................
+def adjust_endpoints(a, start_date=None, end_date=None):
+    """Returns a TimeSeries going from `start_date` to `end_date`.
+    If `start_date` and `end_date` both fall into the initial range of dates, 
+    the new series is NOT a copy.
+    """
+    # Series validity tests .....................
+    if not isinstance(a, TimeSeries):
+        raise TypeError,"Argument should be a valid TimeSeries object!"
+    if a.freq == 'U':
+        raise TimeSeriesError, \
+            "Cannot adjust a series with 'Undefined' frequency."
+    if not a.dates.isvalid():
+        raise TimeSeriesError, \
+            "Cannot adjust a series with missing or duplicated dates."
+    # Flatten the series if needed ..............
+    a = a.flatten()
+    shp_flat = a.shape
+    # Dates validity checks .,...................
+    msg = "%s should be a valid Date instance! (got %s instead)"
+    (dstart, dend) = a.dates[[0,-1]]
+    if start_date is None: 
+        start_date = dstart
+        start_lag = 0
+    else:
+        if not isDateType(start_date):
+            raise TypeError, msg % ('start_date', type(start_date))
+        start_lag = start_date - dstart
+    #....          
+    if end_date is None: 
+        end_date = dend
+        end_lag = 0
+    else:
+        if not isDateType(end_date):
+            raise TypeError, msg % ('end_date', type(end_date))
+        end_lag = end_date - dend    
+    # Check if the new range is included in the old one
+    if start_lag >= 0:
+        if end_lag == 0:
+            return a[start_lag:]
+        elif end_lag < 0:
+            return a[start_lag:end_lag]
+    # Create a new series .......................
+    newdates = date_array(start_date=start_date, end_date=end_date)
+    newshape = list(shp_flat)
+    newshape[0] = len(newdates)
+    newshape = tuple(newshape)
+    
+    newdata = masked_array(numeric.empty(newshape, dtype=a.dtype), mask=True)
+    newseries = TimeSeries(newdata, newdates)
+    start_date = max(start_date, dstart)
+    end_date = min(end_date, dend) + 1
+    newseries[start_date:end_date] = a[start_date:end_date]
+    return newseries
+#....................................................................
+def align_series(*series, **kwargs):
+    """Aligns several TimeSeries, so that their starting and ending dates match.
+    Series are resized and filled with mased values accordingly.
+    
+    The function accepts two extras parameters:
+    - `start_date` forces the series to start at that given date,
+    - `end_date` forces the series to end at that given date.
+    By default, `start_date` and `end_date` are set to the smallest and largest
+    dates respectively.    
+    """
+    if len(series) < 2:
+        return series  
+    unique_freqs = numpy.unique([x.freq for x in series])
+    try:
+        common_freq = unique_freqs.item()
+    except ValueError:
+        raise TimeSeriesError, \
+            "All series must have same frequency!"
+    if common_freq == 'U':
+        raise TimeSeriesError, \
+            "Cannot adjust a series with 'Undefined' frequency."
+    valid_states = [x.isvalid() for x in series]
+    if not numpy.all(valid_states):
+        raise TimeSeriesError, \
+            "Cannot adjust a series with missing or duplicated dates."
+    
+    start_date = kwargs.pop('start_date', min([x.start_date() for x in series]))
+    if isinstance(start_date,str):
+        start_date = Date(common_freq, string=start_date)
+    end_date = kwargs.pop('end_date', max([x.end_date() for x in series]))
+    if isinstance(end_date,str):
+        end_date = Date(common_freq, string=end_date)
+    
+    return [adjust_endpoints(x, start_date, end_date) for x in series]
+aligned = align_series
+#....................................................................
+def convert(series, freq, func='auto', position='END', interp=None):
+    """Converts a series to a frequency
+       
+    When converting to a lower frequency, func is a function that acts
+    on a 1-d array and returns a scalar or 1-d array. func should handle
+    masked values appropriately. If func is "auto", then an
+    appropriate function is determined based on the observed attribute
+    of the series. If func is None, then a 2D array is returned, where each
+    column represents the values appropriately grouped into the new frequency.
+    interp and position will be ignored in this case.
+        
+    When converting to a higher frequency, position is 'START' or 'END'
+    and determines where the data point is in each period (eg. if going
+    from monthly to daily, and position is 'END', then each data point is
+    placed at the end of the month). Interp is the method that will be used
+    to fill in the gaps. Valid values are "CUBIC", "LINEAR", "CONSTANT", "DIVIDED",
+    and None.
+        
+    Note: interp currently not implemented
+    """
+    if not isinstance(series,TimeSeries):
+        raise TypeError, "The argument should be a valid TimeSeries!"
+    if not series.isvalid():
+        raise TimeSeriesError, \
+            "Cannot adjust a series with missing or duplicated dates."
+    
+    if position.upper() not in ('END','START'): 
+        raise ValueError("invalid value for position argument: (%s)",str(position))
+    
+    toFreq = corelib.fmtFreq(freq)
+    fromFreq = series.freq
+    start_date = series._dates[0]
+    
+    if fromFreq == toFreq:
+        return series.copy()
+    
+    if series.size == 0:
+        return TimeSeries(series, freq=toFreq, 
+                          start_date=start_date.asfreq(toFreq))
+    if func == 'auto':
+        func = corelib.obs_dict[series.observed]
+
+    tempData = series._series.filled()
+    tempMask = getmaskarray(series)
+
+    cRetVal = cseries.reindex(tempData, fromFreq, toFreq, position, 
+                              int(start_date), tempMask)
+    _values = cRetVal['values']
+    _mask = cRetVal['mask']
+    _startindex = cRetVal['startindex']
+    start_date = Date(freq=toFreq, value=_startindex)
+        
+    tempData = masked_array(_values, mask=_mask)
+
+    if tempData.ndim == 2 and func is not None:
+         tempData = MA.apply_along_axis(func, -1, tempData)
+           
+#    newEnd = series._dates[-1].asfreq(toFreq, "AFTER")
+    
+    newseries = TimeSeries(tempData, freq=toFreq, 
+                           observed=series.observed, 
+                           start_date=start_date)
+    return newseries
+#    return adjust_endpoints(newseries, end_date=newEnd)
+TimeSeries.convert = convert
+#....................................................................
+def fill_missing_dates(data, dates=None, freq=None,fill_value=None):
+    """Finds and fills the missing dates in a time series.
+The data corresponding to the initially missing dates are masked, or filled to 
+`fill_value`.
+    
+:Parameters:
+    `data`
+        Initial array of data.
+    `dates` 
+        Initial array of dates. 
+    `freq` : float *[None]*
+        New date resolutions. If *None*, the initial resolution is used instead.
+    `fill_value` : float *[None]*
+        Default value for missing data. If None, the data are just masked.
+    """
+    freq = corelib.fmtFreq(freq)
+    if freq == 'U':
+        raise ValueError,\
+              "Unable to define a proper date resolution (found %s)." % freq
+    if dates is None:
+        if not isTimeSeries(data):
+            raise InsufficientDateError
+        dates = data._dates
+        freq = dates.freq
+        datad = data._series._data
+        datam = data._series._mask
+#        if fill_value is None:
+#            fill_value = data._fill_value
+    elif not isinstance(dates, DateArray):
+        dates = DateArray(dates, freq)
+        if isinstance(data, MaskedArray):
+            datad = data._data
+            datam = data._mask
+        else:
+            datad = data
+            datam = nomask
+    dflat = dates.asfreq(freq).ravel()
+    n = len(dflat)
+    if not dflat.has_missing_dates():
+        return time_series(data, dflat)
+
+    # ...and now, fill it ! ......
+    (tstart, tend) = dflat[[0,-1]]
+    newdates = date_array(start_date=tstart, end_date=tend, include_last=True)
+    nsize = newdates.size
+    #.............................
+    # Get the steps between consecutive data. 
+    delta = dflat.get_steps()-1
+    gap = delta.nonzero()
+    slcid = numpy.r_[[0,], numpy.arange(1,n)[gap], [n,]]
+    oldslc = numpy.array([slice(i,e) for (i,e) in numpy.broadcast(slcid[:-1],slcid[1:])])
+    addidx = delta[gap].astype(int_).cumsum()
+    newslc = numpy.r_[[oldslc[0]], 
+                      [slice(i+d,e+d) for (i,e,d) in \
+                           numpy.broadcast(slcid[1:-1],slcid[2:],addidx)] 
+                     ]
+    #.............................
+    # Just a quick check
+    vdflat = numeric.asarray(dflat)
+    vnewdates = numeric.asarray(newdates)
+    for (osl,nsl) in zip(oldslc,newslc):
+        assert numpy.equal(vdflat[osl],vnewdates[nsl]).all(),\
+            "Slicing mishap ! Please check %s (old) and %s (new)" % (osl,nsl)
+    #.............................
+    data = MA.asarray(data)
+    newdatad = numeric.empty(nsize, data.dtype)
+    newdatam = numeric.ones(nsize, bool_)
+#    if fill_value is None:
+#        if hasattr(data,'fill_value'):
+#            fill_value = data.fill_value
+#        else:
+#            fill_value = MA.default_fill_value(data)
+    #data = data.filled(fill_value)
+    #....
+    if datam is nomask:
+        for (new,old) in zip(newslc,oldslc):
+            newdatad[new] = datad[old]
+            newdatam[new] = False
+    else:
+        for (new,old) in zip(newslc,oldslc):
+            newdatad[new] = datad[old]
+            newdatam[new] = datam[old]
+    newdata = MA.masked_array(newdatad, mask=newdatam, fill_value=fill_value)    
+    # Get new shape ..............
+    if data.ndim == 1:
+        nshp = (newdates.size,)
+    else:
+        nshp = tuple([-1,] + list(data.shape[1:]))
+    return time_series(newdata.reshape(nshp), newdates)
+
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.DEBUG)
+    from maskedarray.testutils import assert_equal
+    if 0:
+        dlist = ['2007-01-%02i' % i for i in range(1,16)]
+        dates = date_array(dlist)
+        data = masked_array(numeric.arange(15, dtype=float_), mask=[1,0,0,0,0]*3)
+#        btseries = BaseTimeSeries(data._data, dates)
+        tseries = time_series(data, dlist)
+        dseries = numpy.log(tseries)
+    if 1:
+        mlist = ['2005-%02i' % i for i in range(1,13)]
+        mlist += ['2006-%02i' % i for i in range(1,13)]
+        mdata = numpy.arange(24)
+        mser1 = time_series(mdata, mlist, observed='SUMMED')
+        #
+        mlist2 = ['2004-%02i' % i for i in range(1,13)]
+        mlist2 += ['2005-%02i' % i for i in range(1,13)]
+        mser2 = time_series(mdata, mlist2, observed='SUMMED')
+        #
+        today = thisday('m')
+        (malg1,malg2) = aligned(mser1, mser2)
+        
+        C = convert(mser2,'A')
+        D = convert(mser2,'A',func=None)
+        
+    if 0:
+        dlist = ['2007-01-%02i' % i for i in range(1,16)]
+        dates = date_array(dlist)
+        print "."*50+"\ndata"
+        data = masked_array(numeric.arange(15)-6, mask=[1,0,0,0,0]*3)
+        print "."*50+"\nseries"
+        tseries = time_series(data, dlist)
+        
+    if 1:
+        dlist_1 = ['2007-01-%02i' % i for i in range(1,8)]
+        dlist_2 = ['2007-01-%02i' % i for i in numpy.arange(1,28)[::4]]
+        data = masked_array(numeric.arange(7), mask=[1,0,0,0,0,0,0])
+        tseries_1 = time_series(data, dlist_1)
+        tseries_2 = time_series(data, dlist_2)
+        tseries_3 = time_series(data[::-1], dlist_2)
+        
+        try:
+            tseries = tseries_1 + tseries_2
+        except TimeSeriesCompatibilityError:
+            print "I knew it!"
+        tseries = tseries_2 + tseries_3
+        assert_equal(tseries._dates, tseries_3._dates)
+        assert_equal(tseries._mask, [1,0,0,0,0,0,1])
+                
+    if 1:
+        mser3 = time_series(MA.mr_[malg1._series, 100+malg2._series].reshape(2,-1).T, 
+                            dates=malg1.dates)
+        data = mser3._series._data
+

Deleted: trunk/Lib/sandbox/timeseries/corelib.py
===================================================================
--- trunk/Lib/sandbox/timeseries/corelib.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/corelib.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,181 +0,0 @@
-import numpy
-from numpy import ma
-
-
-#############################################################
-############## generally applicable functions ###############
-#############################################################
-def apply_along_axis(func1d, axis, arr, *args):
-    """ Execute func1d(arr[i],*args) where func1d takes 1-D arrays
-        and arr is an N-d array.  i varies so as to apply the function
-        along the given axis for each 1-d subarray in arr.
-        
-        Slightly modified version of the standard numpy version to work with masked arrays.
-    """
-
-    nd = arr.ndim
-    if axis < 0:
-        axis += nd
-    if (axis >= nd):
-        raise ValueError("axis must be less than arr.ndim; axis=%d, rank=%d."
-            % (axis,nd))
-    ind = [0]*(nd-1)
-    i = numpy.zeros(nd,'O')
-    indlist = range(nd)
-    indlist.remove(axis)
-    i[axis] = slice(None,None)
-    outshape = numpy.asarray(arr.shape).take(indlist)
-    i.put(indlist, ind)
-    res = func1d(arr[tuple(i.tolist())],*args)
-    #  if res is a number, then we have a smaller output array
-    if not hasattr(res,'shape') or len(res.shape) == 0:
-        outarr = ma.zeros(outshape,ma.asarray(res).dtype)
-        outarr[ind] = res
-        Ntot = numpy.product(outshape)
-        k = 1
-        while k < Ntot:
-            # increment the index
-            ind[-1] += 1
-            n = -1
-            while (ind[n] >= outshape[n]) and (n > (1-nd)):
-                ind[n-1] += 1
-                ind[n] = 0
-                n -= 1
-            i.put(indlist,ind)
-            res = func1d(arr[tuple(i.tolist())],*args)
-            outarr[ind] = res
-            k += 1
-        return outarr
-    else:
-        Ntot = numpy.product(outshape)
-        holdshape = outshape
-        outshape = list(arr.shape)
-        outshape[axis] = len(res)
-        outarr = ma.zeros(outshape,ma.asarray(res).dtype)
-        outarr[tuple(i.tolist())] = res
-        k = 1
-        while k < Ntot:
-            # increment the index
-            ind[-1] += 1
-            n = -1
-            while (ind[n] >= holdshape[n]) and (n > (1-nd)):
-                ind[n-1] += 1
-                ind[n] = 0
-                n -= 1
-            i.put(indlist, ind)
-            res = func1d(arr[tuple(i.tolist())],*args)
-            outarr[tuple(i.tolist())] = res
-            k += 1
-        return outarr
-
-
-def first_unmasked(m):
-    return __unmasked(m, False, 0)
-    
-def last_unmasked(m):
-    return __unmasked(m, False, -1)
-
-def first_unmasked_val(m):
-    return __unmasked(m, True, 0)
-    
-def last_unmasked_val(m):
-    return __unmasked(m, True, -1)
-
-
-def __unmasked(m, get_val, relpos):
-    
-    if m.mask is ma.nomask:
-        return 0    
-    else:
-        idx = numpy.where(m.mask == False)
-        if len(idx) != 0 and len(idx[0]) != 0:
-            idx = idx[0][relpos]
-        else:
-            idx = None
-
-        if get_val:
-            if idx is None: return ma.masked
-            else: return m[idx]
-        else:
-            return idx
-#############################################################
-
-#converts possible strings for frequency into acceptable values             
-def fmtFreq (freqStr):
-    if freqStr is None:
-        return None    
-    elif freqStr.upper() in ("A","ANNUAL","B","BUSINESS","D","DAILY","M","MONTHLY","Q","QUARTERLY","S","SECONDLY"):
-        return freqStr[0].upper()
-    else:
-        raise ValueError("Invalid frequency: "+str(freqStr))
-        
-
-obsDict = {
-            "UNDEFINED":None,
-            "BEGINNING":first_unmasked_val,
-            "END":last_unmasked_val,
-            "AVERAGED":ma.average,
-            "SUMMED":ma.sum,
-            "MAXIMUM":ma.maximum,
-            "MINIMUM":ma.minimum
-          }
-
-#converts possible strings for observed into acceptable values
-def fmtObserv(obStr):
-
-    obsVals = list(obsDict)
-
-    if obStr is None:
-        return None
-    elif obStr.upper() in obsVals:
-        return obStr.upper()    
-    elif obStr.upper() in ("UNDEFINED", "BEGIN", "END", "AVERAGE", "SUM", "MAX", "MIN"):
-        obStr = obStr.upper()
-        for x in obsVals:
-            if obStr[:2] == x[:2]:
-                return x       
-    else:
-        raise ValueError("Invalid value for observed attribute: "+str(obStr))
-        
-def freqToType(freq):
-    return freqTypeMapping[fmtFreq(freq)]
-
-
-# fake data type for date variables
-class DateSpec:
-    def __init__(self, freq):
-        self.freq = fmtFreq(freq)
-        
-    def __hash__(self): return hash(self.freq)
-    
-    def __eq__(self, other):
-        if hasattr(other, "freq"): return self.freq == other.freq
-        else: return False
-        
-    def __str__(self): return "Date(" + str(self.freq) + ")"
-    
-    
-
-# define custom numpy types.
-# Note: A more robust approach would register these as actual valid numpy types
-# this is just a hack for now
-numpy.dateS = DateSpec("Secondly")
-numpy.dateD = DateSpec("Daily")
-numpy.dateB = DateSpec("Business")
-numpy.dateM = DateSpec("Monthly")
-numpy.dateQ = DateSpec("Quarterly")
-numpy.dateA = DateSpec("Annual")
-
-freqTypeMapping = {
-    'S':numpy.dateS,
-    'D':numpy.dateD,
-    'B':numpy.dateB,
-    'M':numpy.dateM,
-    'Q':numpy.dateQ,
-    'A':numpy.dateA
-}
-
-def isDateType(dtype):
-    if len([x for x in (numpy.dateS,numpy.dateD,numpy.dateB,numpy.dateM,numpy.dateQ,numpy.dateA) if x == dtype]) > 0: return True
-    else: return False
-


Property changes on: trunk/Lib/sandbox/timeseries/examples
___________________________________________________________________
Name: svn:ignore
   + example_alt.py


Modified: trunk/Lib/sandbox/timeseries/examples/example.py
===================================================================
--- trunk/Lib/sandbox/timeseries/examples/example.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/examples/example.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,147 +1,290 @@
-import numpy as np
-import timeseries as ts
-from numpy import ma
-
- 
-# create a time series at business frequency and fill it with random data
-bSer = ts.TimeSeries(np.random.uniform(-100,100,600),dtype=np.float64,freq='B',observed='SUMMED',start_date=ts.thisday('B')-600)
-
-
-# Set negative values to zero.
-bSer[bSer < 0] = 0
-
-
-# Set values occurring on Fridays to 100.
-weekdays = ts.day_of_week(ts.tser(bSer.start_date(),bSer.end_date()))
-bSer[weekdays == 4] = 100
-
-
-"""
-Convert bSer to a monthly frequency series.
-
-The optional func argument to the convert method specifies is a 
-function that acts on a 1-dimension masked array and returns a single
-value.
-"""
-mSer1 = bSer.convert('M',func=ts.average)
-
-
-"""
-If func is None, a "2 dimensional" time series will be returned. In this
-example, the value for each month will be a length 23 masked array (23
-being the max number of business days in a month)
-"""
-mSer1_2d = bSer.convert('M',func=None)
-
-
-"""
-If func is not specified, the observed attribute of the series
-will be used to determine the method. (SUMMED for this example)
-"""
-mSer1_default = bSer.convert('M')
-
-
-"""
-Convert mSer to a business frequency series.
-
-when converting from a lower frequency to a higher frequency, position is one
-of 'START' or 'END', and determines where the data point will be placed in the
-period. In the future, interpolation methods will be supported to fill in the
-resulting masked values.
-"""
-mToB = bSer.convert('M',position='START')
-
-
-# create another monthly frequency series
-mSer2 = ts.TimeSeries(np.random.uniform(-100,100,100),dtype=np.float64,freq='m',observed='END',start_date=ts.thisday('M')-110)
-
-
-"""
-Slicing also supported. The intention is to have indexing behave
-largely in the same manner as regular numpy arrays.
-
-series.adjust_date  convert a date object into the corresponding
-integer for indexing the series
-"""
-sixtyMonthsAgoIdx = mSer2.date_to_index(ts.thisday('m')-60)
-mSer2[sixtyMonthsAgoIdx:sixtyMonthsAgoIdx+10] = 12
-
-
-# Mask the last value in the series
-mSer2[-1] = ts.masked #ts.masked is the same thing as numpy.ma.masked
-
-
-# dates can be used as indices as well
-mSer2[ts.thisday('m')-55] = 400
-
-
-"""
-the tser function makes it easy to index a series over a range of dates
-without worrying about converting the dates to appropriate integers first
-"""
-mSer2[ts.tser(ts.thisday('m')-59, ts.thisday('m')-45)] = 25
-
-
-"""
-Only series of the same frequency and size and same start date
-can be used in the basic operations.
-
-The results are the same as you would expect for masked arrays with the
-basic operations.
-
-start_date and end_date are optional parameters to the aligned function.
-If omitted, the min start_date() and end_date() of all series is used as
-the new boundaries for each series.
-"""
-mSer1, mSer2 = ts.aligned(mSer1, mSer2, start_date=ts.thisday('m')-100, end_date=ts.thisday('m'))
-mAdd1 = mSer1 + mSer2
-
-
-# add the two series together, first filling in masked values with zeros
-mAdd1_filled = mSer1.filled(fill_value=0, ts=True) + mSer2.filled(fill_value=0, ts=True)
-
-# adjust the start and end dates of a series
-newSer = ts.adjust_endpoints(mSer1, start_date=ts.Date(freq='M', year=1954, month=5),  end_date=ts.Date(freq='M', year=2000, month=6))
-
-# calculate the average value in the series. Behaves the same as in ma
-bAverage = ts.average(bSer)
-
-
-# Take the sqrt root of each element in the series (returns a TimeSeries object).
-# Not all functions from ma supported yet, but they are easy to implement
-# for the most part.
-bSqrt = ts.sqrt(bSer)
-
-
-# get the last day of this year, at daily frequency
-dLastDayOfYear = ts.dateOf(ts.thisday('A'),'D','AFTER')
-
-
-# get the first day of this year, at business frequency
-bFirstDayOfYear = ts.dateOf(ts.thisday('A'),'B','BEFORE')
-
-
-# get the last day of the previous quarter, business frequency
-bLastDayOfLastQuarter = ts.dateOf(ts.thisday('Q')-1,'B','AFTER')
-
-
-# dateOf can also go from high frequency to low frequency. In this case, the third parameter has no impact
-aTrueValue = (ts.thisday('Q') == ts.dateOf(ts.thisday('b'),'Q'))
-
-
-# dates of the same frequency can be subtracted (but not added obviously)
-numberOfBusinessDaysPassedThisYear = ts.thisday('b') - bFirstDayOfYear
-
-
-# integers can be added/substracted to/from dates
-fiveDaysFromNow = ts.thisday('d') + 5
-
-
-# get the previous business day, where business day is considered to
-# end at day_end_hour and day_end_min
-pbd = ts.prevbusday(day_end_hour=18,day_end_min=0)
-
-
-# construct a date object explicitly
-myDateQ = ts.Date(freq='Q',year=2004,quarter=3)
-myDateD = ts.Date(freq='D',year=1985,month=10,day=4)
+
+"""
+=== TimeSeries ===
+
+A TimeSeries object is the combination of three ndarrays:
+    
+    - `dates`: DateArray object.
+    - `data` : ndarray.
+    - `mask` : Boolean ndarray, indicating missing or invalid data.
+    
+
+==== Construction ====
+
+To construct a TimeSeries, you can use the class constructor:
+
+>>> TimeSeries(data, dates=None, mask=nomask, 
+               freq=None, observed=None, start_date=None, 
+               dtype=None, copy=False, fill_value=None,
+               keep_mask=True, small_mask=True, hard_mask=False)
+               
+where `data` is a sequence.
+If `dates` is None, a DateArray of the same length as the data is constructed at 
+a `freq` frequency, starting at `start_date`.
+
+Alternatively, you can use the `time_series` function:
+
+
+time_series(data, dates=None, freq=None, 
+            start_date=None, end_date=None, length=None, include_last=True,
+            mask=nomask, dtype=None, copy=False, fill_value=None,
+            keep_mask=True, small_mask=True, hard_mask=False)    
+
+
+Let us construct a series of 600 random elements, starting 600 business days ago, 
+at  a business daily frequency
+
+>>> import numpy as np
+>>> import tseries as ts
+>>> import tsdate as td
+>>> data = np.random.uniform(-100,100,600)
+>>> today = td.thisday('B')
+>>> series = ts.time_series(data, dtype=np.float_, freq='B', observed='SUMMED',
+                            start_date=today-600)
+                            
+Let us set negative values to zero...
+
+>>> series[series<0] = 0
+
+... and the values falling on Fridays to 100
+>>> series[series.day_of_week == 4] = 100
+
+Note that we could also create a temporary array of 'day_of weeks' for the 
+corresponding period, and use it as condition.
+
+>>> weekdays = td.day_of_week(series)
+>>> series[weekdays == 4] = 100
+
+==== Slicing ====
+
+Accessing elements of a TimeSeries works just like slicing
+>>> series[-30:]
+
+But you can also use a date:
+>>> thirtydaysago = today-30
+>>> series[thirtydaysago:]
+
+Or even a string
+>>> series[thirtydaysago.tostring():]
+
+
+==== Conversions ====
+
+To convert a TimeSeries to another frequency, use the `convert` method or function.
+The optional argument `func` must be a function that acts on a 1D masked array 
+and returns a scalar. 
+
+>>> mSer1 = series.convert('M',func=ma.average)
+
+If `func` is not specified, the default value `'auto'` is used instead. In that case,
+the conversion function is estimated from the `observed` attribute of the series.
+For example, if `observed='SUMMED'`, then `func='auto'` is in fact `func=sum`.
+
+>>> mSer1_default = series.convert('M')
+
+If `func` is None, the convert method/function returns a 2D array, where each row 
+corresponds to the new frequency, and the columns to the original data. In our 
+example, convert will return a 2D array with 23 columns, as there are at most
+23 business days per month.
+
+>>> mSer1_2d = series.convert('M',func=None)
+
+When converting from a lower frequency to a higher frequency, an extra argument
+`position` is required. The value of the argument is either 'START' or 'END', 
+and determines where the data point will be placed in the
+period. In the future, interpolation methods will be supported to fill in the
+resulting masked values.
+
+Let us create a second series, this time with a monthly frequency, starting 110
+months ago.
+>>> data = np.random.uniform(-100,100,100).astype(np.float_)
+>>> today = today.asfreq('M') - 110
+>>> mSer2 = ts.TimeSeries(data, freq='m', observed='END',start_date=today)
+>>> sixtymonthsago = today-60
+>>> mSer2[sixtymonthsago:sixtymonthsago+10] = 12
+
+"""
+import numpy as np
+import tseries as ts
+reload(ts)
+import tsdate as td
+reload(td)
+#from numpy import ma
+import maskedarray as ma
+
+data = np.random.uniform(-100,100,600)
+today = td.thisday('B')
+series = ts.time_series(data, dtype=np.float_, freq='B', observed='SUMMED',
+                        start_date=today-600)
+series[series < 0] = 0
+
+#WARNING: The ORIGINAL day_of_week version seemed bugged. 
+#It told me that 2006/12/28 was a Friday.
+weekdays = td.day_of_week(series)
+series[weekdays == 4] = 100
+
+mSer1 = series.convert('M',func=ma.average)
+mSer1_2d = series.convert('M',func=None)
+mSer1_default = series.convert('M')
+mToB = series.convert('M',position='START')
+
+
+# Create another monthly frequency series
+data = np.random.uniform(-100,100,100).astype(np.float_)
+today = today.asfreq('M') - 110
+mSer2 = ts.TimeSeries(data, freq='m', observed='END',start_date=today)
+sixtymonthsago = today-60
+mSer2[sixtymonthsago:sixtymonthsago+10] = 12
+
+
+"""
+==== Operations on TimeSeries ====
+
+If you work with only one TimeSeries, you can use regular commands to process
+the data. For example:
+
+>>> mSer2_log = np.log(mSer2)
+
+Note that invalid values (negative, in that case), are automatically masked.
+Note as well that trying to use the maskedarray command directly is unlikely to 
+work: you would end up with a regular MaskedArray.
+
+When working with multiple series, only series of the same frequency, size and
+starting date can be used in basic operations. The function `align_series` forces
+series to have matching starting and ending dates. By default, the starting date
+will be set to the smallest starting date of the series, and the ending date to
+the largest. [An alias to `align_series` is aligned]
+
+Let's construct a list of months, starting on Jan 2005 and ending on Dec 2006,
+with a gap from Oct 2005 to Dec 2006. 
+
+>>> mlist = ['2005-%02i' % i for i in range(1,10)]
+>>> mlist += ['2006-%02i' % i for i in range(2,13)]
+>>> mdata = numpy.arange(len(mlist))
+>>> mser1 = time_series(mdata, mlist, observed='SUMMED')
+
+Note that the frequency is 'U', for undefined. In fact, you have to specify what
+kind of data is actually missing by forcing a given frequency.
+
+>>> mser1 = mser1.asfreq('M')
+
+Let us check whether there are some duplicated dates (no):
+
+>>> mser1.has_duplicated_dates()
+
+...or missing dates (yes):
+
+>>> mser1.has_missing_dates()
+
+Let us construct a second monthly series, this time without missing dates
+
+>>> mlist2 = ['2004-%02i' % i for i in range(1,13)]
+>>> mlist2 += ['2005-%02i' % i for i in range(1,13)]
+>>> mser2 = time_series(mdata, mlist2, observed='SUMMED')
+
+Let's try to add the two series:
+
+>>> mser3 = mser1 + mser2
+
+That doesn't work, as the series have different starting dates. We need to align 
+them first.
+
+>>> (malg1,malg2) = aligned(mser1, mser2) 
+
+That still doesnt' work, as `malg1` has missing dates. Let us fill it, then: 
+data falling on a date that was missing will be masked.
+
+>>> mser1_filled = fill_missing_dates(mser1)
+>>> (malg1,malg2) = align_series(mser1_filled, mser2) 
+
+Now we can add the two series. Only the data that fall on dates common to the
+original, non-aligned series will be actually added, the others will be masked.
+After all, we are adding masked arrays.
+
+>>> mser3 = malg1 + malg2
+
+We could have filled the initial series first:
+>>> mser3 = filled(malg1,0) + filled(malg2,0)
+
+Alternatively, we can force the series to start/end at some given dates
+
+>>> (malg1,malg2) = aligned(mser1_filled, mser2, 
+>>>                         start_date='2004-06', end_date='2006-06')
+
+"""
+mlist = ['2005-%02i' % i for i in range(1,10)]
+mlist += ['2006-%02i' % i for i in range(2,13)]
+mdata = np.arange(len(mlist))
+mser1 = ts.time_series(mdata, mlist, observed='SUMMED')
+mser1 = mser1.asfreq('M')
+#
+mlist2 = ['2004-%02i' % i for i in range(1,13)]
+mlist2 += ['2005-%02i' % i for i in range(1,13)]
+mser2 = ts.time_series(np.arange(len(mlist2)), mlist2, observed='SUMMED')
+#
+mser1_filled = ts.fill_missing_dates(mser1)
+(malg1,malg2) = ts.aligned(mser1_filled, mser2) 
+mser3 = malg1 + malg2 
+
+"""
+# add the two series together, first filling in masked values with zeros
+mAdd1_filled = mSer1.filled(fill_value=0, ts=True) + mSer2.filled(fill_value=0, ts=True)
+
+# adjust the start and end dates of a series
+newSer = ts.adjust_endpoints(mSer1, start_date=td.Date(freq='M', year=1954, month=5),  end_date=td.Date(freq='M', year=2000, month=6))
+
+# calculate the average value in the series. Behaves the same as in ma
+bAverage = ma.average(series)
+
+
+
+
+
+# Get the last day of this year, at daily frequency
+dLastDayOfYear = td.dateOf(td.thisday('A'),'D','AFTER')
+
+
+# Get the first day of this year, at business frequency
+bFirstDayOfYear = td.dateOf(td.thisday('A'),'B','BEFORE')
+
+# Get the last day of the previous quarter, business frequency
+bLastDayOfLastQuarter = td.dateOf(td.thisday('Q')-1,'B','AFTER')
+
+# dateOf can also go from high frequency to low frequency. In this case, the third parameter has no impact
+aTrueValue = (td.thisday('Q') == td.dateOf(td.thisday('b'),'Q'))
+
+# Dates of the same frequency can be subtracted (but not added obviously)
+numberOfBusinessDaysPassedThisYear = td.thisday('b') - bFirstDayOfYear
+
+# Integers can be added/substracted to/from dates
+fiveDaysFromNow = td.thisday('d') + 5
+
+
+# Get the previous business day, where business day is considered to
+# end at day_end_hour and day_end_min
+pbd = td.prevbusday(day_end_hour=18,day_end_min=0)
+"""
+# ------------------------------------------------------------------------------
+"""
+=== Date construction ===
+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 `mxDate` keyword with an existing mx.DateTime.DateTime object, or 
+      even a datetime.datetime object.
+      
+      >>> td.Date('D', mxDate=mx.DateTime.now())
+      >>> td.Date('D', mxDate=datetime.datetime.now())
+"""
+
+# Construct a sequence of dates at a given frequency


Property changes on: trunk/Lib/sandbox/timeseries/src
___________________________________________________________________
Name: svn:ignore
   + buildit.sh


Added: trunk/Lib/sandbox/timeseries/tcore.py
===================================================================
--- trunk/Lib/sandbox/timeseries/tcore.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/tcore.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,157 @@
+import numpy
+import maskedarray as MA
+
+
+#####---------------------------------------------------------------------------
+#---- --- Generic functions ---
+#####---------------------------------------------------------------------------
+def first_unmasked_val(a):
+    "Returns the first unmasked value in a 1d maskedarray."
+    (i,j) = MA.extras.flatnotmasked_edges(a)
+    return a[i]
+
+def last_unmasked_val(a):
+    "Returns the last unmasked value in a 1d maskedarray."
+    (i,j) = MA.extras.flatnotmasked_edges(a)
+    return a[j]
+
+def reverse_dict(d):
+    "Reverses the keys and values of a dictionary."
+    alt = []
+    tmp = [alt.extend([(w,k) for w in v]) for (k,v) in d.iteritems()]
+    return dict(alt)
+
+
+
+#####---------------------------------------------------------------------------
+#---- --- Option conversion ---
+#####---------------------------------------------------------------------------
+obs_dict = {"UNDEFINED":None,
+            "UNDEF":None,
+            "BEGIN": first_unmasked_val,
+            "BEGINNING": first_unmasked_val,
+            "END": last_unmasked_val,
+            "ENDING": last_unmasked_val,
+            "AVERAGED": MA.average,
+            "AVERAGE": MA.average,
+            "MEAN": MA.average,
+            "SUMMED": MA.sum,
+            "SUM": MA.sum,
+            "MAXIMUM": MA.maximum,
+            "MAX": MA.maximum,
+            "MINIMUM": MA.minimum,
+            "MIN": MA.minimum,
+            }
+obsDict = obs_dict
+#
+def fmtObserv(obStr):
+    "Converts a possible 'Observed' string into acceptable values."
+    if obStr is None:
+        return None
+    elif obStr.upper() in obs_dict.keys():
+        return obStr.upper()    
+    else:
+        raise ValueError("Invalid value for observed attribute: %s " % str(obStr))
+
+
+fmtfreq_dict = {'A': ['ANNUAL','ANNUALLY','YEAR','YEARLY'],
+                'B': ['BUSINESS','BUSINESSLYT'],
+                'D': ['DAY','DAILY',],
+                'H': ['HOUR','HOURLY',],
+                'M': ['MONTH','MONTHLY',],
+                'Q': ['QUARTER','QUARTERLY',],
+                'S': ['SECOND','SECONDLY',],
+                'T': ['MINUTE','MINUTELY',],
+                'W': ['WEEK','WEEKLY',],
+                'U': ['UNDEF','UNDEFINED'],
+                }
+fmtfreq_revdict = reverse_dict(fmtfreq_dict)
+
+def fmtFreq (freqStr):
+    "Converts a possible 'frequency' string to acceptable values."
+    if freqStr is None:
+        return None    
+    elif freqStr.upper() in fmtfreq_dict.keys():
+        return freqStr[0].upper()
+    elif freqStr.upper() in fmtfreq_revdict.keys():
+        return fmtfreq_revdict[freqStr.upper()]
+    else:
+        raise ValueError("Invalid frequency: %s " % str(freqStr))
+        
+class DateSpec:
+    "Fake data type for date variables."
+    def __init__(self, freq):
+        self.freq = fmtFreq(freq)
+        
+    def __hash__(self): 
+        return hash(self.freq)
+    
+    def __eq__(self, other):
+        if hasattr(other, "freq"): 
+            return self.freq == other.freq
+        else: 
+            return False
+    def __str__(self): 
+        return "Date(%s)" % str(self.freq)
+    
+    
+
+# define custom numpy types.
+# Note: A more robust approach would register these as actual valid numpy types
+# this is just a hack for now
+numpy.dateA = DateSpec("Annual")
+numpy.dateB = DateSpec("Business")
+numpy.dateD = DateSpec("Daily")
+numpy.dateH = DateSpec("Hourly")
+numpy.dateM = DateSpec("Monthly")
+numpy.dateQ = DateSpec("Quarterly")
+numpy.dateS = DateSpec("Secondly")
+numpy.dateT = DateSpec("Minutely")
+numpy.dateW = DateSpec("Weekly")
+numpy.dateU = DateSpec("Undefined")
+
+
+freq_type_mapping = {'A': numpy.dateA,
+                     'B': numpy.dateB,
+                     'D': numpy.dateD,
+                     'H': numpy.dateH,
+                     'M': numpy.dateM,
+                     'Q': numpy.dateQ,
+                     'S': numpy.dateS,
+                     'T': numpy.dateT,
+                     'W': numpy.dateW,
+                     'U': numpy.dateU,
+                     }
+        
+def freqToType(freq):
+    return freq_type_mapping[fmtFreq(freq)]
+
+def isDateType(dtype):
+    #TODO: That looks messy. We should simplify that
+    if len([x for x in freq_type_mapping.values() if x == dtype]) > 0: 
+        return True
+    else: 
+        return False
+
+#####---------------------------------------------------------------------------
+#---- --- Misc functions ---
+#####---------------------------------------------------------------------------
+#http://aspn.activestate.com/ASPN/Mail/Message/python-tutor/2302348
+def flatten_sequence(iterable):
+    """Flattens a compound of nested iterables."""
+    itm = iter(iterable)
+    for elm in itm:
+        if hasattr(elm,'__iter__') and not isinstance(elm, basestring):
+            for f in flatten_sequence(elm):
+                yield f
+        else:
+            yield elm
+        
+def flatargs(*args):
+    "Flattens the arguments."
+    if not hasattr(args, '__iter__'):
+        return args
+    else:
+        return flatten_sequence(args)
+        
+


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

Added: trunk/Lib/sandbox/timeseries/tdates.py
===================================================================
--- trunk/Lib/sandbox/timeseries/tdates.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/tdates.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,1089 @@
+"""
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id$
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author$)"
+__version__ = '1.0'
+__revision__ = "$Revision$"
+__date__     = '$Date$'
+
+import datetime
+import itertools
+import warnings
+
+
+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 maskedarray as MA
+#reload(MA)
+
+import mx.DateTime as mxD
+from mx.DateTime.Parser import DateFromString as mxDFromString
+
+import tcore as corelib
+import cseries
+
+
+import logging
+logging.basicConfig(level=logging.DEBUG,
+                    format='%(name)-15s %(levelname)s %(message)s',)
+daflog = logging.getLogger('darray_from')
+dalog = logging.getLogger('DateArray')
+
+#####---------------------------------------------------------------------------
+#---- --- Date Info ---
+#####---------------------------------------------------------------------------
+OriginDate = mxD.Date(1970)
+secondlyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(seconds=1)
+minutelyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(minutes=1)
+hourlyOriginDate = OriginDate - mxD.DateTimeDeltaFrom(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 `mxDate` keyword with an existing mx.DateTime.DateTime object, or 
+      even a datetime.datetime object.
+      
+      >>> td.Date('D', mxDate=mx.DateTime.now())
+      >>> td.Date('D', mxDate=datetime.datetime.now())
+      """
+    def __init__(self, freq, year=None, month=None, day=None, quarter=None, 
+                 hours=None, minutes=None, seconds=None, 
+                 mxDate=None, value=None, string=None):
+        
+        if hasattr(freq, 'freq'):
+            self.freq = corelib.fmtFreq(freq.freq)
+        else:
+            self.freq = corelib.fmtFreq(freq)
+        self.type = corelib.freqToType(self.freq)
+        
+        if value is not None:
+            if self.freq == 'A':
+                self.mxDate = mxD.Date(value, -1, -1)
+            elif self.freq == 'B':
+                valtmp = (value - 1)//5
+                self.mxDate = mxD.DateTimeFromAbsDateTime(value + valtmp*7 - valtmp*5)
+            elif self.freq in ['D','U']:
+                self.mxDate = mxD.DateTimeFromAbsDateTime(value)
+            elif self.freq == 'H':
+                self.mxDate = hourlyOriginDate + mxD.DateTimeDeltaFrom(hours=value)
+            elif self.freq == 'M':
+                self.mxDate = mxD.DateTimeFromAbsDateTime(1) + \
+                              mxD.RelativeDateTime(months=value-1, day=-1)
+            elif self.freq == 'Q':
+                self.mxDate = mxD.DateTimeFromAbsDateTime(1) + \
+                              mxD.RelativeDateTime(years=(value // 4), 
+                                                   month=((value * 3) % 12), day=-1)
+            elif self.freq == 'S':
+                self.mxDate = secondlyOriginDate + mxD.DateTimeDeltaFromSeconds(value)
+            elif self.freq == 'T':
+                self.mxDate = minutelyOriginDate + mxD.DateTimeDeltaFrom(minutes=value)
+            elif self.freq == 'W':
+                self.mxDate = mxD.Date(1,1,7) + \
+                              mxD.RelativeDateTime(weeks=value-1)
+#                              mxD.RelativeDateTime(weeks=value-5./7-1)
+        
+        elif string is not None:
+            self.mxDate = mxDFromString(string)  
+            
+        elif mxDate is not None:
+            if isinstance(mxDate, datetime.datetime):
+                mxDate = mxD.strptime(mxDate.isoformat()[:19], "%Y-%m-%dT%H:%M:%S")
+            self.mxDate = truncateDate(self.freq, mxDate)
+            
+        else:
+            # First, some basic checks.....
+            if year is None:
+                raise InsufficientDateError            
+            if self.freq in ('B', 'D', 'W'):
+                if month is None or day is None: 
+                    raise InsufficientDateError
+            elif self.freq == 'M':
+                if month is None: 
+                    raise InsufficientDateError
+                day = -1
+            elif self.freq == 'Q':
+                if quarter is None: 
+                    raise InsufficientDateError
+                month = quarter * 3
+                day = -1
+            elif self.freq == 'A':
+                month = -1
+                day = -1
+            elif self.freq == 'S':
+                if month is None or day is None or seconds is None: 
+                    raise InsufficientDateError
+                
+            if self.freq in ['A','B','D','M','Q','W']:
+                self.mxDate = truncateDate(self.freq, mxD.Date(year, month, day))
+                if self.freq == 'B':
+                    if self.mxDate.day_of_week in [5,6]:
+                        raise ValueError("Weekend passed as business day")
+            elif self.freq in ['H','S','T']:
+                if not hours:
+                    if not minutes:
+                        if not seconds:
+                            hours = 0
+                        else:
+                            hours = seconds//3600
+                    else:
+                        hours = minutes // 60
+                if not minutes:
+                    if not seconds:
+                        minutes = 0
+                    else:
+                        minutes = (seconds-hours*3600)//60
+                if not seconds:
+                    seconds = 0
+                else:
+                    seconds = seconds % 60
+                self.mxDate = truncateDate(self.freq,
+                                           mxD.Date(year, month, day, 
+                                                    hours, minutes, seconds))
+        self.value = self.__value()
+    # FIXME: Shall we set them as properties ?
+    def day(self):          
+        "Returns the day of month."
+        return self.mxDate.day
+    def day_of_week(self):  
+        "Returns the day of week."
+        return self.mxDate.day_of_week
+    def day_of_year(self):  
+        "Returns the day of year."
+        return self.mxDate.day_of_year
+    def month(self):        
+        "Returns the month."
+        return self.mxDate.month
+    def quarter(self):   
+        "Returns the quarter."   
+        return monthToQuarter(self.mxDate.month)
+    def year(self):         
+        "Returns the year."
+        return self.mxDate.year
+    def second(self):    
+        "Returns the seconds."  
+        return int(self.mxDate.second)
+    def minute(self):     
+        "Returns the minutes."  
+        return int(self.mxDate.minute)
+    def hour(self):         
+        "Returns the hour."
+        return int(self.mxDate.hour)
+    def week(self):
+        "Returns the week."
+        return self.mxDate.iso_week[1]
+ 
+    def __add__(self, other):
+        if isinstance(other, Date):
+            raise FrequencyDateError("Cannot add dates", self.freq, other.freq)
+        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.freq, other.freq)
+            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 subtract dates", \
+                                     self.freq, other.freq)
+        return int(self) == int(other) 
+    
+    def __cmp__(self, other): 
+        if not hasattr(other, 'freq'):
+            return False
+        elif self.freq != other.freq:
+            raise FrequencyDateError("Cannot subtract dates", \
+                                     self.freq, other.freq)
+        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.freq == 'A':
+            val = int(self.mxDate.year)
+        # Business days.
+        elif self.freq == 'B':
+            days = self.mxDate.absdate
+            weeks = days // 7
+#            val = (weeks*5) + (days - weeks*7)
+            val = days - weeks*2
+        # Daily/undefined
+        elif self.freq in ['D', 'U']:
+            val = self.mxDate.absdate
+        # Hourly........
+        elif self.freq == 'H':
+            val = (self.mxDate - hourlyOriginDate).hours
+        # Monthly.......
+        elif self.freq == 'M':
+            val = (self.mxDate.year-1)*12 + self.mxDate.month
+        # Quarterly.....
+        elif self.freq == 'Q':
+            val = (self.mxDate.year-1)*4 + self.mxDate.month//3
+        # Secondly......
+        elif self.freq == 'S':
+            val = (self.mxDate - secondlyOriginDate).seconds
+        # Minutely......
+        elif self.freq == 'T':
+            val = (self.mxDate - minutelyOriginDate).minutes
+        # Weekly........
+        elif self.freq == 'W':
+            #val = int(self.mxDate.year*365.25/7.-1) + self.mxDate.iso_week[1]
+            val = self.mxDate.absdate//7
+        return int(val)
+    #......................................................
+    def default_fmtstr(self):
+        "Defines the default formats for printing Dates."
+        if self.freq == "A":
+            fmt =  "%Y"
+        elif self.freq in ("B","D"):
+            fmt =  "%d-%b-%y"
+        elif self.freq == "M":
+            fmt =  "%b-%Y"
+        elif self.freq == "Q":
+            fmt =  "%YQ%q"
+        elif self.freq == 'H':
+            fmt = "%d-%b-%Y %H:00"
+        elif self.freq == 'T':
+            fmt = "%d-%b-%Y %H:%M"
+        elif self.freq == "S":
+            fmt =  "%d-%b-%Y %H:%M:%S"
+        elif self.freq == "W":
+            fmt =  "%YW%W"
+        else:
+            fmt = "%d-%b-%y"
+        return fmt
+        
+    def strfmt(self, fmt):
+        "Formats the date"
+        qFmt = fmt.replace("%q", "XXXX")
+        tmpStr = self.mxDate.strftime(qFmt)
+        return tmpStr.replace("XXXX", str(self.quarter()))
+            
+    def __str__(self):
+        return self.strfmt(self.default_fmtstr())
+
+    def __repr__(self): 
+        return "<%s : %s>" % (str(self.freq), str(self))
+    #......................................................
+    def toordinal(self):
+        "Returns the date as an ordinal."
+        return self.mxDate.absdays
+
+    def fromordinal(self, ordinal):
+        "Returns the date as an ordinal."
+        return Date(self.freq, mxDate=mxD.DateTimeFromAbsDays(ordinal))
+    
+    def tostring(self):
+        "Returns the date as a string."
+        return str(self)
+    
+    def toobject(self):
+        "Returns the date as itself."
+        return self
+    
+    def asfreq(self, toFreq, relation='before'):
+        """Converts the date to a new frequency."""
+        return asfreq(self, toFreq, relation)
+    
+    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 truncateDate(freq, mxDate):
+    """Chops off the irrelevant information from the mxDate passed in."""
+    freq = corelib.fmtFreq(freq)
+    if freq == 'A':
+        return mxD.Date(mxDate.year)
+    elif freq == 'Q':
+        return mxD.Date(mxDate.year, monthToQuarter(mxDate.month)*3)
+    elif freq == 'M':
+        return mxD.Date(mxDate.year, mxDate.month)
+    elif freq == 'W':
+        d = mxDate.absdate
+        return mxD.DateTimeFromAbsDateTime(d + (7 - d % 7) % 7 - 1)
+    elif freq in ('B', 'D'):
+        if freq == 'B' and mxDate.day_of_week in [5,6]:
+            raise ValueError("Weekend passed as business day")
+        return mxD.Date(mxDate.year, mxDate.month, mxDate.day)
+    elif freq == 'H':
+        return mxD.Date(mxDate.year, mxDate.month, mxDate.day, \
+                        mxDate.hour)
+    elif freq == 'T':
+        return mxD.Date(mxDate.year, mxDate.month, mxDate.day, \
+                        mxDate.hour, mxDate.minute)
+    else:
+        return mxDate
+    
+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`."
+    freq = corelib.fmtFreq(freq)
+    tempDate = mxD.now()
+    # if it is Saturday or Sunday currently, freq==B, then we want to use Friday
+    if freq == 'B' and tempDate.day_of_week >= 5:
+        tempDate -= (tempDate.day_of_week - 4)
+    if freq in ('B','D','H','S','T','W'):
+        return Date(freq, mxDate=tempDate)
+    elif freq == 'M':
+        return Date(freq, year=tempDate.year, month=tempDate.month)
+    elif freq == 'Q':
+        return Date(freq, year=tempDate.year, quarter=monthToQuarter(tempDate.month))
+    elif freq == '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 = mxD.localtime()
+    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.fmtFreq(toFreq)
+    _rel = relation.upper()[0]
+#    if _rel not in ['B', 'A']:
+#        msg = "Invalid relation '%s': Should be in ['before', 'after']"
+#        raise ValueError, msg % relation
+#    elif _rel == 'B':
+#        before = True
+#    else:
+#        before = False
+
+#    if not isDateType(date):
+    if not isinstance(date, Date):
+        raise DateError, "Date should be a valid Date instance!"
+
+    if date.freq == toFreq:
+        return date
+    else:
+        value = cseries.asfreq(numeric.asarray(date.value), date.freq, toFreq, _rel)
+        if value > 0:
+            return Date(freq=toFreq, value=value)
+        else:
+            return None
+#    # Convert to annual ....................
+#    elif toFreq == 'A':
+#        return Date(freq='A', year=date.year())
+#    # Convert to quarterly .................
+#    elif toFreq == 'Q':
+#        if date.freq == 'A':
+#            if before: 
+#                return Date(freq='A', year=date.year(), quarter=1)
+#            else: 
+#                return Date(freq='A', year=date.year(), quarter=4)
+#        else:
+#            return Date(freq='Q', year=date.year(), quarter=date.quarter())
+#    # Convert to monthly....................
+#    elif toFreq == 'M':
+#        if date.freq == 'A':
+#            if before: 
+#                return Date(freq='M', year=date.year(), month=1)
+#            else: 
+#                return Date(freq='M', year=date.year(), month=12)
+#        elif date.freq == 'Q':
+#            if before: 
+#                return dateOf(date-1, 'M', "AFTER")+1
+#            else: 
+#                return Date(freq='M', year=date.year(), month=date.month())
+#        else:
+#            return Date(freq='M', year=date.year(), month=date.month())
+#    # Convert to weekly ....................
+#    elif toFreq == 'W':
+#        if date.freq == 'A':
+#            if before: 
+#                return Date(freq='W', year=date.year(), month=1, day=1)
+#            else: 
+#                return Date(freq='W', year=date.year(), month=12, day=-1)
+#        elif date.freq in ['Q','M']:
+#            if before: 
+#                return dateOf(date-1, 'W', "AFTER")+1
+#            else: 
+#                return Date(freq='W', year=date.year(), month=date.month())
+#        else:
+#            val = date.weeks() + int(date.year()*365.25/7.-1)
+#            return Date(freq='W', value=val)
+#    # Convert to business days..............
+#    elif toFreq == 'B':
+#        if date.freq in ['A','Q','M','W']:
+#            if before: 
+#                return dateOf(dateOf(date, 'D'), 'B', "AFTER")
+#            else: 
+#                return dateOf(dateOf(date, 'D', "AFTER"), 'B', "BEFORE")
+#        elif date.freq == 'D':
+#            # BEFORE result: preceeding Friday if date is a weekend, same day otherwise
+#            # AFTER result: following Monday if date is a weekend, same day otherwise
+#            tempDate = date.mxDate
+#            if before:
+#                if tempDate.day_of_week >= 5: 
+#                    tempDate -= (tempDate.day_of_week - 4)
+#            else:
+#                if tempDate.day_of_week >= 5: 
+#                    tempDate += 7 - tempDate.day_of_week
+#            return Date(freq='B', mxDate=tempDate)
+#        else: 
+#            if before: 
+#                return dateOf(dateOf(date, 'D'), 'B', "BEFORE")
+#            else: 
+#                return dateOf(dateOf(date, 'D'), 'B', "AFTER")
+#    # Convert to day .......................
+#    elif toFreq == 'D':
+#        # ...from annual
+#        if date.freq == 'A':
+#            if before: 
+#                return Date(freq='D', year=date.year(), month=1, day=1)
+#            else: 
+#                return Date(freq='D', year=date.year(), month=12, day=31) 
+#        # ...from quarter
+#        elif date.freq == 'Q':
+#            if before: 
+#                return dateOf(date-1, 'D', "AFTER")+1
+#            else: 
+#                return Date(freq='D', year=date.year(), month=date.month(), 
+#                            day=date.day())
+#        # ...from month
+#        elif date.freq == 'M':
+#            if before:
+#                return Date(freq='D', year=date.year(), month=date.month(), day=1)
+#            else:
+#                (mm,yy) = (date.month(), date.year())
+#                if date.month() == 12:
+#                    (mm, yy) = (1, yy + 1)
+#                else:
+#                    mm = mm + 1
+#                return Date('D', year=yy, month=mm, day=1)-1
+#        # ...from week
+#        elif date.freq == 'W':
+#            if before:
+#                return Date(freq='D', year=date.year(), month=date.month(), 
+#                            day=date.day())
+#            else:
+#                ndate = date + 1 
+#                return Date(freq='D', year=ndate.year(), month=ndate.month(), 
+#                            day=ndate.day()) 
+#        # ...from a lower freq
+#        else:
+#            return Date('D', year=date.year(), month=date.month(), day=date.day())  
+#    #Convert to hour........................
+#    elif toFreq == 'H':
+#        if date.freq in ['A','Q','M','W']:
+#            if before: 
+#                return dateOf(dateOf(date, 'D', "BEFORE"), 'H', "BEFORE")
+#            else: 
+#                return dateOf(dateOf(date, 'D', "AFTER"), 'H', "AFTER")
+#        if date.freq in ['B','D']:
+#            if before: 
+#                return Date(freq='H', year=date.year(), month=date.month(), 
+#                            day=date.day(), hours=0)
+#            else: 
+#                return Date(freq='H', year=date.year(), month=date.month(), 
+#                            day=date.day(), hours=23)
+#        else: 
+#            return Date(freq='H', year=date.year(), month=date.month(), 
+#                        day=date.day(), hours=date.hour())
+#    #Convert to second......................
+#    elif toFreq == 'T':
+#        if date.freq in ['A','Q','M','W']:
+#            if before: 
+#                return dateOf(dateOf(date, 'D', "BEFORE"), 'T', "BEFORE")
+#            else: 
+#                return dateOf(dateOf(date, 'D', "AFTER"), 'T', "AFTER")
+#        elif date.freq in ['B','D','H']:
+#            if before: 
+#                return Date(freq='T', year=date.year(), month=date.month(), 
+#                            day=date.day(), minutes=0)
+#            else: 
+#                return Date(freq='T', year=date.year(), month=date.month(), 
+#                            day=date.day(), minutes=24*60-1)
+#        else:
+#            return Date(freq='H', year=date.year(), month=date.month(), 
+#                        day=date.day(), hours=date.hour(), minutes=date.minute())
+#    #Convert to minute......................
+#    elif toFreq == 'S':
+#        if date.freq in ['A','Q','M','W']:
+#            if before: 
+#                return dateOf(dateOf(date, 'D', "BEFORE"), 'S', "BEFORE")
+#            else: 
+#                return dateOf(dateOf(date, 'D', "AFTER"), 'S', "AFTER")
+#        elif date.freq in ['B','D']:
+#            if before: 
+#                return Date(freq='S', year=date.year(), month=date.month(), 
+#                            day=date.day(), seconds=0)
+#            else: 
+#                return Date(freq='S', year=date.year(), month=date.month(), 
+#                            day=date.day(), seconds=24*60*60-1)
+            
+def isDate(data):
+    "Returns whether `data` is an instance of Date."
+    return isinstance(data, Date)
+
+            
+#####---------------------------------------------------------------------------
+#---- --- DateArray ---
+#####--------------------------------------------------------------------------- 
+ufunc_dateOK = ['add','subtract',
+                'equal','not_equal','less','less_equal', 'greater','greater_equal',
+                'isnan']
+
+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.    
+    """
+    def __new__(cls, dates=None, freq='U', copy=False):
+        #dalog.info("__new__ received %s [%i]" % (type(dates), numpy.size(dates)))
+        if isinstance(dates, DateArray):
+            #dalog.info("__new__ sends %s as %s" % (type(dates), cls))
+            cls.__defaultfreq = dates.freq
+            if not copy:
+                return dates.view(cls)
+            return dates.copy().view(cls)
+        else:
+            _dates = numeric.asarray(dates, dtype=int_)
+            if copy:
+                _dates = _dates.copy()
+            #dalog.info("__new__ sends %s as %s" % (type(_dates), cls))
+            if freq is None:
+                freq = 'U'
+            cls.__defaultfreq = corelib.fmtFreq(freq)
+            (cls.__toobj, cls.__toord, cls.__tostr) = (None, None, None)
+            (cls.__steps, cls.__full, cls.__hasdups) = (None, None, None)
+            return _dates.view(cls)
+    
+    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):
+        #dalog.info("__array_finalize__ received %s" % type(obj))
+        if hasattr(obj, 'freq'):
+            self.freq = obj.freq
+        else:
+            self.freq = self.__defaultfreq
+        #dalog.info("__array_finalize__ sends %s" % type(self))
+    
+    def __getitem__(self, index):
+        #dalog.info("__getitem__ got  index %s (%s)"%(index, type(index)))
+        if isinstance(index, Date):
+            index = self.find_dates(index)
+        elif numeric.asarray(index).dtype.kind == 'O':
+            try:
+                index = self.find_dates(index)       
+            except AttributeError:
+                pass     
+        r = ndarray.__getitem__(self, index)
+        if r.size == 1:
+            # Only one element, and it's not a scalar: we have a DateArray of size 1
+            if len(r.shape) > 0:
+                r = r.item()
+            return Date(self.freq, value=r)
+        else:
+            return r
+        
+    def __repr__(self):
+        return ndarray.__repr__(self)
+    #......................................................
+    @property
+    def years(self):
+        "Returns the years."
+        return numeric.asarray([d.year() for d in self], dtype=int_)
+    @property
+    def months(self):
+        "Returns the months."
+        return numeric.asarray([d.month() for d in self], dtype=int_)
+    @property
+    def day_of_year(self):
+        "Returns the days of years."
+        return numeric.asarray([d.day_of_year() for d in self], dtype=int_)
+    yeardays = day_of_year
+    @property
+    def day_of_week(self):
+        "Returns the days of week."
+        return numeric.asarray([d.day_of_week() for d in self], dtype=int_)
+    #.... Conversion methods ....................
+#    def toobject(self):
+#        "Converts the dates from ordinals to Date objects."
+#        # Note: we better try to cache the result
+#        if self.__toobj is None:
+##            toobj = numeric.empty(self.size, dtype=object_)
+##            toobj[:] = [Date(self.freq, value=d) for d in self]
+##            self.__toobj = toobj
+#            self.__toobj = self
+#        return self.__toobj
+    #
+    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.__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.__toord = toord
+        return self.__toord
+    #
+    def tostring(self):
+        "Converts the dates to strings."
+        # Note: we better cache the result
+        if self.__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.__tostr = tostr
+        return self.__tostr
+    #
+#    def asfreq_ini(self, freq=None):
+#        "Converts the dates to another frequency."
+#        # Note: As we define a new object, we don't need caching
+#        if freq is None:
+#            return self
+#        freq = corelib.fmtFreq(freq)
+#        if freq == self.freq:
+#            return self        
+#        if self.isvalid():
+#            new = numeric.arange(self.size, dtype=int_)
+#            new += self[0].asfreq(freq).value
+#        else:
+#            new = numpy.fromiter((d.asfreq(freq).value for d in self),
+#                                 dtype=float_)
+#        return DateArray(new, freq=freq)
+    
+    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:
+            return self
+        freq = corelib.fmtFreq(freq)
+        if freq == self.freq:
+            return self        
+        _rel = relation.upper()[0]
+        new = cseries.asfreq(numeric.asarray(self), self.freq, freq, _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 ValueError, "Date out of bounds!"
+        return c  
+#    def find_dates_alt(self, *dates):
+#        "Returns the indices corresponding to given dates, as an array."
+#        ifreq = self.freq
+#        c = numpy.zeros(self.shape, bool_)
+#        dates = date_array([d for d in corelib.flatargs(*dates)]).asfreq(ifreq)
+#        for d in numeric.asarray(dates):
+#            c += (self == d)
+#        c = c.nonzero()
+#        if fromnumeric.size(c) == 0:
+#            raise ValueError, "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 ValueError, "Date out of bounds!"
+            return index
+        else:
+            index_asarray = (self == date.value).nonzero()
+            if fromnumeric.size(index_asarray) == 0:
+                raise ValueError, "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 daily!")
+        if self.__steps is None:
+            val = numeric.asarray(self).ravel()
+            if val.size > 0:
+                steps = val[1:] - val[:-1]
+                if self.__full is None:
+                    self.__full = (steps.max() == 1)
+                if self.__hasdups is None:
+                    self.__hasdups = (steps.min() == 0)
+            else:
+                self.__full = True
+                self.__hasdups = False
+            self.__steps = steps
+        return self.__steps
+    
+    def has_missing_dates(self):
+        "Returns whether the DateArray have missing dates."
+        if self.__full is None:
+            steps = self.get_steps()
+        return not(self.__full)
+    
+    def isfull(self):
+        "Returns whether the DateArray has no missing dates."
+        if self.__full is None:
+            steps = self.get_steps()
+        return self.__full
+    
+    def has_duplicated_dates(self):
+        "Returns whether the DateArray has duplicated dates."
+        if self.__hasdups is None:
+            steps = self.get_steps()
+        return self.__hasdups
+    
+    def isvalid(self):
+        "Returns whether the DateArray is valid: no missing/duplicated dates."
+        return  (self.isfull() and not self.has_duplicated_dates())
+    #......................................................
+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
+        #dalog.info('__datearithmetics got method %s' % methodname)
+    #
+    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'
+        #dalog.info('__datearithmetics got other %s' % type(other))
+        method = getattr(super(DateArray,instance), self.methodname)
+        if isinstance(other, DateArray):
+            if other.freq != freq:
+                raise FrequencyDateError("Cannot operate on dates", \
+                                         freq, other.freq)
+#            other = 
+        elif isinstance(other, Date):
+            if other.freq != freq:
+                raise FrequencyDateError("Cannot operate on dates", \
+                                         freq, other.freq)
+            other = other.value
+            #dalog.info('__datearithmetics got other %s' % type(other))
+        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)
+#............................
+DateArray.__add__ = _datearithmetics('__add__', asdates=True)
+DateArray.__radd__ = _datearithmetics('__add__', asdates=True)
+DateArray.__sub__ = _datearithmetics('__sub__', asdates=True)
+DateArray.__rsub__ = _datearithmetics('__rsub__', asdates=True)
+DateArray.__le__ = _datearithmetics('__le__', asdates=False)
+DateArray.__lt__ = _datearithmetics('__lt__', asdates=False)
+DateArray.__ge__ = _datearithmetics('__ge__', asdates=False)
+DateArray.__gt__ = _datearithmetics('__gt__', asdates=False)
+DateArray.__eq__ = _datearithmetics('__eq__', asdates=False)
+DateArray.__ne__ = _datearithmetics('__ne__', asdates=False)
+
+#####---------------------------------------------------------------------------
+#---- --- 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()
+    # Case #1: dates as strings .................
+    if dlist.dtype.kind == 'S':
+        #...construct a list of ordinals
+        ords = numpy.fromiter((mxDFromString(s).absdays 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 ['i','f']:
+        #...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), float_)
+        #...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, mxDate=m) for m in dlist]
+        #...as datetime objects
+        elif hasattr(dlist[0], 'toordinal'):
+            ords = numpy.fromiter((d.toordinal() for d in dlist), float_)
+            if freq is None:
+                freq = guess_freq(ords)
+            dates = [Date(freq, mxDate=mxD.DateTimeFromAbsDays(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.fmtFreq(freq)
+    # Case #1: we have a list ...................
+    if dlist is not None:
+        # Already a DateArray....................
+        if isinstance(dlist, DateArray):
+            if freq != dlist.freq:
+                return dlist.asfreq(freq)
+            else:
+                return dlist
+        return _listparser(dlist, freq)
+    # Case #2: we have a starting date ..........
+    if start_date is None:
+        raise InsufficientDateError
+#    if not isDateType(start_date):
+    if not isinstance(start_date, Date):
+        raise DateError, "Starting date should be a valid Date instance!"
+    # Check if we have an end_date
+    if end_date is None:
+        if length is None:
+            raise ValueError,"No length precised!"
+    else:
+        if not isinstance(end_date, Date):
+            raise DateError, "Ending date should be a valid Date instance!"
+#        assert(isDateType(end_date),
+#               "Starting date should be a valid Date instance!")
+        length = 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:
+            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')
+
+
+################################################################################


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

Added: trunk/Lib/sandbox/timeseries/tests/test_dates.py
===================================================================
--- trunk/Lib/sandbox/timeseries/tests/test_dates.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/tests/test_dates.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,144 @@
+# pylint: disable-msg=W0611, W0612, W0511,R0201
+"""Tests suite for MaskedArray.
+Adapted from the original test_ma by Pierre Gerard-Marchant
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: test_core.py 59 2006-12-22 23:58:11Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 59 $"
+__date__     = '$Date: 2006-12-22 18:58:11 -0500 (Fri, 22 Dec 2006) $'
+
+import types
+import datetime
+
+import numpy
+import numpy.core.fromnumeric  as fromnumeric
+import numpy.core.numeric as numeric
+from numpy.testing import NumpyTest, NumpyTestCase
+from numpy.testing.utils import build_err_msg
+
+import maskedarray
+from maskedarray import masked_array
+
+import maskedarray.testutils
+from maskedarray.testutils import assert_equal, assert_array_equal
+
+import tdates
+reload(tdates)
+from tdates import date_array_fromlist, Date, DateArray, mxDFromString
+
+class test_creation(NumpyTestCase):
+    "Base test class for MaskedArrays."
+    
+    def __init__(self, *args, **kwds):
+        NumpyTestCase.__init__(self, *args, **kwds)
+    
+    def test_fromstrings(self):
+        "Tests creation from list of strings"
+        dlist = ['2007-01-%02i' % i for i in range(1,15)]
+        # A simple case: daily data
+        dates = date_array_fromlist(dlist, 'D')
+        assert_equal(dates.freq,'D')
+        assert(dates.isfull())
+        assert(not dates.has_duplicated_dates())
+        assert_equal(dates, 732677+numpy.arange(len(dlist)))
+        # as simple, but we need to guess the frequency this time
+        dates = date_array_fromlist(dlist, 'D')
+        assert_equal(dates.freq,'D')
+        assert(dates.isfull())
+        assert(not dates.has_duplicated_dates())
+        assert_equal(dates, 732677+numpy.arange(len(dlist)))
+        # Still daily data, that we force to month
+        dates = date_array_fromlist(dlist, 'M')
+        assert_equal(dates.freq,'M')
+        assert(not dates.isfull())
+        assert(dates.has_duplicated_dates())
+        assert_equal(dates, [24073]*len(dlist))
+        # Now, for monthly data
+        dlist = ['2007-%02i' % i for i in range(1,13)]
+        dates = date_array_fromlist(dlist, 'M')
+        assert_equal(dates.freq,'M')
+        assert(dates.isfull())
+        assert(not dates.has_duplicated_dates())
+        assert_equal(dates, 24073 + numpy.arange(12))
+        # Monthly data  w/ guessing
+        dlist = ['2007-%02i' % i for i in range(1,13)]
+        dates = date_array_fromlist(dlist, )
+        assert_equal(dates.freq,'M')
+        assert(dates.isfull())
+        assert(not dates.has_duplicated_dates())
+        assert_equal(dates, 24073 + numpy.arange(12))
+        
+    def test_fromstrings_wmissing(self):
+        "Tests creation from list of strings w/ missing dates"
+        dlist = ['2007-01-%02i' % i for i in (1,2,4,5,7,8,10,11,13)]
+        dates = date_array_fromlist(dlist)
+        assert_equal(dates.freq,'U')
+        assert(not dates.isfull())
+        assert(not dates.has_duplicated_dates())
+        assert_equal(dates.tovalue(),732676+numpy.array([1,2,4,5,7,8,10,11,13]))
+        #
+        ddates = date_array_fromlist(dlist, 'D')
+        assert_equal(ddates.freq,'D')
+        assert(not ddates.isfull())
+        assert(not ddates.has_duplicated_dates())
+        #
+        mdates = date_array_fromlist(dlist, 'M')
+        assert_equal(mdates.freq,'M')
+        assert(not dates.isfull())
+        assert(mdates.has_duplicated_dates())
+        #
+    
+    def test_fromsobjects(self):
+        "Tests creation from list of objects."
+        dlist = ['2007-01-%02i' % i for i in (1,2,4,5,7,8,10,11,13)]
+        dates = date_array_fromlist(dlist)
+        dobj = [datetime.datetime.fromordinal(d) for d in dates.toordinal()]
+        odates = date_array_fromlist(dobj)
+        assert_equal(dates,odates)
+        dobj = [mxDFromString(d) for d in dlist]
+        odates = date_array_fromlist(dobj)
+        assert_equal(dates,odates)
+
+
+class test_methods(NumpyTestCase):
+    "Base test class for MaskedArrays."
+    
+    def __init__(self, *args, **kwds):
+        NumpyTestCase.__init__(self, *args, **kwds)       
+        
+    def test_getitem(self):
+        "Tests getitem"
+        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])
+        assert_equal(mdates[lag], mdates[0])
+        lag = mdates.find_dates(Date('M',value=24080))
+        assert_equal(mdates[lag], mdates[5])
+        # Using several dates
+        lag = mdates.find_dates(Date('M',value=24073), Date('M',value=24084))
+        assert_equal(mdates[lag], 
+                     DateArray([mdates[0], mdates[-1]], freq='M'))
+        assert_equal(mdates[[mdates[0],mdates[-1]]], mdates[lag])
+        #
+        assert_equal(mdates>=mdates[-4], [0,0,0,0,0,0,1,1,1,1])
+        dlist = ['2006-%02i' % i for i in range(1,5)+range(7,13)]
+        mdates = date_array_fromlist(dlist).asfreq('M')
+
+        
+    def test_getsteps(self):
+        dlist = ['2007-01-%02i' %i for i in (1,2,3,4,8,9,10,11,12,15)]
+        ddates = date_array_fromlist(dlist)
+        assert_equal(ddates.get_steps(), [1,1,1,4,1,1,1,1,3])
+
+###############################################################################
+#------------------------------------------------------------------------------
+if __name__ == "__main__":
+    NumpyTest().run()        
\ No newline at end of file

Copied: trunk/Lib/sandbox/timeseries/tests/test_timeseries.py (from rev 2488, trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py)
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py	2007-01-04 16:42:30 UTC (rev 2488)
+++ trunk/Lib/sandbox/timeseries/tests/test_timeseries.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,283 @@
+# pylint: disable-msg=W0611, W0612, W0511,R0201
+"""Tests suite for MaskedArray.
+Adapted from the original test_ma by Pierre Gerard-Marchant
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id$
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author$)"
+__version__ = '1.0'
+__revision__ = "$Revision$"
+__date__     = '$Date$'
+
+import types
+
+import numpy as N
+from numpy import bool_, complex_, float_, int_, object_
+import numpy.core.fromnumeric  as fromnumeric
+import numpy.core.numeric as numeric
+from numpy.testing import NumpyTest, NumpyTestCase
+from numpy.testing.utils import build_err_msg
+
+import maskedarray
+from maskedarray import masked_array, masked, nomask
+
+import maskedarray.testutils
+#reload(maskedarray.testutils)
+from maskedarray.testutils import assert_equal, assert_array_equal
+
+#import tdates
+##reload(tdates)
+#from tdates import date_array_fromlist
+import tseries
+#reload(tseries)
+from tseries import Date, date_array_fromlist
+from tseries import time_series, TimeSeries, adjust_endpoints, mask_period
+
+class test_creation(NumpyTestCase):
+    "Base test class for MaskedArrays."
+    def __init__(self, *args, **kwds):
+        NumpyTestCase.__init__(self, *args, **kwds)
+        dlist = ['2007-01-%02i' % i for i in range(1,16)]
+        dates = date_array_fromlist(dlist)
+        data = masked_array(numeric.arange(15), mask=[1,0,0,0,0]*3)
+        self.d = (dlist, dates, data)
+
+    def test_fromlist (self):
+        "Base data definition."
+        (dlist, dates, data) = self.d
+        series = time_series(data, dlist)
+        assert(isinstance(series, TimeSeries))
+        assert_equal(series._mask, [1,0,0,0,0]*3)
+        assert_equal(series._series, data)
+        assert_equal(series._dates, date_array_fromlist(dlist))
+        assert_equal(series.freq, 'D')
+
+    def test_fromrange (self):
+        "Base data definition."
+        (dlist, dates, data) = self.d
+        series = time_series(data, start_date=Date('D',value=dates[0]),
+                             length=15)
+        assert(isinstance(series, TimeSeries))
+        assert_equal(series._mask, [1,0,0,0,0]*3)
+        assert_equal(series._series, data)
+        assert_equal(series._dates, dates)
+        assert_equal(series.freq, 'D')
+
+    def test_fromseries (self):
+        "Base data definition."
+        (dlist, dates, data) = self.d
+        series = time_series(data, dlist)
+        dates = dates+15
+        series = time_series(series, dates)
+        assert(isinstance(series, TimeSeries))
+        assert_equal(series._mask, [1,0,0,0,0]*3)
+        assert_equal(series._series, data)
+        assert_equal(series._dates, dates)
+        assert_equal(series.freq, 'D')
+#...............................................................................
+
+class test_arithmetics(NumpyTestCase):
+    "Some basic arithmetic tests"
+    def __init__(self, *args, **kwds):
+        NumpyTestCase.__init__(self, *args, **kwds)
+        dlist = ['2007-01-%02i' % i for i in range(1,16)]
+        dates = date_array_fromlist(dlist)
+        data = masked_array(numeric.arange(15), mask=[1,0,0,0,0]*3)
+        self.d = (time_series(data, dlist), data)
+        
+    def test_intfloat(self):
+        "Test arithmetic timeseries/integers"
+        (series, data) =self.d
+        #
+        nseries = series+1
+        assert(isinstance(nseries, TimeSeries))
+        assert_equal(nseries._mask, [1,0,0,0,0]*3)
+        assert_equal(nseries._series, data+1)
+        assert_equal(nseries._dates, series._dates)
+        #        
+        nseries = series-1
+        assert(isinstance(nseries, TimeSeries))
+        assert_equal(nseries._mask, [1,0,0,0,0]*3)
+        assert_equal(nseries._series, data-1)
+        assert_equal(nseries._dates, series._dates)
+        #
+        nseries = series*1
+        assert(isinstance(nseries, TimeSeries))
+        assert_equal(nseries._mask, [1,0,0,0,0]*3)
+        assert_equal(nseries._series, data*1)
+        assert_equal(nseries._dates, series._dates)
+        #
+        nseries = series/1.
+        assert(isinstance(nseries, TimeSeries))
+        assert_equal(nseries._mask, [1,0,0,0,0]*3)
+        assert_equal(nseries._series, data/1.)
+        assert_equal(nseries._dates, series._dates)
+    
+    def test_intfloat_inplace(self):
+        "Test int/float arithmetics in place."
+        (series, data) =self.d
+        nseries = series.astype(float_)
+        idini = id(nseries)
+        data = data.astype(float_)
+        #
+        nseries += 1.
+        assert(isinstance(nseries, TimeSeries))
+        assert_equal(nseries._mask, [1,0,0,0,0]*3)
+        assert_equal(nseries._series, data+1.)
+        assert_equal(nseries._dates, series._dates)
+        assert_equal(id(nseries),idini)
+        #
+        nseries -= 1.
+        assert(isinstance(nseries, TimeSeries))
+        assert_equal(nseries._mask, [1,0,0,0,0]*3)
+        assert_equal(nseries._series, data)
+        assert_equal(nseries._dates, series._dates)
+        assert_equal(id(nseries),idini)
+        #
+        nseries *= 2.
+        assert(isinstance(nseries, TimeSeries))
+        assert_equal(nseries._mask, [1,0,0,0,0]*3)
+        assert_equal(nseries._series, data*2.)
+        assert_equal(nseries._dates, series._dates)
+        assert_equal(id(nseries),idini)
+        #
+        nseries /= 2.
+        assert(isinstance(nseries, TimeSeries))
+        assert_equal(nseries._mask, [1,0,0,0,0]*3)
+        assert_equal(nseries._series, data)
+        assert_equal(nseries._dates, series._dates)
+        assert_equal(id(nseries),idini)
+    #
+    def test_updatemask(self):
+        "Checks modification of mask."
+        (series, data) =self.d
+        assert_equal(series._mask, [1,0,0,0,0]*3)
+        series.mask = nomask
+        assert(series._mask is nomask)
+        assert(series._series._mask is nomask)
+        series._series.mask = [1,0,0]*5
+        assert_equal(series._mask, [1,0,0]*5)
+        assert_equal(series._series._mask, [1,0,0]*5)
+        series[2] = masked
+        assert_equal(series._mask, [1,0,1]+[1,0,0]*4)
+        assert_equal(series._series._mask, [1,0,1]+[1,0,0]*4)
+#...............................................................................
+
+class test_getitem(NumpyTestCase):
+    "Some getitem tests"
+    def __init__(self, *args, **kwds):
+        NumpyTestCase.__init__(self, *args, **kwds)
+        dlist = ['2007-01-%02i' % i for i in range(1,16)]
+        dates = date_array_fromlist(dlist)
+        data = masked_array(numeric.arange(15), mask=[1,0,0,0,0]*3, dtype=float_)
+        self.d = (time_series(data, dlist), data, dates)
+    
+    def test_wdate(self):
+        "Tests  getitem with date as index"
+        (series, data, dates) = self.d
+        last_date = series[-1]._dates
+        assert_equal(series[-1], series[last_date])
+        assert_equal(series._dates[-1], dates[-1])
+        assert_equal(series[-1]._dates, dates[-1])
+        assert_equal(series[last_date]._dates, dates[-1])
+        assert_equal(series._series[-1], data._data[-1])
+        assert_equal(series[-1]._series, data._data[-1])
+        assert_equal(series._mask[-1], data._mask[-1])
+        #
+        series['2007-01-06'] = 999
+        assert_equal(series[5], 999)
+        #
+    def test_wtimeseries(self):
+        "Tests getitem w/ TimeSeries as index"
+        (series, data, dates) = self.d
+        # Testing a basic condition on data
+        cond = (series<8).filled(False)
+        dseries = series[cond]
+        assert_equal(dseries._data, [1,2,3,4,6,7])
+        assert_equal(dseries._dates, series._dates[[1,2,3,4,6,7]])
+        assert_equal(dseries._mask, nomask)
+        # Testing a basic condition on dates
+        series[series._dates < Date('D',string='2007-01-06')] = masked
+        assert_equal(series[:5]._series._mask, [1,1,1,1,1])
+    
+    def test_wslices(self):
+        "Test get/set items."
+        (series, data, dates) = self.d
+        # Basic slices
+        assert_equal(series[3:7]._series._data, data[3:7]._data)
+        assert_equal(series[3:7]._series._mask, data[3:7]._mask)
+        assert_equal(series[3:7]._dates, dates[3:7])
+        # Ditto
+        assert_equal(series[:5]._series._data, data[:5]._data)
+        assert_equal(series[:5]._series._mask, data[:5]._mask)
+        assert_equal(series[:5]._dates, dates[:5])
+        # With set
+        series[:5] = 0
+        assert_equal(series[:5]._series, [0,0,0,0,0])
+        dseries = N.log(series)
+        series[-5:] = dseries[-5:]
+        assert_equal(series[-5:], dseries[-5:])
+        # Now, using dates !
+        dseries = series[series.dates[3]:series.dates[7]]
+        assert_equal(dseries, series[3:7])
+        
+class test_functions(NumpyTestCase):
+    "Some getitem tests"
+    def __init__(self, *args, **kwds):
+        NumpyTestCase.__init__(self, *args, **kwds)
+        dlist = ['2007-01-%02i' % i for i in range(1,16)]
+        dates = date_array_fromlist(dlist)
+        data = masked_array(numeric.arange(15), mask=[1,0,0,0,0]*3)
+        self.d = (time_series(data, dlist), data, dates)
+    #
+    def test_adjustendpoints(self):
+        "Tests adjust_endpoints"
+        (series, data, dates) = self.d
+        dseries = adjust_endpoints(series, series.dates[0], series.dates[-1])
+        assert_equal(dseries, series)
+        dseries = adjust_endpoints(series, series.dates[3], series.dates[-3])
+        assert_equal(dseries, series[3:-2])
+        dseries = adjust_endpoints(series, end_date=Date('D', string='2007-01-31'))
+        assert_equal(dseries.size, 31)
+        assert_equal(dseries._mask, N.r_[series._mask, [1]*16])
+        dseries = adjust_endpoints(series, end_date=Date('D', string='2007-01-06'))
+        assert_equal(dseries.size, 6)
+        assert_equal(dseries, series[:6])
+        dseries = adjust_endpoints(series, 
+                                   start_date=Date('D', string='2007-01-06'),
+                                   end_date=Date('D', string='2007-01-31'))
+        assert_equal(dseries.size, 26)
+        assert_equal(dseries._mask, N.r_[series._mask[5:], [1]*16])
+    #
+    def test_maskperiod(self):        
+        "Test mask_period"
+        (series, data, dates) = self.d
+        series.mask = nomask
+        (start, end) = ('2007-01-06', '2007-01-12')
+        mask = mask_period(series, start, end, inside=True, include_edges=True,
+                           inplace=False)
+        assert_equal(mask._mask, N.array([0,0,0,0,0,1,1,1,1,1,1,1,0,0,0]))
+        mask = mask_period(series, start, end, inside=True, include_edges=False,
+                           inplace=False)
+        assert_equal(mask._mask, [0,0,0,0,0,0,1,1,1,1,1,0,0,0,0])
+        mask = mask_period(series, start, end, inside=False, include_edges=True,
+                           inplace=False)
+        assert_equal(mask._mask, [1,1,1,1,1,1,0,0,0,0,0,1,1,1,1])
+        mask = mask_period(series, start, end, inside=False, include_edges=False,
+                           inplace=False)
+        assert_equal(mask._mask, [1,1,1,1,1,0,0,0,0,0,0,0,1,1,1])
+    #
+    def pickling(self):
+        "Tests pickling/unpickling"
+        (series, data, dates) = self.d
+        tmp = maskedarray.loads(series.dumps())
+        assert_equal(tmp._data, series._data)
+        assert_equal(tmp._dates, series._dates)
+        assert_equal(tmp._mask, series._mask)
+        
+###############################################################################
+#------------------------------------------------------------------------------
+if __name__ == "__main__":
+    NumpyTest().run()        
\ No newline at end of file


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

Deleted: trunk/Lib/sandbox/timeseries/timeseries.py
===================================================================
--- trunk/Lib/sandbox/timeseries/timeseries.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/timeseries.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,504 +0,0 @@
-import numpy
-from numpy import ma
-
-import corelib
-import cseries
-import tsdate
-import copy as copytools
-
-from types import MethodType
-
-masked = ma.masked
-nomask = ma.nomask
-
-def ts_compatible(a, b):
-    if a.freq != b.freq:
-        raise ValueError("Both TimeSeries must have same freq!")
-    elif a.start_date() != b.start_date():
-        raise ValueError("Both TimeSeries must have same start_date!")
-    elif a.shape != b.shape:
-        raise ValueError("Both TimeSeries must be of the same size!")
-
-
-class ts_unary_operation:
-    def __init__ (self, abfunc):
-        self.f = abfunc
-        self.__doc__ = getattr(abfunc, "__doc__", str(abfunc))
-
-    def __call__ (self, a, *args, **kwargs):
-        "Execute the call behavior."
-        if isinstance(a, TimeSeries):
-            return TimeSeries(self.f(a, *args, **kwargs), freq=a.freq, observed=a.observed, start_date=a.start_date())
-        else:
-            return self.f(a, *args, **kwargs)
-        
-        
-class ts_binary_operation:
-    def __init__ (self, abfunc):
-        self.f = abfunc
-        self.__doc__ = getattr(abfunc, "__doc__", str(abfunc))
-
-    def __call__ (self, a, b, *args, **kwargs):
-        "Execute the call behavior."
-
-        if isinstance(a, TimeSeries) and isinstance(b, TimeSeries):
-            ts_compatible(a, b)
-            return TimeSeries(self.f(a, b, *args, **kwargs), freq=a.freq, observed=a.observed, start_date=a.start_date())
-        elif isinstance(a, TimeSeries):
-            if corelib.isDateType(a.tstype):
-                return TimeSeries(self.f(a, b, *args, **kwargs), dtype=a.tstype, freq=a.freq, observed=a.observed, start_date=a.start_date())
-            else:
-                return TimeSeries(self.f(a, b, *args, **kwargs), freq=a.freq, observed=a.observed, start_date=a.start_date())
-        elif isinstance(b, TimeSeries):
-            if corelib.isDateType(b.tstype):
-                return TimeSeries(self.f(a, b, *args, **kwargs), dtype=b.tstype, freq=b.freq, observed=b.observed, start_date=b.start_date())
-            else:
-                return TimeSeries(self.f(a, b, *args, **kwargs), freq=b.freq, observed=b.observed, start_date=b.start_date())
-        else:
-            return self.f(a, b, *args, **kwargs)
-            
-    def reduce (self, target, axis=0, dtype=None):
-        return self.f.reduce(target, axis, dtype)
-
-    def outer (self, a, b):
-        return self.f.outer(a, b)
-
-    def accumulate (self, target, axis=0):
-        return datawrap(self.f.accumulate(target, axis), target)
-
-    def __str__ (self):
-        return "Masked version of " + str(self.f)            
-
-
-class TimeSeries(ma.MaskedArray):
-
-    __array_priority__ = 10.2
-
-    def __init__(self, data, dtype=None, freq=None, start_date=None, observed=None, copy=True, order=False, mask=ma.nomask, fill_value=None):
-    
-        if isinstance(data, TimeSeries):
-            if freq is None: freq = data.freq
-            if start_date is None: start_date = data.start_date()
-            if observed is None: observed = data.observed
-        else:
-            if observed is None: observed = 'END'
-        
-        self.freq = corelib.fmtFreq(freq)
-
-        if isinstance(start_date, tsdate.Date):
-            if start_date.freq != self.freq: raise ValueError("frequency of start_date must match frequency of series")
-            else: self.__start_date = start_date
-        else:
-            self.__start_date = tsdate.Date(freq=self.freq, value=start_date)
-
-        self.observed = corelib.fmtObserv(observed)
-
-        self.tstype = None
-
-        if corelib.isDateType(dtype) or (isinstance(data, TimeSeries) and corelib.isDateType(data.tstype)):
-            self.tstype = dtype
-            dtype = numpy.int_
-
-        super(TimeSeries, self).__init__(data=data, dtype=dtype, copy=copy, order=order, mask=mask, fill_value=fill_value)
-        
-        if self.tstype is None: self.tstype = self.dtype
-
-
-    def __getitem__(self, key):
-        return super(TimeSeries, self).__getitem__(self.__prepKey(key))
-        
-    def __setitem__(self, key, value):
-        super(TimeSeries, self).__setitem__(self.__prepKey(key), value)
-
-    def __prepKey(self, key):
-    
-        if isinstance(key, tsdate.Date):
-            key = int(key - self.start_date())
-            if key < 0: raise ValueError("Date out of bounds")
-            else: return key
-
-        elif isinstance(key, TimeSeries):
-            if corelib.isDateType(key.tstype):
-                if key.tstype.freq != self.freq:
-                    raise ValueError("series of frequency "+str(self.freq)+" given date expression of type "+str(key.tstype.freq))
-
-                if key.mask is ma.nomask: key = numpy.asarray(key) - int(self.start_date())
-                else: key = numpy.asarray(key[key.mask == False]) - int(self.start_date())
-                
-                if len(numpy.where(key < 0)[0]) > 0: raise ValueError("Indices out of bounds")
-                
-                return key
-                
-            else:
-
-                # frequency, size, and start_date of key must all match self
-                # when the data type is note a date
-                ts_compatible(key, self)
-
-                if key.tstype is numpy.bool_:
-                    key = key.filled(False)
-                elif numpy.ravel(key.mask).any():
-                    raise ValueError("masked values cannot be used as indices!")
-
-                return numpy.asarray(key)
-        
-        elif isinstance(key, ma.MaskedArray):
-
-            if key.dtype is numpy.bool_:
-                key = key.filled(False)
-            elif numpy.ravel(key.mask).any():
-                raise ValueError("masked values cannot be used as indices!")
-
-            return numpy.asarray(key)
-        
-        else: return key
-
-    
-    def convert(self, freq, func='auto', position='END', interp=None):
-        """
-        return self converted to freq.
-        
-        When converting to a lower frequency, func is a function that acts
-        on a 1-d array and returns a scalar or 1-d array. func should handle
-        masked values appropriately. If func is "auto", then an
-        appropriate function is determined based on the observed attribute
-        of the series. If func is None, then a 2D array is returned, where each
-        column represents the values appropriately grouped into the new frequency.
-        interp and position will be ignored in this case.
-        
-        When converting to a higher frequency, position is 'START' or 'END'
-        and determines where the data point is in each period (eg. if going
-        from monthly to daily, and position is 'END', then each data point is
-        placed at the end of the month). Interp is the method that will be used
-        to fill in the gaps. Valid values are "CUBIC", "LINEAR", "CONSTANT", "DIVIDED",
-        and None.
-        
-        Note: interp currently not implemented
-        
-        """
-        
-        if position.upper() not in ('END','START'): raise ValueError("invalid value for position argument: (%s)",str(position))
-        
-        toFreq = corelib.fmtFreq(freq)
-        fromFreq = self.freq
-        
-        if fromFreq != toFreq:
-        
-            if func == 'auto':
-                func = corelib.obsDict[self.observed]
-
-            if self.size == 0:
-                return TimeSeries(self, freq=toFreq, start_date=tsdate.dateOf(self.start_date(), toFreq))
-
-            tempData = self.filled()
-
-            if self.mask is ma.nomask:
-                tempMask = numpy.empty(tempData.shape, dtype=numpy.bool_)
-                tempMask[:] = False
-            else: tempMask = self.mask
-
-            cRetVal = cseries.convert(tempData, fromFreq, toFreq, position, int(self.start_date()), tempMask)
-
-            _values = cRetVal['values']
-            _mask = cRetVal['mask']
-            startIndex = cRetVal['startindex']
-            
-            tempData = ma.array(_values)
-            tempMask = ma.make_mask(_mask)
-            tempData[tempMask] = ma.masked
-
-            if func is not None and tempData.ndim == 2:
-                tempData = corelib.apply_along_axis(func, 1, tempData)
-
-            return TimeSeries(tempData, freq=toFreq, observed=self.observed, start_date=startIndex)
-            
-        else:
-            return copytools.deepcopy(self)
-
-
-    def adjust_endpoints(self, start_date=None, end_date=None):
-        self.__init__(adjust_endpoints(self, start_date=start_date, end_date=end_date))
-
-        
-    def __str__(self):
-        retVal = ""
-
-        if self.shape[0] > 0:
-            for i in range(self.shape[0]):
-                index = str(self.start_date() + i)
-                index = index + (" " * (6-len(index)))
-                retVal += index + " --> " + str(self[i])+"\n"
-            return retVal
-        else:
-            return "<no data>"
-            
-            
-    def first_value(self, asDate=False):
-        firstIndex = corelib.first_unmasked(self)
-        if asDate:
-            return self.start_date() + firstIndex
-        else:
-            return firstIndex
-        
-    def last_value(self, asDate=False):
-        lastIndex = corelib.last_unmasked(self)
-        if asDate:
-            return self.start_date() + lastIndex
-        else:
-            return lastIndex
-            
-    def start_date(self): return self.__start_date
-    def end_date(self): return self.__start_date + (self.shape[0] - 1)
-            
-    def date_to_index(self, date):
-        if date.freq != self.freq: raise ValueError("date.freq != self.freq")
-        return date - self.start_date()
-            
-            
-    # built-in methods
-
-    def __and__(self, other): return bitwise_and(self, other)
-    def __or__(self, other): return bitwise_or(self, other)
-    def __xor__(self, other): return bitwise_xor(self, other)
-    __rand__ = __and__
-    __ror__ = __or__
-    __rxor__ = __xor__
-    def __abs__(self): return absolute(self)
-    def __neg__(self): return negative(self)
-    def __pos__(self): return TimeSeries(self)
-    def __add__(self, other): return add(self, other)
-    __radd__ = __add__
-    def __mod__ (self, other): return remainder(self, other)
-    def __rmod__ (self, other): return remainder(other, self)
-    def __lshift__ (self, n): return left_shift(self, n)
-    def __rshift__ (self, n): return right_shift(self, n)
-    def __sub__(self, other): return subtract(self, other)
-    def __rsub__(self, other): return subtract(other, self)
-    def __mul__(self, other): return multiply(self, other)
-    __rmul__ = __mul__
-    def __div__(self, other): return divide(self, other)
-    def __rdiv__(self, other): return divide(other, self)
-    def __truediv__(self, other): return true_divide(self, other)
-    def __rtruediv__(self, other): return true_divide(other, self)
-    def __floordiv__(self, other): return floor_divide(self, other)
-    def __rfloordiv__(self, other): return floor_divide(other, self)
-    def __pow__(self, other, third=None): return power(self, other, third)
-    def __sqrt__(self): return sqrt(self)
-
-    def __iadd__(self, other):
-        return self + other
-
-    def __imul__(self, other):
-        return self * other
-
-    def __isub__(self, other):
-        return self - other
-
-    def __idiv__(self, other):
-        return self / other
-
-    def __eq__(self, other): return equal(self,other)
-    def __ne__(self, other): return not_equal(self,other)
-    def __lt__(self, other): return less(self,other)
-    def __le__(self, other): return less_equal(self,other)
-    def __gt__(self, other): return greater(self,other)
-    def __ge__(self, other): return greater_equal(self,other)
-
-    def astype (self, tc):
-        "return self as array of given type."
-        d = self._data.astype(tc)
-        return datawrap(ma.array(d, mask=self._mask), self)
-
-    def filled (self, fill_value=None, ts=False):
-        d = super(TimeSeries, self).filled(fill_value)
-        if ts: return datawrap(d, self)
-        else: return d
-
-
-def datawrap(data, ts): return TimeSeries(data, freq=ts.freq, observed=ts.observed, start_date=ts.start_date())
-
-## wrappers for numpy.ma funcs
-
-sqrt = ts_unary_operation(ma.sqrt)
-log = ts_unary_operation(ma.log)
-log10 = ts_unary_operation(ma.log10)
-exp = ts_unary_operation(ma.exp)
-sin = ts_unary_operation(ma.sin)
-cos = ts_unary_operation(ma.cos)
-tan = ts_unary_operation(ma.tan)
-arcsin = ts_unary_operation(ma.arcsin)
-arccos = ts_unary_operation(ma.arccos)
-arctan = ts_unary_operation(ma.arctan)
-
-def cumprod(self, axis=0, dtype=None, out=None): return datawrap(ma._cumprod(self, axis, dtype, out), self)
-def cumsum(self, axis=0, dtype=None, out=None): return datawrap(ma._cumsum(self, axis, dtype, out), self)
-
-arcsinh = ts_unary_operation(ma.arcsinh)
-arccosh = ts_unary_operation(ma.arccosh)
-arctanh = ts_unary_operation(ma.arctanh)
-sinh = ts_unary_operation(ma.sinh)
-cosh = ts_unary_operation(ma.cosh)
-tanh = ts_unary_operation(ma.tanh)
-absolute = ts_unary_operation(ma.absolute)
-fabs = ts_unary_operation(ma.fabs)
-negative = ts_unary_operation(ma.negative)
-nonzero = ts_unary_operation(ma.nonzero)
-
-around = ts_unary_operation(ma.around)
-floor = ts_unary_operation(ma.floor)
-ceil = ts_unary_operation(ma.ceil)
-logical_not = ts_unary_operation(ma.logical_not)
-
-def zeros(shape, dtype=float, freq=None, start_date=None, observed=None):
-    return TimeSeries(ma.zeros(shape, dtype), freq=freq, start_date=start_date, observed=observed)
-def ones(shape, dtype=float, freq=None, start_date=None, observed=None):
-    return TimeSeries(ma.ones(shape, dtype), freq=freq, start_date=start_date, observed=observed)
-
-
-# functions from ma that we want to return scalars or masked arrays
-count = ma.count
-sum = ma.sum
-product = ma.product
-average = ma.average
-compress = ma.compress
-minimum = ma.minimum
-maximum = ma.maximum
-alltrue = ma.alltrue
-allclose = ma.allclose
-allequal = ma.allequal
-sometrue = ma.sometrue
-std = ma._std
-var = ma._var
-
-def argmin (x, axis = -1, out=None, fill_value=None):
-    # same as argmin for ma, but returns a date instead of integer
-    return x.start_date() + ma.argmin(x, axis, out, fill_value)    
-
-def argmax (x, axis = -1, out=None, fill_value=None):
-    # same as argmax for ma, but returns a date instead of integer
-    return x.start_date() + ma.argmax(x, axis, out, fill_value)
-    
-
-# binary operators
-add = ts_binary_operation(ma.add)
-subtract = ts_binary_operation(ma.subtract)
-multiply = ts_binary_operation(ma.multiply)
-divide = ts_binary_operation(ma.divide)
-power = ts_binary_operation(ma.power)
-true_divide = ts_binary_operation(ma.true_divide)
-floor_divide = ts_binary_operation(ma.floor_divide)
-remainder = ts_binary_operation(ma.remainder)
-fmod = ts_binary_operation(ma.fmod)
-hypot = ts_binary_operation(ma.hypot)
-arctan2 = ts_binary_operation(ma.arctan2)
-equal = ts_binary_operation(ma.equal)
-not_equal = ts_binary_operation(ma.not_equal)
-less_equal = ts_binary_operation(ma.less_equal)
-greater_equal = ts_binary_operation(ma.greater_equal)
-less = ts_binary_operation(ma.less)
-greater = ts_binary_operation(ma.greater)
-logical_and = ts_binary_operation(ma.logical_and)
-logical_or = ts_binary_operation(ma.logical_or)
-logical_xor = ts_binary_operation(ma.logical_xor)
-bitwise_and = ts_binary_operation(ma.bitwise_and)
-bitwise_or = ts_binary_operation(ma.bitwise_or)
-bitwise_xor = ts_binary_operation(ma.bitwise_xor)
-
-def left_shift (a, n): return datawrap(ma.left_shift(a, n), a)
-def right_shift (a, n): return datawrap(ma.right_shift(a, n), a)
-
-def masked_where(condition, x, copy=1): return datawrap(ma.masked_where(condition, x, copy), x)
-def masked_greater(x, value, copy=1): return datawrap(ma.masked_greater(x, value, copy), x)
-def masked_greater_equal(x, value, copy=1): return datawrap(ma.masked_greater_equal(x, value, copy), x)
-def masked_less(x, value, copy=1): return datawrap(ma.masked_less(x, value, copy), x)
-def masked_less_equal(x, value, copy=1): return datawrap(ma.masked_less_equal(x, value, copy), x)
-def masked_not_equal(x, value, copy=1): return datawrap(ma.masked_not_equal(x, value, copy), x)
-def masked_equal(x, value, copy=1): return datawrap(ma.masked_equal(x, value, copy), x)
-def masked_inside(x, v1, v2, copy=1): return datawrap(ma.masked_inside(x, v1, v2, copy), x)
-def masked_outside(x, v1, v2, copy=1): return datawrap(ma.masked_outside(x, v1, v2, copy), x)
-
-def clip(self,a_min,a_max,out=None): return datawrap(ma._clip(self, a_min, a_max, out=None), self)
-
-
-array = TimeSeries
-
-def _m(f):
-    return MethodType(f, None, array)
-    
-array.clip = _m(clip)
-array.argmax = _m(argmax)
-array.argmin = _m(argmin)
-array.cumprod = _m(cumprod)
-array.cumsum = _m(cumsum)
-
-
-# time series specific functions
-
-def tser(start, end):
-    if start.freq != end.freq:
-        raise ValueError("start and end dates must have same frequency!")
-    return TimeSeries(numpy.arange(int(start), int(end)+1), dtype=corelib.freqTypeMapping[start.freq], freq=start.freq, start_date=start)
-
-def year(dateSer):
-    return __getDateInfo(dateSer,'Y')
-
-def quarter(dateSer):
-    return __getDateInfo(dateSer,'Q')
-    
-def month(dateSer):
-    return __getDateInfo(dateSer,'M')
-    
-def day(dateSer):
-    return __getDateInfo(dateSer,'D')
-    
-def day_of_week(dateSer):
-    return __getDateInfo(dateSer,'W')
-
-def __getDateInfo(dateSer,infoCode):
-    newData = ma.array(cseries.getDateInfo(dateSer.filled(), dateSer.tstype.freq, infoCode))
-    if dateSer.mask is not ma.nomask:
-        newData[dateSer.mask] = ma.masked
-    return datawrap(newData, dateSer)
-
-
-def adjust_endpoints(a, start_date=None, end_date=None):
-    """adjust_endpoints(a, start_date=None, end_date=None) returns a new
-    TimeSeries going from start_date to end_date"""
-    
-    if start_date is None: start_date = a.start_date()
-    if end_date is None: end_date = a.end_date()
-
-    tmpShape = list(a.shape)
-    tmpShape[0] = max(end_date - start_date + 1, 0)
-    tmpShape = tuple(tmpShape)
-    
-    tmpSer = TimeSeries(ma.resize(a, tmpShape), freq=a.freq, observed=a.observed, start_date=start_date)
-    
-    setStart, setEnd = max(start_date, a.start_date()), min(end_date, a.end_date())
-    setLen = setEnd - setStart
-    
-    tmpSer[:] = ma.masked
-    
-    if setLen >= 0:
-        tmpSer[tmpSer.date_to_index(setStart):tmpSer.date_to_index(setEnd)+1] = a[a.date_to_index(setStart):a.date_to_index(setEnd)+1]
-            
-    return tmpSer
-
-
-def aligned(*series, **kwargs):
-    
-    if len(series) < 2:
-        return series
-        
-    freq = series[0].freq
-    
-    if len(set([x.freq for x in series])) > 1: raise ValueError("All series must have same frequency!")
-    
-    if 'start_date' in kwargs: start_date = kwargs['start_date']
-    else: start_date = min([x.start_date() for x in series])
-    
-    if 'end_date' in kwargs: end_date = kwargs['end_date']
-    else: end_date = max([x.end_date() for x in series])
-    
-    return [adjust_endpoints(x, start_date=start_date, end_date=end_date) for x in series]
-    
\ No newline at end of file

Deleted: trunk/Lib/sandbox/timeseries/tsdate.py
===================================================================
--- trunk/Lib/sandbox/timeseries/tsdate.py	2007-01-14 21:25:20 UTC (rev 2551)
+++ trunk/Lib/sandbox/timeseries/tsdate.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -1,197 +0,0 @@
-import corelib
-import cseries
-import numpy as np
-import mx.DateTime
-
-class Date:
-    def __init__(self, freq, year=None, month=None, day=None, seconds=None, quarter=None, mxDate=None, value=None):
-        
-        if hasattr(freq, 'freq'):
-            self.freq = corelib.fmtFreq(freq.freq)
-        else:
-            self.freq = corelib.fmtFreq(freq)
-        self.type = corelib.freqToType(self.freq)
-        
-        if value is not None:
-            if self.freq == 'D':
-                self.mxDate = mx.DateTime.DateTimeFromAbsDays(value-1)
-            elif self.freq == 'B':
-                value = value - 1
-                self.mxDate = mx.DateTime.DateTimeFromAbsDays(value + (value//5)*7 - (value//5)*5)
-            elif self.freq == 'S':
-                self.mxDate = secondlyOriginDate + mx.DateTime.DateTimeDeltaFromSeconds(value)
-            elif self.freq == 'M':
-                self.mxDate = (mx.DateTime.Date(0)) + mx.DateTime.RelativeDateTime(months=value-1, day=-1)
-            elif self.freq == 'A':
-                self.mxDate = mx.DateTime.Date(value, -1, -1)
-            elif self.freq == 'Q':
-                self.mxDate = (mx.DateTime.Date(0)) + mx.DateTime.RelativeDateTime(years=(value // 4), month=((value * 3) % 12), day=-1)
-        elif mxDate is not None:
-            self.mxDate = mxDate
-        else:
-            error = ValueError("Insufficient parameters given to create a date at the given frequency")
-
-            if year is None:
-                raise error            
-            
-            if self.freq in ('B', 'D'):
-                if month is None or day is None: raise error
-            elif self.freq == 'M':
-                if month is None: raise error
-                day = -1
-            elif self.freq == 'Q':
-                if quarter is None: raise error
-                month = quarter * 3
-                day = -1
-            elif self.freq == 'A':
-                month = -1
-                day = -1
-            elif self.freq == 'S':
-                if month is None or day is None or seconds is None: raise error
-                
-            if self.freq != 'S':
-                self.mxDate = mx.DateTime.Date(year, month, day)
-                if self.freq == 'B':
-                    if self.mxDate.day_of_week == 5 or self.mxDate.day_of_week == 6:
-                        raise ValueError("Weekend passed as business day")
-            else:
-                _hours = int(seconds/3600)
-                _minutes = int((seconds - _hours*3600)/60)
-                _seconds = seconds % 60
-                
-                self.mxDate = mx.DateTime.Date(year, month, day, _hours, _minutes, _seconds)
-                
-        self.value = self.__value()
-                
-    def day(self):          return self.mxDate.day
-    def day_of_week(self):  return self.mxDate.day_of_week
-    def month(self):        return self.mxDate.month
-    def quarter(self):      return monthToQuarter(self.mxDate.month)
-    def year(self):         return self.mxDate.year
-    def seconds(self):      return int(self.mxDate.second)
-    def minute(self):       return int(self.mxDate.minute)
-    def hour(self):         return int(self.mxDate.hour)
-    
-    def strfmt(self, fmt):
-        qFmt = fmt.replace("%q", "XXXX")
-        tmpStr = self.mxDate.strftime(qFmt)
-        return tmpStr.replace("XXXX", str(self.quarter()))
-            
-    def __str__(self):
-        return self.strfmt(self.default_fmtstr())
-    
-    def default_fmtstr(self):
-        if self.freq in ("B", "D"):
-            return "%d-%b-%y"
-        elif self.freq == "S":
-            return "%d-%b-%Y %H:%M:%S"
-        elif self.freq == "M":
-            return "%b-%Y"
-        elif self.freq == "Q":
-            return "%Yq%q"
-        elif self.freq == "A":
-            return "%Y"
-        else:
-            return "%d-%b-%y"
-        
-    def __add__(self, other):
-        if isinstance(other, Date):
-            raise TypeError("Cannot add dates")
-        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 ValueError("Cannont subtract dates of different frequency (" + str(self.freq) + " != " + str(other.freq) + ")")
-            else:
-                return int(self) - int(other) 
-        else:
-            return self + (-1) * int(other)
-
-
-    def __repr__(self): return "<" + str(self.freq) + ":" + str(self) + ">"
-    
-    def __eq__(self, other):
-        if self.freq != other.freq:
-            raise TypeError("frequencies are not equal!")
-        return int(self) == int(other) 
-    
-    def __cmp__(self, other): 
-        if self.freq != other.freq:
-            raise TypeError("frequencies are not equal!")
-        return int(self)-int(other)    
-        
-    def __hash__(self): return hash(int(self)) ^ hash(self.freq)
-    
-    def __int__(self):
-        return self.value
-            
-    def __value(self):
-        
-        if self.freq == 'D':
-            return self.mxDate.absdate
-        elif self.freq == 'B':
-            days = self.mxDate.absdate
-            weeks = days // 7
-            return int((weeks*5) + (days - weeks*7))
-        elif self.freq == 'M':
-            return self.mxDate.year*12 + self.mxDate.month
-        elif self.freq == 'S':
-            return int((self.mxDate - secondlyOriginDate).seconds)
-        elif self.freq == 'A':
-            return int(self.mxDate.year)
-        elif self.freq == 'Q':
-            return int(self.mxDate.year*4 + self.mxDate.month/3)
-
-    
-secondlyOriginDate = mx.DateTime.Date(1980) - mx.DateTime.DateTimeDeltaFromSeconds(1)
-
-    
-#######################
-# FUNCTIONS
-#######################
-def monthToQuarter(monthNum):
-    return int((monthNum-1)/3)+1
-
-def thisday(freq):
-
-    freq = corelib.fmtFreq(freq)
-
-    tempDate = mx.DateTime.now()
-    
-    # if it is Saturday or Sunday currently, freq==B, then we want to use Friday
-    if freq == 'B' and tempDate.day_of_week >= 5:
-        tempDate -= (tempDate.day_of_week - 4)
-    if freq == 'B' or freq == 'D' or freq == 'S':
-        return Date(freq, mxDate=tempDate)
-    elif freq == 'M':
-        return Date(freq, year=tempDate.year, month=tempDate.month)
-    elif freq == 'Q':
-        return Date(freq, year=tempDate.year, quarter=monthToQuarter(tempDate.month))
-    elif freq == 'A':
-        return Date(freq, year=tempDate.year)
-
-
-def prevbusday(day_end_hour=18, day_end_min=0):
-    tempDate = mx.DateTime.localtime()
-
-    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')
-
-                
-#    returns date converted to a date of toFreq according to relation
-#    relation = "BEFORE" or "AFTER" (not case sensitive)
-def dateOf(date, toFreq, relation="BEFORE"):
-
-    toFreq = corelib.fmtFreq(toFreq)
-    _rel = relation.upper()[0]
-
-    if date.freq == toFreq:
-        return date
-    else:
-        return Date(freq=toFreq, value=cseries.asfreq(np.asarray(date.value), date.freq, toFreq, _rel))

Copied: trunk/Lib/sandbox/timeseries/tseries.py (from rev 2488, trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py)
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py	2007-01-04 16:42:30 UTC (rev 2488)
+++ trunk/Lib/sandbox/timeseries/tseries.py	2007-01-14 22:39:15 UTC (rev 2552)
@@ -0,0 +1,1242 @@
+# pylint: disable-msg=W0201, W0212
+"""
+Core classes for time/date related arrays.
+
+The `DateArray` class provides a base for the creation of date-based objects,
+using days as the base units. This class could be adapted easily to objects
+with a smaller temporal resolution (for example, using one hour, one second as the
+base unit).
+
+The `TimeSeries` class provides  a base for the definition of time series.
+A time series is defined here as the combination of two arrays:
+    
+    - an array storing the time information (as a `DateArray` instance);
+    - an array storing the data (as a `MaskedArray` instance.
+
+These two classes were liberally adapted from `MaskedArray` class.
+
+
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id$
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author$)"
+__version__ = '1.0'
+__revision__ = "$Revision$"
+__date__     = '$Date$'
+
+
+import logging
+import weakref
+
+
+import numpy
+from numpy import ndarray
+from numpy.core import bool_, complex_, float_, int_, object_
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numeric as numeric
+import numpy.core.umath as umath
+#from numpy.core.records import recarray
+from numpy.core.records import fromarrays as recfromarrays
+
+import maskedarray as MA
+#reload(MA)
+#MaskedArray = MA.MaskedArray
+from maskedarray.core import MaskedArray, MAError, masked, nomask, \
+    filled, getmask, getmaskarray, make_mask_none, mask_or, make_mask, \
+    masked_array
+
+import tcore as corelib
+#reload(corelib)
+from tcore import *
+
+import tdates
+#reload(tdates)
+from tdates import DateError, InsufficientDateError
+from tdates import Date, isDate, DateArray, isDateArray, \
+    date_array, date_array_fromlist, date_array_fromrange, thisday
+
+import cseries
+#reload(cseries)
+
+#...............................................................................
+logging.basicConfig(level=logging.DEBUG,
+                    format='%(name)-15s %(levelname)s %(message)s',)
+talog = logging.getLogger('log.TimeArray')
+tslog = logging.getLogger('TimeSeries')
+btslog = logging.getLogger('BaseTimeSeries')
+
+ufunc_domain = {}
+ufunc_fills = {}
+
+#### --------------------------------------------------------------------------
+#--- ... TimeSeriesError class ...
+#### --------------------------------------------------------------------------
+class TimeSeriesError(Exception):
+    "Class for TS related errors."
+    def __init__ (self, args=None):
+        "Creates an exception."
+        Exception.__init__(self)
+        self.args = args
+    def __str__(self):
+        "Calculates the string representation."
+        return str(self.args)
+    __repr__ = __str__
+
+class TimeSeriesCompatibilityError(TimeSeriesError):
+    """Defines the exception raised when series are incompatible."""
+    def __init__(self, mode, first, second):
+        if mode == 'freq':
+            msg = "Incompatible time steps! (%s <> %s)"
+        elif mode == 'start_date':
+            msg = "Incompatible starting dates! (%s <> %s)"
+        elif mode == 'size':
+            msg = "Incompatible sizes! (%s <> %s)"
+        msg = msg % (first, second)
+        TimeSeriesError.__init__(self, msg)
+
+
+#def _compatibilitycheck(a, b):
+def _timeseriescompat(a, b):
+    """Checks the date compatibility of two TimeSeries object.
+    Returns True if everything's fine, or raises an exception."""
+    if not (hasattr(a,'freq') and hasattr(b, 'freq')):
+        return True
+    if a.freq != b.freq:
+        raise TimeSeriesCompatibilityError('freq', a.freq, b.freq)
+    elif a.start_date() != b.start_date():
+        raise TimeSeriesCompatibilityError('start_date', 
+                                           a.start_date(), b.start_date())
+    elif (a._dates.get_steps() != b._dates.get_steps()).any():
+        raise TimeSeriesCompatibilityError('time_steps', 
+                                           a._dates.get_steps(), b._dates.get_steps())
+    elif a.shape != b.shape:
+        raise TimeSeriesCompatibilityError('size', "1: %s" % str(a.shape), 
+                                                   "2: %s" % str(b.shape))
+    return True
+
+def _datadatescompat(data,dates):
+    """Checks the compatibility of dates and data at the creation of a TimeSeries.
+    Returns True if everything's fine, raises an exception otherwise."""    
+    # If there's only 1 element, the date is a Date object, which has no size...
+    tsize = numeric.size(dates)
+    dsize = data.size
+    # Only one data
+    if dsize == tsize:
+        return True
+    elif data.ndim > 1:
+        dsize = numeric.asarray(data.shape)[:-1].prod()
+        if dsize == tsize:
+            return True    
+    raise TimeSeriesCompatibilityError('size', "data: %s" % dsize, 
+                                               "dates: %s" % tsize)
+
+def _getdatalength(data):
+    "Estimates the length of a series (size/nb of variables)."
+    if numeric.ndim(data) >= 2:
+        return numeric.asarray(numeric.shape(data))[:-1].prod()
+    else:
+        return numeric.size(data)
+
+##### --------------------------------------------------------------------------
+##--- ... Time Series ...
+##### --------------------------------------------------------------------------
+#if oldma:
+#    parentclass = ndarray
+#else:
+#    parentclass = MaskedArray
+#
+class TimeSeries(MaskedArray, object):     
+    """Base class for the definition of time series.
+A time series is here defined as the combination of three arrays:
+    
+    - `series` : *[ndarray]*
+        Data part
+    - `mask` : *[ndarray]*
+        Mask part
+    - `dates` : *[DateArray]*
+        Date part
+    
+The combination of `series` and `dates` is the `data` part.
+    """
+    def __new__(cls, data, dates=None, mask=nomask, 
+                freq=None, observed=None, start_date=None, 
+                dtype=None, copy=False, fill_value=None,
+                keep_mask=True, small_mask=True, hard_mask=False):
+        #tslog.info("__new__: received data types %s, %s" % (type(data), data))
+        options = dict(copy=copy, dtype=dtype, fill_value=fill_value,
+                       keep_mask=keep_mask, small_mask=small_mask, 
+                       hard_mask=hard_mask, )
+        if isinstance(data, TimeSeries):
+            # Check dates ........
+            if dates is None:
+                newdates = data._dates
+            else:
+                if not hasattr(dates,'freq'):
+                    raise DateError, "Invalid Dates!"       
+                newdates = dates
+                data._dates = newdates
+                if hasattr(data, '_data') and hasattr(data._data, '_dates'):
+                    data._data._dates = newdates
+            cls._defaultdates = newdates    
+            # Check frequency......
+            if freq is not None:
+                freq = corelib.fmtFreq(freq)
+                if freq != newdates.freq:
+                    _dates = newdates.tofreq(freq)
+            else:
+                freq = newdates.freq
+            # Check observed.......
+            if observed is not None:
+                observed = data._observed
+            cls._defaultobserved = observed  
+            _data = data._series
+        else:
+            # Check dates ........
+            if dates is None:
+                length = _getdatalength(data)
+                newdates = date_array(start_date=start_date, length=length,
+                                      freq=freq)                 
+            elif not hasattr(dates, 'freq'):
+                newdates = date_array(dlist=dates, freq=freq)
+            else:
+                newdates = dates
+            _data = data
+            if hasattr(data, '_mask') :
+                mask = mask_or(data._mask, mask)
+            cls._defaultdates = newdates    
+            cls._defaultobserved = observed  
+#            if oldma:
+#                newdata = MaskedArray(data, mask=mask, dtype=dtype,
+#                                      copy=copy,fill_value=fill_value)
+#                cls._defaultmask = newdata._mask
+#                cls._defaulthardmask = True
+#                cls._fill_value = newdata._fill_value
+#                assert(_datadatescompat(newdata,dates))
+#                return ndarray.__new__(cls,shape=newdata.shape,dtype=newdata.dtype,
+#                                       buffer=newdata._data)
+#            _data = data
+#        newdata = MaskedArray.__new__(cls, data=_data, mask=mask, **options)
+        newdata = super(TimeSeries,cls).__new__(cls, _data, mask=mask,
+                                                **options)
+        assert(_datadatescompat(data,newdates))
+        return newdata
+            
+    #..................................
+    def __array_wrap__(self, obj, context=None):
+#        if oldma:
+#            tmpself = MaskedArray(self._data, mask=self._mask)
+#            return TimeSeries(MaskedArray.__array_wrap__(tmpself, obj, context),
+#                              dates=self._dates)
+#        print "__array_wrap__"
+        return TimeSeries(super(TimeSeries,self).__array_wrap__(obj, context),
+                          dates=self._dates)
+    #............................................
+    def __array_finalize__(self,obj):
+        #tslog.info("__array_finalize__ received %s" % type(obj))      
+        if isinstance(obj, TimeSeries):
+            self._dates = obj._dates
+            self._data = obj._series._data
+            self._mask = obj._series._mask
+            self._series = obj._series
+            self._hardmask = obj._series._hardmask
+            self.observed = obj.observed
+            self._fill_value = obj._fill_value
+        else:     
+            self._dates = self._defaultdates
+            self.observed = self._defaultobserved
+            self._series = MA.array(obj, mask=self._defaultmask, 
+                                    copy=False, hard_mask=self._defaulthardmask)
+            self._mask = self._defaultmask
+            self._data = obj
+            self._hardmask = self._defaulthardmask
+            self.fill_value = self._fill_value
+        self._mask =  self._series._mask
+        self._data = self._series._data
+        self._hardmask = self._series._hardmask
+        #tslog.info("__array_finalize__ sends %s" % type(self))
+        return
+    #............................................
+    def __getattribute__(self,attr):
+        "Returns a given attribute."
+        # Here, we need to be smart: _mask should call _series._mask...
+        if attr in ['_data','_mask','_hardmask']:
+            return getattr(self._series,attr)
+        return super(TimeSeries, self).__getattribute__(attr)
+    
+    def __setattribute__(self,attr, value):
+        """Sets an attribute to a given value."""
+        # Same thing here: if we modify ._mask, we need to modify _series._mask
+        # ...as well
+        super(TimeSeries, self).__setattribute__(attr, value)
+        if attr in ['_data','_mask','_hardmask']:
+            super(self._series.__class__, self._series).__setattribute__(attr, value)
+            setattr(self._series, attr, value)
+    #............................................
+    def __checkindex(self, index):
+        "Checks the validity of an index."
+        if isinstance(index, int):
+            return index
+        if isinstance(index, str):
+            return self._dates.date_to_index(Date(self._dates.freq, string=index))
+        elif isDate(index) or isDateArray(index):
+            return self._dates.date_to_index(index)
+        elif isinstance(index,slice):
+            slice_start = self.__checkindex(index.start)
+            slice_stop = self.__checkindex(index.stop)
+            return slice(slice_start, slice_stop, index.step)
+        elif isTimeSeries(index):
+            index = index._series
+        if getmask(index) is not nomask:
+            msg = "Masked arrays must be filled before they can be used as indices!"
+            raise IndexError, msg
+        return index
+
+    def __getitem__(self, index):
+        """x.__getitem__(y) <==> x[y]
+Returns the item described by i. Not a copy as in previous versions.
+        """
+        index = self.__checkindex(index)
+        data = self._series[index]
+        date = self._dates[index]
+        m = self._mask
+        scalardata = (len(numeric.shape(data))==0)
+        # 
+        if m is nomask:
+            if scalardata:
+                return TimeSeries(data, dates=date)
+            else:
+                return TimeSeries(data, dates=date, mask=nomask, keep_mask=True,
+                                  copy=False)
+        #....
+        mi = m[index]
+        if mi.size == 1:
+            if mi:
+                return TimeSeries(data, dates=date, mask=True)
+            return TimeSeries(data, dates=date, mask=nomask)
+        else:
+            return TimeSeries(data, dates=date, mask=mi)
+    #........................
+    def __setitem__(self, index, value):
+        """x.__setitem__(i, y) <==> x[i]=y
+Sets item described by index. If value is masked, masks those locations.
+        """
+        if self is masked:
+            raise MAError, 'Cannot alter the masked element.'
+        index = self.__checkindex(index)
+        #....
+        if isinstance(value, TimeSeries):
+            assert(_timeseriescompat(self[index], value))
+            self._series[index] = value._series
+        else:
+            self._series[index] = value
+        # Don't forget to update the mask !
+        self._mask = self._series._mask
+        
+    #........................
+    def __getslice__(self, i, j):
+        "Gets slice described by i, j"
+        i = self.__checkindex(i)
+        j = self.__checkindex(j)
+        (data, date) = (self._series[i:j], self._dates[i:j])
+        return TimeSeries(data, dates=date, copy=False)
+    #....
+    def __setslice__(self, i, j, value):
+        "Gets item described by i. Not a copy as in previous versions."
+        i = self.__checkindex(i)
+        j = self.__checkindex(j)
+        #....
+        data = self._series[i:j]
+        if isinstance(value, TimeSeries):
+            assert(_timeseriescompat(self[i:j], value))
+            self._series[i:j] = value._series
+        else:
+            self._series[i:j] = value
+        # Don't forget to update the mask !
+        self._mask = self._series._mask
+    #......................................................
+    def __len__(self):
+        if self.ndim == 0:
+            return 0
+        return ndarray.__len__(self)
+    #......................................................
+    def __str__(self):
+        """Returns a string representation of self (w/o the dates...)"""
+        return str(self._series)
+    def __repr__(self):
+        """Calculates the repr representation, using masked for fill if
+           it is enabled. Otherwise fill with fill value.
+        """
+        desc = """\
+timeseries(data  =
+ %(data)s,
+           dates = 
+ %(time)s, 
+           freq  = %(freq)s)
+"""
+        desc_short = """\
+timeseries(data  = %(data)s,
+           dates = %(time)s,
+           freq  = %(freq)s)
+"""
+        if numeric.size(self._dates) > 2 and self.isvalid():
+            timestr = "[%s ... %s]" % (str(self._dates[0]),str(self._dates[-1]))
+        else:
+            timestr = str(self.dates)
+
+        if self.ndim <= 1:
+            return desc_short % {'data': str(self._series),
+                                 'time': timestr,
+                                 'freq': self.freq, }
+        return desc % {'data': str(self._series),
+                       'time': timestr,
+                       'freq': self.freq, }
+    #............................................
+    def _get_mask(self):
+        """Returns the current mask."""
+        return self._series._mask
+    def _set_mask(self, mask):
+        """Sets the mask to `mask`."""
+        mask = make_mask(mask, copy=False, small_mask=True)
+        if mask is not nomask:
+            if mask.size != self._data.size:
+                raise ValueError, "Inconsistent shape between data and mask!"
+            if mask.shape != self._data.shape:
+                mask.shape = self._data.shape
+            self._series._mask = mask  
+        else:
+            self._series._mask = nomask
+    mask = property(fget=_get_mask, fset=_set_mask, doc="Mask")
+ 
+    def ids (self):
+        """Return the ids of the data, dates and mask areas"""
+        return (id(self._series), id(self.dates),)
+        
+    def copy(self):
+        "Returns a copy of the TimeSeries."
+        return TimeSeries(self, copy=True)
+    
+    #------------------------------------------------------
+    @property
+    def series(self):
+        "Returns the series."
+        return self._series
+    @property
+    def dates(self):
+        """Returns the dates"""
+        return self._dates
+    @property
+    def freq(self):
+        """Returns the corresponding frequency."""
+        return self._dates.freq
+#    @property
+    def years(self):
+        """Returns the corresponding years."""
+        return self._dates.years
+#    @property
+    def months(self):
+        """Returns the corresponding months."""
+        return self._dates.months
+#    @property
+    def yeardays(self):
+        """Returns the corresponding days of year."""
+        return self._dates.yeardays
+    day_of_year = yeardays
+#    @property
+    def weekdays(self):
+        """Returns the corresponding days of weeks."""
+        return self._dates.day_of_week
+    day_of_week = weekdays
+    
+    def start_date(self):
+        """Returns the first date of the series."""
+        return self._dates[0]
+#
+    def end_date(self):
+        """Returns the last date of the series."""
+        return self._dates[-1]
+    
+    def isvalid(self):
+        """Returns whether the series has no duplicate/missing dates."""
+        return self._dates.isvalid()
+    
+    def has_missing_dates(self):
+        """Returns whether there's a date gap in the series."""
+        return self._dates.has_missing_dates()
+    
+    def isfull(self):
+        """Returns whether there's no date gap in the series."""
+        return self._dates.isfull()
+    
+    def has_duplicated_dates(self):
+        """Returns whether there are duplicated dates in the series."""
+        return self._dates.has_duplicated_dates()
+    
+    def date_to_index(self, date):
+        "Returns the index corresponding to a given date, as an integer."
+        return self._dates.date_to_index(date) 
+    #.....................................................
+    def asfreq(self, freq=None):
+        "Converts the dates to another frequency."
+        if freq is None:
+            return self
+        return TimeSeries(self._series, dates=self._dates.asfreq(freq))
+    
+    def convert(self, freq, func='auto', position='END', interp=None):
+        "Converts the dates to another frequency, and adapt the data."
+        return convert(self, freq, func=func, position=position, interp=interp)
+        
+##### --------------------------------------------------------------------------
+##--- ... Additional methods ...
+##### --------------------------------------------------------------------------
+class _inplacemethod(object):
+    """Defines a wrapper for inplace arithmetic array methods (iadd, imul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+    """
+    def __init__ (self, binop):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self.f = binop
+        self.obj = None
+    #
+    def __get__(self, obj, objtype=None):
+        "Gets the calling object."
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, other, *args):
+        "Execute the call behavior."
+        instance = self.obj
+        assert(_timeseriescompat(instance,other))
+        func = getattr(instance._series, self.f)    
+        func(other, *args)
+        return instance
+#......................................
+TimeSeries.__iadd__ = _inplacemethod('__iadd__')
+TimeSeries.__iand__ = _inplacemethod('__iand__')
+TimeSeries.__idiv__ = _inplacemethod('__idiv__')
+TimeSeries.__isub__ = _inplacemethod('__isub__')
+TimeSeries.__imul__ = _inplacemethod('__imul__')
+
+
+class _tsmathmethod(object):
+    """Defines a wrapper for arithmetic array methods (add, mul...).
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+The `_dates` part remains unchanged.
+    """
+    def __init__ (self, binop):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self.f = binop
+    #
+    def __get__(self, obj, objtype=None):
+        "Gets the calling object."
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, other, *args):
+        "Execute the call behavior."
+        instance = self.obj
+        _dates = instance._dates
+        #tslog.info("_tsmathmethod: series: %s" % instance,)
+        #tslog.info("_tsmathmethod: other  : %s" % other,)
+        func = getattr(instance._series, self.f)    
+        if isinstance(other, TimeSeries):
+            assert(_timeseriescompat(instance, other))
+        return instance.__class__(func(other, *args), dates=_dates,)
+#......................................
+TimeSeries.__add__ = _tsmathmethod('__add__')
+TimeSeries.__radd__ = _tsmathmethod('__add__')
+TimeSeries.__sub__ = _tsmathmethod('__sub__')
+TimeSeries.__rsub__ = _tsmathmethod('__rsub__')
+TimeSeries.__pow__ = _tsmathmethod('__pow__')
+TimeSeries.__mul__ = _tsmathmethod('__mul__')
+TimeSeries.__rmul__ = _tsmathmethod('__mul__')
+TimeSeries.__div__ = _tsmathmethod('__div__')
+TimeSeries.__rdiv__ = _tsmathmethod('__rdiv__')
+TimeSeries.__truediv__ = _tsmathmethod('__truediv__')
+TimeSeries.__rtruediv__ = _tsmathmethod('__rtruediv__')
+TimeSeries.__floordiv__ = _tsmathmethod('__floordiv__')
+TimeSeries.__rfloordiv__ = _tsmathmethod('__rfloordiv__')
+TimeSeries.__eq__ = _tsmathmethod('__eq__')
+TimeSeries.__ne__ = _tsmathmethod('__ne__')
+TimeSeries.__lt__ = _tsmathmethod('__lt__')
+TimeSeries.__le__ = _tsmathmethod('__le__')
+TimeSeries.__gt__ = _tsmathmethod('__gt__')
+TimeSeries.__ge__ = _tsmathmethod('__ge__')
+#................................................
+class _tsarraymethod(object):
+    """Defines a wrapper for basic array methods.
+When called, returns a new TimeSeries object, with the new series the result of
+the method applied on the original series.
+If `ondates` is True, the same operation is performed on the `_dates`.
+If `ondates` is False, the `_dates` part remains unchanged.
+    """
+    def __init__ (self, methodname, ondates=False):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self._name = methodname
+        self._ondates = ondates
+    #
+    def __get__(self, obj, objtype=None):
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, *args):
+        "Execute the call behavior."
+        _name = self._name
+        instance = self.obj
+        func_series = getattr(instance._series, _name)
+        if self._ondates:
+            func_dates = getattr(instance._dates, _name)
+            return instance.__class__(func_series(*args), 
+                                      dates=func_dates(*args))
+        else:
+            return instance.__class__(func_series(*args), 
+                                      dates=instance._dates)  
+#TimeSeries.astype = _tsarraymethod('astype')
+TimeSeries.reshape = _tsarraymethod('reshape', ondates=True)
+TimeSeries.copy = _tsarraymethod('copy', ondates=True)
+TimeSeries.compress = _tsarraymethod('compress', ondates=True)
+TimeSeries.ravel = _tsarraymethod('ravel', ondates=True)
+TimeSeries.filled = _tsarraymethod('filled', ondates=False)
+TimeSeries.cumsum = _tsarraymethod('cumsum',ondates=False)
+TimeSeries.cumprod = _tsarraymethod('cumprod',ondates=False)
+TimeSeries.anom = _tsarraymethod('anom',ondates=False)
+
+#......................................
+class _tsaxismethod(object):
+    """Defines a wrapper for array methods working on an axis (mean...).
+When called, returns a ndarray, as the result of the method applied on the series.
+    """
+    def __init__ (self, methodname):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self._name = methodname
+    #
+    def __get__(self, obj, objtype=None):
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, *args, **params):
+        "Execute the call behavior."
+        (_dates, _series) = (self.obj._dates, self.obj._series)
+        func = getattr(_series, self._name)
+        result = func(*args, **params)
+        if _series.ndim < 2 or _dates.size == _series.size:
+            return result
+        else:
+            try:
+                axis = params.get('axis', args[0])
+                if axis in [-1, _series.ndim-1]:
+                    result = TimeSeries(result, dates=_dates)
+            except IndexError:
+                pass
+            return result
+#.......................................
+TimeSeries.sum = _tsaxismethod('sum')
+TimeSeries.prod = _tsaxismethod('prod')
+TimeSeries.mean = _tsaxismethod('mean')
+TimeSeries.var = _tsaxismethod('var')
+TimeSeries.varu = _tsaxismethod('varu')
+TimeSeries.std = _tsaxismethod('std')
+TimeSeries.stdu = _tsaxismethod('stdu')
+
+class _tsblockedmethods(object):
+    """Defines a wrapper for array methods that should be temporarily disabled.
+    """
+    def __init__ (self, methodname):
+        """abfunc(fillx, filly) must be defined.
+           abinop(x, filly) = x for all x to enable reduce.
+        """
+        self._name = methodname
+    #
+    def __get__(self, obj, objtype=None):
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, *args, **params):
+        raise NotImplementedError
+TimeSeries.transpose = _tsarraymethod('transpose', ondates=True)
+TimeSeries.swapaxes = _tsarraymethod('swapaxes', ondates=True)
+
+
+#####---------------------------------------------------------------------------
+#---- --- 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(TimeSeries, self._methodname).__doc__
+        except:
+            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')
+#
+##### ---------------------------------------------------------------------------
+#---- ... Additional methods ...
+##### ---------------------------------------------------------------------------
+def tofile(self, output, sep='\t', format_dates=None):
+    """Writes the TimeSeries to a file.
+
+:Parameters:
+    - `output` (String) : Name or handle of the output file.
+    - `sep` (String) : Column separator *['\t']*.
+    - `format` (String) : Data format *['%s']*.
+    """
+    if not hasattr(output, 'writeline'):
+        ofile = open(output,'w')
+    else:
+        ofile = output
+    if format_dates is None:
+        format_dates = self.dates[0].default_fmtstr()
+    oformat = "%%s%s%s" % (sep,format_dates)
+    for (_dates,_data) in numpy.broadcast(self._dates.ravel().asstrings(), 
+                                          filled(self)):
+        ofile.write('%s\n' % sep.join([oformat % (_dates, _data) ]))
+    ofile.close()
+TimeSeries.tofile = tofile
+
+#............................................
+def asrecords(series):
+    """Returns the masked time series as a recarray.
+Fields are `_dates`, `_data` and _`mask`.
+        """
+    desctype = [('_dates',int_), ('_series',series.dtype), ('_mask', bool_)]
+    flat = series.ravel()
+    _dates = numeric.asarray(flat._dates)
+    if flat.size > 0:
+        return recfromarrays([_dates, flat._data, getmaskarray(flat)],
+                             dtype=desctype,
+                             shape = (flat.size,),  
+                             )
+    else:
+        return recfromarrays([[], [], []], dtype=desctype, 
+                             shape = (flat.size,),  
+                             )
+TimeSeries.asrecords = asrecords
+
+def flatten(series):
+    """Flattens a (multi-) time series to 1D series."""
+    shp_ini = series.shape
+    # Already flat time series....
+    if len(shp_ini) == 1:
+        return series
+    # Folded single time series ..
+    newdates = series._dates.ravel()
+    if series._dates.size == series._series.size:
+        newshape = (series._series.size,)
+    else:
+        newshape = (numeric.asarray(shp_ini[:-1]).prod(), shp_ini[-1])
+    newseries = series._series.reshape(newshape)
+    return time_series(newseries, newdates)
+TimeSeries.flatten = flatten
+
+
+
+#####---------------------------------------------------------------------------
+#---- --- Archiving ---
+#####---------------------------------------------------------------------------
+def _tsreconstruct(baseclass, datesclass, baseshape, basetype, fill_value):
+    """Internal function that builds a new TimeSeries from the information stored
+in a pickle."""
+#    raise NotImplementedError,"Please use timeseries.archive/unarchive instead."""
+    _series = ndarray.__new__(ndarray, baseshape, basetype)
+    _dates = ndarray.__new__(datesclass, baseshape, int_)
+    _mask = ndarray.__new__(ndarray, baseshape, bool_)
+    return baseclass.__new__(baseclass, _series, dates=_dates, mask=_mask, 
+                             dtype=basetype, fill_value=fill_value)
+#    
+def _tsgetstate(a):
+    "Returns the internal state of the TimeSeries, for pickling purposes."
+#    raise NotImplementedError,"Please use timeseries.archive/unarchive instead."""
+    records = a.asrecords()
+    state = (1,
+             a.shape, 
+             a.dtype,
+             a.freq,
+             records.flags.fnc,
+             a.fill_value,
+             records
+             )
+    return state
+#    
+def _tssetstate(a, state):
+    """Restores the internal state of the TimeSeries, for pickling purposes.
+`state` is typically the output of the ``__getstate__`` output, and is a 5-tuple:
+
+    - class name
+    - a tuple giving the shape of the data
+    - a typecode for the data
+    - a binary string for the data
+    - a binary string for the mask.
+    """
+    (ver, shp, typ, frq, isf, flv, rec) = state
+    a.fill_value = flv
+    a._dates = a._dates.__class__(rec['_dates'], freq=frq)
+    (a._dates).__tostr = None
+    _data = rec['_series'].view(typ)
+    _mask = rec['_mask'].view(MA.MaskType)
+    a._series = masked_array(_data, mask=_mask)
+#    a._data.shape = shp
+#    a._dates.shape = shp
+#    a._mask = rec['_mask'].view(MA.MaskType)
+#    a._mask.shape = shp
+#        
+def _tsreduce(a):
+    """Returns a 3-tuple for pickling a MaskedArray."""
+    return (_tsreconstruct,
+            (a.__class__, a.dates.__class__, (0,), 'b', -9999),
+            a.__getstate__())
+#    
+TimeSeries.__getstate__ = _tsgetstate
+TimeSeries.__setstate__ = _tssetstate
+TimeSeries.__reduce__ = _tsreduce
+#TimeSeries.__dump__ = dump
+#TimeSeries.__dumps__ = dumps
+
+
+##### -------------------------------------------------------------------------
+#---- --- TimeSeries creator ---
+##### -------------------------------------------------------------------------
+def time_series(data, dates=None, freq=None, observed=None,
+                start_date=None, end_date=None, length=None, include_last=True,
+                mask=nomask,
+                dtype=None, copy=False, fill_value=None,
+                keep_mask=True, small_mask=True, hard_mask=False):
+    """Creates a TimeSeries object
+    
+:Parameters:
+    `dates` : ndarray
+        Array of dates.
+    `data` : 
+        Array of data.
+    """
+    if dates is None:
+        length = _getdatalength(data)
+        dates = date_array(start_date=start_date, end_date=end_date,
+                           length=length, include_last=include_last, freq=freq)   
+    elif not isinstance(dates, DateArray):
+        dates = date_array(dlist=dates, freq=freq)
+    return TimeSeries(data=data, dates=dates, mask=mask, observed=observed,
+                      copy=copy, dtype=dtype, fill_value=fill_value,
+                      keep_mask=keep_mask, small_mask=small_mask, 
+                      hard_mask=hard_mask,)
+
+
+def isTimeSeries(series):
+    "Returns whether the series is a valid TimeSeries object."
+    return isinstance(series, TimeSeries)
+
+##### --------------------------------------------------------------------------
+#---- ... Additional functions ...
+##### --------------------------------------------------------------------------
+def mask_period(data, start_date=None, end_date=None, 
+                inside=True, include_edges=True, inplace=True):
+    """Returns x as an array masked where dates fall outside the selection period,
+as well as where data are initially missing (masked).
+
+:Parameters:
+    `data` : Timeseries
+        Data to process
+    `start_date` : Date *[None]*
+        Starting date. If None, uses the first date.
+    `end_date` : Date *[None]*
+        Ending date. If None, uses the last date.
+    `inside` : Boolean *[True]*
+        Whether the dates inside the range should be masked. If not, masks outside.
+    `include_edges` : Boolean *[True]*
+        Whether the starting and ending dates should be masked.
+    `inplace` : Boolean *[True]*
+        Whether the data mask should be modified in place. If not, returns a new
+        TimeSeries.
+"""
+    if not isTimeSeries(data):
+        raise ValueError,"Data should be a valid TimeSeries!"
+    # Check the starting date ..............
+    if start_date is None:
+        start_date = data._dates[0]
+    elif isinstance(start_date, str):
+        start_date = Date(data.freq, string=start_date)
+    elif not isDateType(start_date):
+        raise DateError,"Starting date should be a valid date!"
+    start_date = max(start_date, data.dates[0])
+    # Check the ending date ................
+    if end_date is None:
+        end_date = data._dates[-1]
+    elif isinstance(end_date, str):
+        end_date = Date(data.freq, string=end_date)
+    elif not isDateType(end_date):
+        raise DateError,"Starting date should be a valid date!"
+    end_date = min(end_date, data.dates[-1])
+    # Constructs the selection mask .........
+    if inside:
+        if include_edges:
+            selection = (data.dates >= start_date) & (data.dates <= end_date)
+        else:
+            selection = (data.dates > start_date) & (data.dates < end_date)
+    else:
+        if include_edges:
+            selection = (data.dates <= start_date) | (data.dates >= end_date)
+        else:
+            selection = (data.dates < start_date) | (data.dates > end_date)
+    # Process the data:
+    if inplace:
+        if data._mask is nomask:
+            data._mask = selection
+        else:
+            data._mask += selection
+    else:
+        return TimeSeries(data, mask=selection, keep_mask=True)
+    return data
+
+def mask_inside_period(data, start_date=None, end_date=None, 
+                       include_edges=True, inplace=True):
+    """Masks values falling inside a given range of dates."""
+    return mask_period(data, start_date=start_date, end_date=end_date, 
+                       inside=True, include_edges=include_edges, inplace=inplace)
+def mask_outside_period(data, start_date=None, end_date=None, 
+                       include_edges=True, inplace=True):
+    """Masks values falling outside a given range of dates."""
+    return mask_period(data, start_date=start_date, end_date=end_date, 
+                       inside=False, include_edges=include_edges, inplace=inplace)
+#..........................................................
+def adjust_endpoints(a, start_date=None, end_date=None):
+    """Returns a TimeSeries going from `start_date` to `end_date`.
+    If `start_date` and `end_date` both fall into the initial range of dates, 
+    the new series is NOT a copy.
+    """
+    # Series validity tests .....................
+    if not isinstance(a, TimeSeries):
+        raise TypeError,"Argument should be a valid TimeSeries object!"
+    if a.freq == 'U':
+        raise TimeSeriesError, \
+            "Cannot adjust a series with 'Undefined' frequency."
+    if not a.dates.isvalid():
+        raise TimeSeriesError, \
+            "Cannot adjust a series with missing or duplicated dates."
+    # Flatten the series if needed ..............
+    a = a.flatten()
+    shp_flat = a.shape
+    # Dates validity checks .,...................
+    msg = "%s should be a valid Date instance! (got %s instead)"
+    (dstart, dend) = a.dates[[0,-1]]
+    if start_date is None: 
+        start_date = dstart
+        start_lag = 0
+    else:
+        if not isDateType(start_date):
+            raise TypeError, msg % ('start_date', type(start_date))
+        start_lag = start_date - dstart
+    #....          
+    if end_date is None: 
+        end_date = dend
+        end_lag = 0
+    else:
+        if not isDateType(end_date):
+            raise TypeError, msg % ('end_date', type(end_date))
+        end_lag = end_date - dend    
+    # Check if the new range is included in the old one
+    if start_lag >= 0:
+        if end_lag == 0:
+            return a[start_lag:]
+        elif end_lag < 0:
+            return a[start_lag:end_lag]
+    # Create a new series .......................
+    newdates = date_array(start_date=start_date, end_date=end_date)
+    newshape = list(shp_flat)
+    newshape[0] = len(newdates)
+    newshape = tuple(newshape)
+    
+    newdata = masked_array(numeric.empty(newshape, dtype=a.dtype), mask=True)
+    newseries = TimeSeries(newdata, newdates)
+    start_date = max(start_date, dstart)
+    end_date = min(end_date, dend) + 1
+    newseries[start_date:end_date] = a[start_date:end_date]
+    return newseries
+#....................................................................
+def align_series(*series, **kwargs):
+    """Aligns several TimeSeries, so that their starting and ending dates match.
+    Series are resized and filled with mased values accordingly.
+    
+    The function accepts two extras parameters:
+    - `start_date` forces the series to start at that given date,
+    - `end_date` forces the series to end at that given date.
+    By default, `start_date` and `end_date` are set to the smallest and largest
+    dates respectively.    
+    """
+    if len(series) < 2:
+        return series  
+    unique_freqs = numpy.unique([x.freq for x in series])
+    try:
+        common_freq = unique_freqs.item()
+    except ValueError:
+        raise TimeSeriesError, \
+            "All series must have same frequency!"
+    if common_freq == 'U':
+        raise TimeSeriesError, \
+            "Cannot adjust a series with 'Undefined' frequency."
+    valid_states = [x.isvalid() for x in series]
+    if not numpy.all(valid_states):
+        raise TimeSeriesError, \
+            "Cannot adjust a series with missing or duplicated dates."
+    
+    start_date = kwargs.pop('start_date', min([x.start_date() for x in series]))
+    if isinstance(start_date,str):
+        start_date = Date(common_freq, string=start_date)
+    end_date = kwargs.pop('end_date', max([x.end_date() for x in series]))
+    if isinstance(end_date,str):
+        end_date = Date(common_freq, string=end_date)
+    
+    return [adjust_endpoints(x, start_date, end_date) for x in series]
+aligned = align_series
+#....................................................................
+def convert(series, freq, func='auto', position='END', interp=None):
+    """Converts a series to a frequency
+       
+    When converting to a lower frequency, func is a function that acts
+    on a 1-d array and returns a scalar or 1-d array. func should handle
+    masked values appropriately. If func is "auto", then an
+    appropriate function is determined based on the observed attribute
+    of the series. If func is None, then a 2D array is returned, where each
+    column represents the values appropriately grouped into the new frequency.
+    interp and position will be ignored in this case.
+        
+    When converting to a higher frequency, position is 'START' or 'END'
+    and determines where the data point is in each period (eg. if going
+    from monthly to daily, and position is 'END', then each data point is
+    placed at the end of the month). Interp is the method that will be used
+    to fill in the gaps. Valid values are "CUBIC", "LINEAR", "CONSTANT", "DIVIDED",
+    and None.
+        
+    Note: interp currently not implemented
+    """
+    if not isinstance(series,TimeSeries):
+        raise TypeError, "The argument should be a valid TimeSeries!"
+    if not series.isvalid():
+        raise TimeSeriesError, \
+            "Cannot adjust a series with missing or duplicated dates."
+    
+    if position.upper() not in ('END','START'): 
+        raise ValueError("invalid value for position argument: (%s)",str(position))
+    
+    toFreq = corelib.fmtFreq(freq)
+    fromFreq = series.freq
+    start_date = series._dates[0]
+    
+    if fromFreq == toFreq:
+        return series.copy()
+    
+    if series.size == 0:
+        return TimeSeries(series, freq=toFreq, 
+                          start_date=start_date.asfreq(toFreq))
+    if func == 'auto':
+        func = corelib.obs_dict[series.observed]
+
+    tempData = series._series.filled()
+    tempMask = getmaskarray(series)
+
+    cRetVal = cseries.reindex(tempData, fromFreq, toFreq, position, 
+                              int(start_date), tempMask)
+    _values = cRetVal['values']
+    _mask = cRetVal['mask']
+    _startindex = cRetVal['startindex']
+    start_date = Date(freq=toFreq, value=_startindex)
+        
+    tempData = masked_array(_values, mask=_mask)
+
+    if tempData.ndim == 2 and func is not None:
+        tempData = MA.apply_along_axis(func, -1, tempData)
+           
+#    newEnd = series._dates[-1].asfreq(toFreq, "AFTER")
+    
+    newseries = TimeSeries(tempData, freq=toFreq, 
+                           observed=series.observed, 
+                           start_date=start_date)
+    return newseries
+#    return adjust_endpoints(newseries, end_date=newEnd)
+TimeSeries.convert = convert
+#....................................................................
+def fill_missing_dates(data, dates=None, freq=None,fill_value=None):
+    """Finds and fills the missing dates in a time series.
+The data corresponding to the initially missing dates are masked, or filled to 
+`fill_value`.
+    
+:Parameters:
+    `data`
+        Initial array of data.
+    `dates` 
+        Initial array of dates. 
+    `freq` : float *[None]*
+        New date resolutions. If *None*, the initial resolution is used instead.
+    `fill_value` : float *[None]*
+        Default value for missing data. If None, the data are just masked.
+    """
+    freq = corelib.fmtFreq(freq)
+    if freq == 'U':
+        raise ValueError,\
+              "Unable to define a proper date resolution (found %s)." % freq
+    if dates is None:
+        if not isTimeSeries(data):
+            raise InsufficientDateError
+        dates = data._dates
+        freq = dates.freq
+        datad = data._series._data
+        datam = data._series._mask
+#        if fill_value is None:
+#            fill_value = data._fill_value
+    elif not isinstance(dates, DateArray):
+        dates = DateArray(dates, freq)
+        if isinstance(data, MaskedArray):
+            datad = data._data
+            datam = data._mask
+        else:
+            datad = data
+            datam = nomask
+    dflat = dates.asfreq(freq).ravel()
+    n = len(dflat)
+    if not dflat.has_missing_dates():
+        return time_series(data, dflat)
+
+    # ...and now, fill it ! ......
+    (tstart, tend) = dflat[[0,-1]]
+    newdates = date_array(start_date=tstart, end_date=tend, include_last=True)
+    nsize = newdates.size
+    #.............................
+    # Get the steps between consecutive data. 
+    delta = dflat.get_steps()-1
+    gap = delta.nonzero()
+    slcid = numpy.r_[[0,], numpy.arange(1,n)[gap], [n,]]
+    oldslc = numpy.array([slice(i,e) for (i,e) in numpy.broadcast(slcid[:-1],slcid[1:])])
+    addidx = delta[gap].astype(int_).cumsum()
+    newslc = numpy.r_[[oldslc[0]], 
+                      [slice(i+d,e+d) for (i,e,d) in \
+                           numpy.broadcast(slcid[1:-1],slcid[2:],addidx)] 
+                     ]
+    #.............................
+    # Just a quick check
+    vdflat = numeric.asarray(dflat)
+    vnewdates = numeric.asarray(newdates)
+    for (osl,nsl) in zip(oldslc,newslc):
+        assert numpy.equal(vdflat[osl],vnewdates[nsl]).all(),\
+            "Slicing mishap ! Please check %s (old) and %s (new)" % (osl,nsl)
+    #.............................
+    data = MA.asarray(data)
+    newdatad = numeric.empty(nsize, data.dtype)
+    newdatam = numeric.ones(nsize, bool_)
+#    if fill_value is None:
+#        if hasattr(data,'fill_value'):
+#            fill_value = data.fill_value
+#        else:
+#            fill_value = MA.default_fill_value(data)
+    #data = data.filled(fill_value)
+    #....
+    if datam is nomask:
+        for (new,old) in zip(newslc,oldslc):
+            newdatad[new] = datad[old]
+            newdatam[new] = False
+    else:
+        for (new,old) in zip(newslc,oldslc):
+            newdatad[new] = datad[old]
+            newdatam[new] = datam[old]
+    newdata = MA.masked_array(newdatad, mask=newdatam, fill_value=fill_value)    
+    # Get new shape ..............
+    if data.ndim == 1:
+        nshp = (newdates.size,)
+    else:
+        nshp = tuple([-1,] + list(data.shape[1:]))
+    return time_series(newdata.reshape(nshp), newdates)
+
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.DEBUG)
+    from maskedarray.testutils import assert_equal
+    if 0:
+        dlist = ['2007-01-%02i' % i for i in range(1,16)]
+        dates = date_array(dlist)
+        data = masked_array(numeric.arange(15, dtype=float_), mask=[1,0,0,0,0]*3)
+#        btseries = BaseTimeSeries(data._data, dates)
+        tseries = time_series(data, dlist)
+        dseries = numpy.log(tseries)
+    if 1:
+        mlist = ['2005-%02i' % i for i in range(1,13)]
+        mlist += ['2006-%02i' % i for i in range(1,13)]
+        mdata = numpy.arange(24)
+        mser1 = time_series(mdata, mlist, observed='SUMMED')
+        #
+        mlist2 = ['2004-%02i' % i for i in range(1,13)]
+        mlist2 += ['2005-%02i' % i for i in range(1,13)]
+        mser2 = time_series(mdata, mlist2, observed='SUMMED')
+        #
+        today = thisday('m')
+        (malg1,malg2) = aligned(mser1, mser2)
+        
+        C = convert(mser2,'A')
+        D = convert(mser2,'A',func=None)
+        
+    if 0:
+        dlist = ['2007-01-%02i' % i for i in range(1,16)]
+        dates = date_array(dlist)
+        print "."*50+"\ndata"
+        data = masked_array(numeric.arange(15)-6, mask=[1,0,0,0,0]*3)
+        print "."*50+"\nseries"
+        tseries = time_series(data, dlist)
+        
+    if 1:
+        dlist_1 = ['2007-01-%02i' % i for i in range(1,8)]
+        dlist_2 = ['2007-01-%02i' % i for i in numpy.arange(1,28)[::4]]
+        data = masked_array(numeric.arange(7), mask=[1,0,0,0,0,0,0])
+        tseries_1 = time_series(data, dlist_1)
+        tseries_2 = time_series(data, dlist_2)
+        tseries_3 = time_series(data[::-1], dlist_2)
+        
+        try:
+            tseries = tseries_1 + tseries_2
+        except TimeSeriesCompatibilityError:
+            print "I knew it!"
+        tseries = tseries_2 + tseries_3
+        assert_equal(tseries._dates, tseries_3._dates)
+        assert_equal(tseries._mask, [1,0,0,0,0,0,1])
+                
+    if 1:
+        mser3 = time_series(MA.mr_[malg1._series, 100+malg2._series].reshape(2,-1).T, 
+                            dates=malg1.dates)
+        data = mser3._series._data
+


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



More information about the Scipy-svn mailing list