[Scipy-svn] r2476 - in trunk/Lib/sandbox/timeseries: . mtimeseries

scipy-svn at scipy.org scipy-svn at scipy.org
Tue Jan 2 12:55:03 EST 2007


Author: pierregm
Date: 2007-01-02 11:54:31 -0600 (Tue, 02 Jan 2007)
New Revision: 2476

Added:
   trunk/Lib/sandbox/timeseries/mtimeseries/
   trunk/Lib/sandbox/timeseries/mtimeseries/__init__.py
   trunk/Lib/sandbox/timeseries/mtimeseries/example.py
   trunk/Lib/sandbox/timeseries/mtimeseries/test_date.py
   trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py
   trunk/Lib/sandbox/timeseries/mtimeseries/tests/
   trunk/Lib/sandbox/timeseries/mtimeseries/timeseries_ini.py
   trunk/Lib/sandbox/timeseries/mtimeseries/tscore.py
   trunk/Lib/sandbox/timeseries/mtimeseries/tsdate.py
   trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py
Log:
support for maskedarray


Property changes on: trunk/Lib/sandbox/timeseries/mtimeseries
___________________________________________________________________
Name: svn:ignore
   + test_timeseriesA.py
tseriesA.py
crapper


Added: trunk/Lib/sandbox/timeseries/mtimeseries/__init__.py
===================================================================

Added: trunk/Lib/sandbox/timeseries/mtimeseries/example.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/example.py	2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/example.py	2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,290 @@
+
+"""
+=== 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

Added: trunk/Lib/sandbox/timeseries/mtimeseries/test_date.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/test_date.py	2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/test_date.py	2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,145 @@
+# 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 as N
+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
+reload(maskedarray.testutils)
+from maskedarray.testutils import assert_equal, assert_array_equal
+
+import tsdate
+reload(tsdate)
+from tsdate import *
+
+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 = datearray_fromlist(dlist, 'D')
+        assert_equal(dates.freq,'D')
+        assert(dates.isfull())
+        assert(not dates.ispacked())
+        assert_equal(dates, 732677+numpy.arange(len(dlist)))
+        # as simple, but we need to guess the frequency this time
+        dates = datearray_fromlist(dlist, 'D')
+        assert_equal(dates.freq,'D')
+        assert(dates.isfull())
+        assert(not dates.ispacked())
+        assert_equal(dates, 732677+numpy.arange(len(dlist)))
+        # Still daily data, that we force to month
+        dates = datearray_fromlist(dlist, 'M')
+        assert_equal(dates.freq,'M')
+        assert(not dates.isfull())
+        assert(dates.ispacked())
+        assert_equal(dates, [24085]*len(dlist))
+        # Now, for monthly data
+        dlist = ['2007-%02i' % i for i in range(1,13)]
+        dates = datearray_fromlist(dlist, 'M')
+        assert_equal(dates.freq,'M')
+        assert(dates.isfull())
+        assert(not dates.ispacked())
+        assert_equal(dates, 24085 + numpy.arange(12))
+        # Monthly data  w/ guessing
+        dlist = ['2007-%02i' % i for i in range(1,13)]
+        dates = datearray_fromlist(dlist, )
+        assert_equal(dates.freq,'M')
+        assert(dates.isfull())
+        assert(not dates.ispacked())
+        assert_equal(dates, 24085 + 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 = datearray_fromlist(dlist)
+        assert_equal(dates.freq,'U')
+        assert(not dates.isfull())
+        assert(not dates.ispacked())
+        assert_equal(dates.tovalue(),732676+numpy.array([1,2,4,5,7,8,10,11,13]))
+        #
+        ddates = datearray_fromlist(dlist, 'D')
+        assert_equal(ddates.freq,'D')
+        assert(not ddates.isfull())
+        assert(not ddates.ispacked())
+        #
+        mdates = datearray_fromlist(dlist, 'M')
+        assert_equal(mdates.freq,'M')
+        assert(not dates.isfull())
+        assert(mdates.ispacked())
+        #
+    
+    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 = datearray_fromlist(dlist)
+        dobj = [datetime.datetime.fromordinal(d) for d in dates.toordinal()]
+        odates = datearray_fromlist(dobj)
+        assert_equal(dates,odates)
+        dobj = [mxDFromString(d) for d in dlist]
+        odates = datearray_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 = datearray_fromlist(dlist).asfreq('M')
+        # Using an integer
+        assert_equal(mdates[0].value, 24085)
+        assert_equal(mdates[-1].value, 24096)
+        # Using a date
+        lag = mdates.find_dates(mdates[0])
+        assert_equal(mdates[lag], mdates[0])
+        lag = mdates.find_dates(Date('M',value=24092))
+        assert_equal(mdates[lag], mdates[5])
+        # Using several dates
+        lag = mdates.find_dates(Date('M',value=24085), Date('M',value=24096))
+        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 = datearray_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 = datearray_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

Added: trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py	2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/test_timeseries.py	2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,273 @@
+# 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 numpy as N
+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
+reload(maskedarray.testutils)
+from maskedarray.testutils import assert_equal, assert_array_equal
+
+import tsdate
+reload(tsdate)
+from tsdate import date_array_fromlist
+import tseries
+reload(tseries)
+from tseries import *
+
+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"
+        (tseries, data, dates) = self.d
+        last_date = tseries[-1]._dates
+        assert_equal(tseries[-1], tseries[last_date])
+        assert_equal(tseries._dates[-1], dates[-1])
+        assert_equal(tseries[-1]._dates, dates[-1])
+        assert_equal(tseries[last_date]._dates, dates[-1])
+        assert_equal(tseries._series[-1], data._data[-1])
+        assert_equal(tseries[-1]._series, data._data[-1])
+        assert_equal(tseries._mask[-1], data._mask[-1])
+        #
+        tseries['2007-01-06'] = 999
+        assert_equal(tseries[5], 999)
+        #
+    def test_wtimeseries(self):
+        "Tests getitem w/ TimeSeries as index"
+        (tseries, data, dates) = self.d
+        # Testing a basic condition on data
+        cond = (tseries<8).filled(False)
+        dseries = tseries[cond]
+        assert_equal(dseries._data, [1,2,3,4,6,7])
+        assert_equal(dseries._dates, tseries._dates[[1,2,3,4,6,7]])
+        assert_equal(dseries._mask, nomask)
+        # Testing a basic condition on dates
+        tseries[tseries._dates < Date('D',string='2007-01-06')] = MA.masked
+        assert_equal(tseries[:5]._series._mask, [1,1,1,1,1])
+    
+    def test_wslices(self):
+        "Test get/set items."
+        (tseries, data, dates) = self.d
+        # Basic slices
+        assert_equal(tseries[3:7]._series._data, data[3:7]._data)
+        assert_equal(tseries[3:7]._series._mask, data[3:7]._mask)
+        assert_equal(tseries[3:7]._dates, dates[3:7])
+        # Ditto
+        assert_equal(tseries[:5]._series._data, data[:5]._data)
+        assert_equal(tseries[:5]._series._mask, data[:5]._mask)
+        assert_equal(tseries[:5]._dates, dates[:5])
+        # With set
+        tseries[:5] = 0
+        assert_equal(tseries[:5]._series, [0,0,0,0,0])
+        dseries = numpy.log(tseries)
+        tseries[-5:] = dseries[-5:]
+        assert_equal(tseries[-5:], dseries[-5:])
+        # Now, using dates !
+        dseries = tseries[tseries.dates[3]:tseries.dates[7]]
+        assert_equal(dseries, tseries[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"
+        (tseries, data, dates) = self.d
+        dseries = adjust_endpoints(tseries, tseries.dates[0], tseries.dates[-1])
+        assert_equal(dseries, tseries)
+        dseries = adjust_endpoints(tseries, tseries.dates[3], tseries.dates[-3])
+        assert_equal(dseries, tseries[3:-2])
+        dseries = adjust_endpoints(tseries, end_date=Date('D', string='2007-01-31'))
+        assert_equal(dseries.size, 31)
+        assert_equal(dseries._mask, numpy.r_[tseries._mask, [1]*16])
+        dseries = adjust_endpoints(tseries, end_date=Date('D', string='2007-01-06'))
+        assert_equal(dseries.size, 6)
+        assert_equal(dseries, tseries[:6])
+        dseries = adjust_endpoints(tseries, 
+                                   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, numpy.r_[tseries._mask[5:], [1]*16])
+    #
+    def test_maskperiod(self):        
+        "Test mask_period"
+        (tseries, data, dates) = self.d
+        tseries.mask = nomask
+        (start, end) = ('2007-01-06', '2007-01-12')
+        mask = mask_period(tseries, start, end, inside=True, include_edges=True,
+                           inplace=False)
+        assert_equal(mask._mask, numpy.array([0,0,0,0,0,1,1,1,1,1,1,1,0,0,0]))
+        mask = mask_period(tseries, 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(tseries, 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(tseries, 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])
+        
+###############################################################################
+#------------------------------------------------------------------------------
+if __name__ == "__main__":
+    NumpyTest().run()        
\ No newline at end of file

Added: trunk/Lib/sandbox/timeseries/mtimeseries/timeseries_ini.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/timeseries_ini.py	2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/timeseries_ini.py	2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,1494 @@
+# 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 GF Gerard-Marchant
+:contact: pierregm_at_uga_edu
+:date: $Date: 2006-12-05 20:40:46 -0500 (Tue, 05 Dec 2006) $
+:version: $Id: timeseries.py 25 2006-12-06 01:40:46Z backtopop $
+"""
+__author__ = "Pierre GF Gerard-Marchant ($Author: backtopop $)"
+__version__ = '1.0'
+__revision__ = "$Revision: 25 $"
+__date__     = '$Date: 2006-12-05 20:40:46 -0500 (Tue, 05 Dec 2006) $'
+
+
+#import numpy as N
+
+#from numpy.core import bool_, float_, int_
+import numpy.core.umath as umath
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.numeric as numeric
+from numpy import ndarray
+from numpy.core.records import recarray
+from numpy.core.records import fromarrays as recfromarrays
+
+#from cPickle import dump, dumps
+
+import core
+reload(core)
+from core import *
+
+
+import maskedarray as MA
+#reload(MA)
+from maskedarray.core import domain_check_interval, \
+    domain_greater_equal, domain_greater, domain_safe_divide, domain_tan  
+from maskedarray.core import MaskedArray, MAError, masked_array, isMaskedArray,\
+    getmask, getmaskarray, filled, mask_or, make_mask
+from maskedarray.core import convert_typecode, masked_print_option, \
+    masked_singleton
+from maskedarray.core import load, loads, dump, dumps
+
+import addons.numpyaddons
+reload(addons.numpyaddons)
+from addons.numpyaddons import ascondition
+
+#...............................................................................
+talog = logging.getLogger('TimeArray')
+tslog = logging.getLogger('TimeSeries')
+mtslog = logging.getLogger('MaskedTimeSeries')
+
+nomask = MA.nomask
+masked = MA.masked
+masked_array = MA.masked_array
+ufunc_domain = {}
+ufunc_fills = {}
+
+
+#### --------------------------------------------------------------------------
+#--- ... Date Tools ...
+#### --------------------------------------------------------------------------
+dtmdefault = dtm.datetime(2001,1,1)
+
+def isDate(obj):
+    """Returns *True* if the argument is a `datetime.date` or `datetime.datetime`
+instance."""
+    return isinstance(obj,dtm.date) or isinstance(obj,dtm.datetime)
+    
+def fake_dates(count, start=dtmdefault):
+    """fake_dates(count, start=dtmdefault)
+Returns a *count x 1* array of daily `datetime` objects.
+:Parameters:
+    - `count` (Integer) : Number of dates to output
+    - `start` (datetime object) : Starting date *[NOW]*."""
+    dobj = list(dtmrule.rrule(DAILY,count=count,dtstart=start))
+    return N.array(dobj)
+
+#### --------------------------------------------------------------------------
+#--- ... TimeArray class ...
+#### --------------------------------------------------------------------------
+class TimeArrayError(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 TimeArray(ndarray):
+    """Stores datetime.dateime objects in an array."""
+    def __new__(subtype, series, copy=False):
+        vlev = 3
+        if isinstance(series, TimeArray):
+#            verbose.report("DA __new__: data isDA types %s, %s, %i" % \
+#                           (type(series), series, len(series)), vlev)
+            if not copy:            
+                return series
+            else: 
+                return series.copy()
+        else:
+            data = N.array(series)
+            if data.dtype.str == "|O8":
+                new = data #.view(subtype)
+                talog.debug("__new__: data isnotDA types %s, %s, %s, %i" % \
+                            (type(series), data.dtype, series, data.size),)
+            else:
+                new = N.empty(data.shape, dtype="|O8")
+                newflat = new.flat
+                talog.debug("DA __new__: data isnotDA types %s, %s, %s, %i" % \
+                            (type(series), data.dtype, series, data.size),)
+                # Forces years (as integers) to be read as characters 
+                if N.all(data < 9999):
+                    data = data.astype("|S4")
+                # Parse dates as characters
+                if data.dtype.char == "S":
+                    for (k,s) in enumerate(data.flat):
+                        newflat[k] = dtmparser.parse(s, default=dtmdefault)
+                else:
+                    # Parse dates as ordinals
+                    try:
+                        for k,s in enumerate(data.flat):
+                            newflat[k] = dtm.datetime.fromordinal(s)
+                    except TypeError:
+                        raise TypeError, "unable to process series !"
+                subtype._asobject = new
+        return new.view(subtype)
+#    #............................................
+#    def __array_wrap__(self, obj):
+#        return TimeArray(obj)
+    #............................................
+    def __array_finalize__(self, obj):
+        self._resolution = None
+        (self._years, self._months, self._days, self._yeardays) = [None]*4
+        self._asobjects = None
+        self._asstrings = None
+        self._asordinals = None
+        return
+    #............................................
+    def __len__(self):
+        "Returns the length of the object. Or zero if it fails."
+        if self.ndim == 0:
+            return 0
+        return ndarray.__len__(self)
+        
+        
+    def __getitem__(self,i):
+        # Force a singleton to DateArray
+        try:
+            obj = ndarray.__getitem__(self,i)
+        except IndexError:
+            obj = self
+        return TimeArray(obj)
+#        if isDate(obj):
+#            return DateArray(obj)
+#        else:
+#            return obj
+    #............................................
+    def __eq__(self, other):
+        return N.asarray(self).__eq__(N.asarray(other))
+    
+    def __add__(self, other):
+        if not isinstance(other, dtm.timedelta):
+            raise TypeError, "Unsupported type for add operation"
+        return TimeArray(N.asarray(self).__add__(other))
+    
+    def __sub__(self, other):
+        if not isinstance(other, dtm.timedelta) and \
+           not isinstance(other, TimeArray):
+            raise TypeError, "Unsupported type for sub operation"
+        return N.asarray(self).__sub__(N.asarray(other))       
+    
+    def __mul__(self, other):
+        raise TimeArrayError, "TimeArray objects cannot be multiplied!"
+    __rmul__ = __mul__
+    __imul__ = __mul__
+#    def shape(self):
+#        if self.size == 1:
+#            return (1,)
+#        else:
+#            return self._data.shape
+    #............................................
+    def __str__(self):
+        """x.__str__() <=> str(x)"""
+        return str(self.asstrings())
+    def __repr__(self):
+        """Calculate the repr representation, using masked for fill if
+           it is enabled. Otherwise fill with fill value.
+        """
+        template = """\
+timearray( 
+ %(data)s,)"""
+        template_short = """\
+timearray(%(data)s)"""
+        if self.ndim <= 1:
+            return template_short % {'data': str(self)}
+        else:
+            return template % {'data': str(self)} 
+    #............................................
+    @property
+    def resolution(self):
+        """Calculates the initial resolution, as the smallest difference
+between consecutive values (in days)."""
+        if self._resolution is None:
+            self._resolution = N.diff(self.asordinals().ravel()).min()
+            if self._resolution <= 3:
+                self._resolution = 1
+            elif self._resolution <= 10:
+                self._resolution = 7
+            elif self._resolution <= 270:
+                self._resolution = 30
+            else:
+                self._resolution = 365
+        return self._resolution
+    timestep = resolution
+    #............................................
+    def has_missing_dates(self, resolution=None):
+        """Returns *True* there's a gap in the series, assuming a regular series
+with a constant timestep of `resolution`.
+If `resolution` is None, uses the default resolution of `self`.
+"""
+        if self.size < 2:
+            return False
+        dt = N.diff(self.asordinals().ravel())
+        if resolution is None:
+            resolution = self.resolution
+        if resolution == 1:
+            return (dt.max() > 1)
+        elif resolution == 30:
+            return (dt.max() > 31)
+        elif resolution == 365:
+            return (dt.max() > 730)
+        else:
+            #FIXME: OK, there's probly something wrong here...
+            return True
+    #............................................
+    def asobjects(self):
+        """Transforms an array of ordinal dates to an array of date objects."""
+        if self._asobjects is None:
+            if self.size == 1:
+                self._asobjects = self.item()
+            else:
+                self._asobjects = self
+        return self._asobjects
+    #............................................
+    def asordinals(self):
+        """Transforms an array of dates to an array of date objects."""
+        if self._asordinals is None:
+            # Build list of datetime objects
+            self._asordinals = N.empty(self.shape,dtype=int_)
+            _asordinalsflat = self._asordinals.flat
+            if self.size == 0:
+                self._asordinals = dtm.datetime.toordinal(dtmdefault)
+            elif self.size == 1:
+                self._asordinals = dtm.datetime.toordinal(self.item())
+            else:
+                itr = (dtm.datetime.toordinal(val) for val in self.flat)
+                self._asordinals = N.fromiter(itr, float_)
+        return self._asordinals
+    #............................................
+    def asstrings(self, stringsize=10):
+        """Transforms a *N*-array of ordinal dates to a *N*-list of  
+datestrings `YYYY-MM-DD`.
+
+:Parameters:
+    `stringsize` : Integer *[10]*
+        String size. 
+        
+        - `< 4' outputs 4
+        - `< 8' outputs 8
+        - anything else outputs 10.
+        """ 
+        if not stringsize:
+            strsize = 10
+        elif stringsize <= 4:
+            strsize = 4
+        elif stringsize <= 8:
+            strsize = 7
+        else:
+            strsize = 10      
+        if self._asstrings is None:
+            deftype = "|S10"
+            if self.size == 0:
+                self._asstrings = N.array(dtmdefault.isoformat(), dtype=deftype)
+            elif self.size == 1:
+                self._asstrings = N.array(self.item().isoformat(), dtype=deftype)
+            else:
+                itr = (val.isoformat() for val in self.flat)
+                self._asstrings = N.fromiter(itr,dtype=deftype).reshape(self.shape) 
+        return self._asstrings.getfield('S%i' % strsize)
+    #............................................  
+    def astype(self, newtype):
+        """Outputs the array in the type provided in argument."""
+        newtype = N.dtype(newtype)
+        if newtype.type == int_:
+            return self.asordinals()
+        elif newtype.type == str_:
+            n = newtype.itemsize
+            if n > 0 :
+                return self.asstrings(stringsize=n)
+            else:
+                return self.asstrings(stringsize=n)
+        elif newtype.type == object_:
+            return self
+        else:
+            raise ValueError, "Invalid type: %s" % newtype
+    #------------------------------------------------------
+    @property
+    def years(self):
+        """Returns the corresponding year (as integer)."""
+        if self._years is None:
+            self._years = self.asstrings(1).astype(int_)
+        return self._years
+    @property
+    def months(self):
+        """Returns the corresponding month (as integer)."""
+        if self._months is None:
+            self._months = N.empty(self.shape, dtype=int_)
+            _monthsflat = self._months.flat
+            if self.size == 1:
+                try:
+                    _monthsflat[:] = self.asobjects().month
+                except AttributeError:
+                    _monthsflat[:] = 1
+            else:
+                for (k,val) in enumerate(self.asobjects().flat):
+                    _monthsflat[k] = val.month
+        return self._months    
+    @property
+    def days(self):
+        """Returns the corresponding day of month (as integer)."""
+        if self._days is None:
+            self._days = N.empty(self.shape, dtype=int_)
+            _daysflat = self._days.flat
+            if self.size == 1:
+                try:
+                    _daysflat[:] = self.asobjects().day
+                except AttributeError:
+                    _daysflat[:] = 1
+            else:
+                for (k,val) in enumerate(self.asobjects().flat):
+                    _daysflat[k] = val.day
+        return self._days
+    @property
+    def yeardays(self):
+        """Returns the corresponding day of year (as integer)."""
+        if self._yeardays is None:
+            self._yeardays = N.empty(self.size, dtype=int_)
+            _doyflat = self._yeardays.flat
+            for (k,val,yyy) in N.broadcast(N.arange(len(self)),
+                                           self.asordinals(),
+                                           self.years.flat):
+                _doyflat[k] = val - dtm.datetime(yyy-1,12,31).toordinal()
+            self._yeardays.reshape(self.shape).astype(int_)
+        return self._yeardays
+#................................................
+def isTimeArray(x):
+    """Checks whether `x` is n array of times/dates (an instance of `TimeArray` )."""
+    return isinstance(x,TimeArray)
+
+def time_array(t, copy=False):
+    """Creates a date array from the series `t`.
+    """
+    if isinstance(t,TimeArray):
+        dobj = t
+    else:
+        t = N.asarray(t)
+        if t.dtype.str == "|O8":
+            dobj = t
+        else:
+            dobj = N.empty(t.shape, dtype="|O8")
+            dobjflat = dobj.flat
+            if t.dtype.char == 'S':
+                for k,s in enumerate(t.flat):
+                    dobjflat[k] = dtmparser.parse(s)
+            else:
+                try:
+                    for k,s in enumerate(t.flat):
+                        dobjflat[k] = dtm.datetime.fromordinal(s)
+                except TypeError:
+                    raise TypeError, "unable to process series !"
+    return TimeArray(dobj, copy=copy)
+            
+#### --------------------------------------------------------------------------
+#--- ... Time Series ...
+#### --------------------------------------------------------------------------
+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__
+#......................................
+#TODO: __eq__, __cmp__ classes
+class TimeSeries(ndarray, object): 
+    """Base class for the definition of time series.
+A time series is here defined as the combination of two arrays:
+    
+    - an array storing the time information (as a `TimeArray` instance);
+    - an array storing the data (as a `MaskedArray` instance.
+    """
+    def __new__(cls, data, dates=None, dtype=None, copy=True):
+        tslog.debug("__new__: data types %s, %s" % (type(data), dtype))
+#        if isinstance(data, TimeSeries):
+        if hasattr(data,"_series") and hasattr(data,"_dates"):
+            tslog.debug("__new__: data has _series and _dates")
+            tslog.debug("__new__: setting basedates",)
+            cls._basedates = data._dates
+            tslog.debug("__new__: setting basedates done",)
+            if (not copy) and (dtype == data.dtype):
+                return N.asarray(data).view(cls)
+            else:
+                return N.array(data, dtype=dtype).view(cls)
+        #..........
+        dsize = N.size(data) 
+        tslog.debug("__new__: args dates type %s, %s" % (type(dates), dates),)
+        if dates is None:
+            _dates = TimeArray(fake_dates(N.size(data)))
+            tslog.debug("__new__: args dates FAKED type %s, %s, %i" % \
+                       (type(dates), _dates, _dates.size),)
+        else:
+            _dates = TimeArray(dates)  
+        tslog.debug("__new__: dates set to %s" % _dates,)
+        if _dates.size != dsize:
+            msg = "Incompatible sizes between dates (%i) and data (%i) !"
+            raise ValueError, msg % (_dates.size, dsize)
+        _dates.shape = N.shape(data)
+        cls._basedates = _dates
+        return N.array(data, copy=copy, dtype=dtype).view(cls)
+    #..................................
+    def __array_wrap__(self, obj):
+        return TimeSeries(obj, dates=self._dates)
+    #..................................
+    def __array_finalize__(self,obj):
+        if not hasattr(self,"_series"):
+            try:
+                self._series = obj._series
+                tslog.debug("__array_finalize__: obj._data => : %s - %s" % \
+                           (id(self._series), self._series.ravel() ),)
+            except AttributeError:
+                self._series = obj
+                tslog.debug("__array_finalize__: obj       => : %s - %s" % \
+                           (id(self._series), self._series.ravel() ),)
+        if not hasattr(self,"_dates"):
+            self._dates = self._basedates
+            tslog.debug("__array_finalize__: dates set!  => : %s - %s" % \
+                       (id(self._dates), self._dates.ravel() ))
+        return
+    #........................
+    def _get_flat(self):
+        """Calculate the flat value.
+        """
+        return self.__class__(self._series.ravel(), 
+                              dates = self._dates.ravel(), copy=False)
+    #....
+    def _set_flat (self, value):
+        "x.flat = value"
+        y = self.ravel()
+        y[:] = value
+    flat = property(fget=_get_flat,fset=_set_flat,doc='Access array in flat form.')   
+    #........................
+    def _get_shape(self):
+        "Return the current shape."
+        return self._series.shape
+    #....
+    def _set_shape (self, newshape):
+        "Set the array's shape."
+        self._series.shape = self._dates.shape = newshape
+    #....
+    shape = property(fget=_get_shape, fset=_set_shape, doc="Array shape")
+    #....
+    @property
+    def ndim(self):
+        """Returns the number of dimensions."""
+        return self._series.ndim
+    @property
+    def size(self):
+        """Returns the total number of elements."""
+        return self._series.size
+    #........................
+    def __getitem__(self, i):
+        "Get item described by i. Not a copy as in previous versions."
+        (tout, dout) = (self._dates[i], self._series[i])
+        return self.__class__(dout, dates=tout)
+    #....
+    def __setitem__(self, index, value):
+        "Gets item described by i. Not a copy as in previous versions."
+        self._series[index] = value
+        return self
+    #........................
+    def __getslice__(self, i, j):
+        "Gets slice described by i, j"
+        (tout, dout) = (self._dates[i:j], self._series[i:j])
+        return self.__class__(dout, dates=tout,)
+    #....
+    def __setslice__(self, i, j, value):
+        "Gets item described by i. Not a copy as in previous versions."
+        #TODO: Here, we should have a better test for self._dates==value._dates
+        if hasattr(value,"_dates"):
+            tslog.debug("__setslice__: value: %s" % str(value))
+            tslog.debug("__setslice__: dates: %s" % str(value._dates),)
+            if not (self._dates == value._dates).all():
+                raise TimeSeriesError,"Can't force a change of dates !"
+        self._series[i:j] = value
+        return self       
+    #........................
+    def ravel (self):
+        """Returns a 1-D view of self."""
+        return self.__class__(self._series.ravel(), 
+                              dates=self._dates.ravel())
+    #........................
+    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, )
+"""
+        desc_short = """\
+timeseries(data  = %(data)s,
+           dates = %(time)s,)
+"""
+        if self.ndim <= 1:
+            return desc_short % {
+                'data': str(self._series),
+                'time': str(self.dates),
+                }
+        return desc % {
+            'data': str(self._series),
+            'time': str(self.dates),
+            }
+ 
+    def ids (self):
+        """Return the ids of the data, dates and mask areas"""
+        return (id(self._series), id(self.dates),)
+    
+    @property
+    def series(self):
+        "Returnhs the series."
+        return self._series
+    
+    #------------------------------------------------------
+    @property
+    def dates(self):
+        """Returns the dates"""
+        return self._dates
+###    def _set_dates(self, object):
+###        """Returns the dates"""
+###        self._dates = object
+###    dates = property(fget=_get_dates, fset=_set_dates, doc="Dates")
+    @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 yuear."""
+        return self._dates.yeardays
+
+    def has_missing_dates(self):
+        """Returns whether there's a date gap in the series."""
+        return self._dates.has_missing_dates()
+
+#### --------------------------------------------------------------------------
+#--- ... Additional methods ...
+#### --------------------------------------------------------------------------
+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):
+        self.obj = obj
+        return self
+    #
+    def __call__ (self, other, *args):
+        "Execute the call behavior."
+        instance = self.obj
+        _dates = instance._dates
+        tslog.debug("_tsmathmethod: series: %s" % instance,)
+        tslog.debug("_tsmathmethod: other  : %s" % other,)
+        func = getattr(instance._series, self.f)
+        if hasattr(instance,"_dates") and hasattr(other,"_dates"):
+            tslog.debug("_tsmathmethod: instance  : %s" % instance,)
+            tslog.debug("_tsmathmethod: other  : %s" % other,)
+            if N.any(instance._dates != other._dates):
+                tslog.debug("_tsmathmethod %s on %s and %s" % \
+                               (self.f, instance, other),)
+                raise TimeSeriesError, "Method %s not yet implemented !" % self.f
+        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__')
+#................................................
+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)  
+#......................................
+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 original 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."
+        func = getattr(self.obj._series, self._name)
+        return func(*args, **params)
+#.......................................
+TimeSeries.astype = _tsarraymethod('astype')
+TimeSeries.reshape = _tsarraymethod('reshape', ondates=True)
+TimeSeries.transpose = _tsarraymethod('transpose', ondates=True)
+TimeSeries.swapaxes = _tsarraymethod('swapaxes', ondates=True)
+TimeSeries.copy = _tsarraymethod('copy', ondates=True)
+TimeSeries.compress = _tsarraymethod('compress', ondates=True)
+#
+TimeSeries.sum = _tsaxismethod('sum')
+TimeSeries.cumsum = _tsaxismethod('cumsum')
+TimeSeries.prod = _tsaxismethod('prod')
+TimeSeries.cumprod = _tsaxismethod('cumprod')
+TimeSeries.mean = _tsaxismethod('mean')
+TimeSeries.var = _tsaxismethod('var')
+TimeSeries.varu = _tsaxismethod('varu')
+TimeSeries.std = _tsaxismethod('std')
+TimeSeries.stdu = _tsaxismethod('stdu')
+
+
+#..............................................................................
+def concatenate(arrays, axis=0):
+    """Concatenates a sequence of time series."""
+    concatenate.__doc__ = N.concatenate.__doc__
+    for a in arrays:
+        if hasattr(a,"_dates"):
+            raise TimeSeriesError, "Not yet implemented for TimeSeries !"
+
+#### ---------------------------------------------------------------------------
+#--- ... Pickling ...
+#### ---------------------------------------------------------------------------
+#FIXME: We're kinda stuck with forcing the mask to have the same shape as the data
+def _tsreconstruct(baseclass, datesclass, baseshape, basetype):
+    """Internal function that builds a new MaskedArray from the information stored
+in a pickle."""
+    _series = ndarray.__new__(ndarray, baseshape, basetype)
+    _dates = ndarray.__new__(datesclass, baseshape, basetype)
+    return TimeSeries.__new__(baseclass, _series, dates=_dates, dtype=basetype)
+
+def _tsgetstate(a):
+    "Returns the internal state of the TimeSeries, for pickling purposes."
+    #TODO: We should prolly go through a recarray here as well.
+    state = (1,
+             a.shape, 
+             a.dtype,
+             a.flags.fnc,
+             (a._series).__reduce__()[-1][-1],
+             (a._dates).__reduce__()[-1][-1])
+    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, isf, raw, dti) = state
+    (a._series).__setstate__((shp, typ, isf, raw))
+    (a._dates).__setstate__((shp, N.dtype('|O8'), isf, dti))
+    (a._dates)._asstrings = None
+        
+def _tsreduce(a):
+    """Returns a 3-tuple for pickling a MaskedArray."""
+    return (_tsreconstruct,
+            (a.__class__, a._dates.__class__, (0,), 'b', ),
+            a.__getstate__())
+
+TimeSeries.__getstate__ = _tsgetstate
+TimeSeries.__setstate__ = _tssetstate
+TimeSeries.__reduce__ = _tsreduce
+TimeSeries.__dump__ = dump
+TimeSeries.__dumps__ = dumps
+
+#................................................
+def tofile(self, output, sep='\t', format='%s', 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
+    oformat = "%%s%s%s" % (sep,format)
+    for (_dates,_data) in N.broadcast(self._dates.ravel().asstrings(), 
+                                      filled(self)):
+        ofile.write('%s\n' % sep.join([oformat % (_dates, _data) ]))
+    ofile.close()
+TimeSeries.tofile = tofile
+
+#### --------------------------------------------------------------------------
+#--- ... Shortcuts ...
+#### --------------------------------------------------------------------------  
+def isTimeSeries(x):
+    """Checks whether `x` is a time series (an instance of `TimeSeries` )."""
+    return isinstance(x, TimeSeries)        
+
+#### --------------------------------------------------------------------------
+#--- ... MaskedTimeSeries class ...
+#### --------------------------------------------------------------------------
+class MaskedTimeSeries(MaskedArray, TimeSeries): 
+    """Base class for the definition of time series.
+A time series is here defined as the combination of two arrays:
+    
+    - an array storing the time information (as a `TimeArray` instance);
+    - an array storing the data (as a `MaskedArray` instance.
+    """
+    def __new__(cls, data, dates=None, mask=nomask, 
+                dtype=None, copy=True, fill_value=-9999):
+        mtslog.log(5, "__new__: data types %s, %s" % (type(data), dtype))
+#        if isinstance(data, TimeSeries):
+        #....................
+        if isinstance(data, TimeSeries):  
+            if isinstance(data, MaskedTimeSeries):
+                _data = data._data
+            else:
+                _data = data
+            _dates = data._dates
+            _series = data._series
+            mtslog.log(5, "__new__ from TS: data %i - %s - %s" % \
+                          (id(_data._series), type(_data._series), _data.ravel()))
+            mtslog.log(5,"__new__ from TS: dates %i - %s - %s" % \
+                          (id(_dates), type(_dates), _dates.ravel()))
+        elif isinstance(data, recarray):
+            assert(data.dtype.names == ('_dates', '_series', '_mask'),
+                   "Invalid fields names (got %s)" % (data.dtype.names,))
+            _dates = data['_dates']
+            _series = data['_series']
+            _mask = data['_mask']
+        else:
+            if hasattr(data, "_data"):
+                _data = TimeSeries(data._data, dates=dates, 
+                                       dtype=dtype, copy=copy)
+            else:
+                _data = TimeSeries(data, dates=dates, 
+                                       dtype=dtype, copy=copy)
+            _dates = _data._dates    
+            _series = _data._series
+            mtslog.log(5,"__new__ from scratch: data %i - %s - %s" % \
+                         (id(_data._series), type(_data._series), _data.ravel()))
+            mtslog.log(5,"__new__ from TS: dates %i - %s - %s" % \
+                         (id(_dates), type(_dates), _dates.ravel()))
+        #.....................
+        if mask is nomask:
+            if hasattr(data, "_mask"):
+                _mask = data._mask
+            else:
+                _mask = nomask
+        else:
+            _mask = make_mask(mask, copy=copy, flag=True)
+        #....Check shapes compatibility
+        if _mask is not nomask:
+            (nd, nm) = (_data.size, _mask.size)
+            if (nm != nd):
+                if nm == 1:
+                    _mask = N.resize(_mask, _data.shape)
+                elif nd == 1:
+                    _data = N.resize(_data, _mask.shape)
+                else:
+                    msg = "Mask and data not compatible (size issues: %i & %i)."
+                    raise MAError, msg % (nm, nd)
+            elif (_mask.shape != _data.shape):
+                mtslog.log(5,"__new__ from scratch: force _mask shape %s > %s" % \
+                             (_mask.shape, _data.shape))
+                _mask.shape = _data.shape
+        #....
+        cls._fill_value = fill_value
+        cls._basemask = _mask
+        cls._basedates = _dates
+        cls._baseseries = _series
+        return _data.view(cls)
+#        return _series.view(cls)
+    #..............
+    def __array_wrap__(self, obj, context=None):
+        """Special hook for ufuncs.
+Wraps the numpy array and sets the mask according to context.
+        """
+#        return MaskedArray.__array_wrap__(obj, context=None)
+        return MaskedTimeSeries(obj, dates=self._dates, mask=self._mask,
+                                fill_value=self._fill_value)
+        
+    #..............
+    def __array_finalize__(self,obj):
+        mtslog.log(5, "__array_finalize__: obj is %s" % (type(obj), ))
+        if not hasattr(self, "_data"):
+            self._data = obj
+        if not hasattr(self, "_dates"):
+            self._dates = self._basedates
+            mtslog.log(5, "__array_finalize__: set dates to: %s - %s" % \
+                          (id(self._dates), self._dates.ravel() ))
+        if not hasattr(self, "_mask"):
+            self._mask = self._basemask
+            mtslog.log(5, "__array_finalize__: set mask to: %s - %s" % \
+                          (id(self._mask), self._mask.ravel() ))
+        if not hasattr(self, "_series"):
+            if hasattr(obj, "_series"):
+                self._series = obj._series
+            else:
+                self._series = obj
+        self.fill_value = self._fill_value
+        return
+
+    #------------------------------------------------------
+#    def __mul__(self):
+    #------------------------------------------------------
+    def __str__(self):
+        """Calculate the str representation, using masked for fill if
+           it is enabled. Otherwise fill with fill value.
+        """
+        if masked_print_option.enabled():
+            f = masked_print_option
+            # XXX: Without the following special case masked
+            # XXX: would print as "[--]", not "--". Can we avoid
+            # XXX: checks for masked by choosing a different value
+            # XXX: for the masked singleton? 2005-01-05 -- sasha
+            if self is masked:
+                return str(f)
+            m = self._mask
+            if m is nomask:
+                res = self._data
+            else:
+                if m.shape == () and m:
+                    return str(f)
+                # convert to object array to make filled work
+                res = (self._series).astype("|O8")
+                res[self._mask] = f
+        else:
+            res = self.filled(self.fill_value)
+        return str(res)
+    
+    def __repr__(self):
+        """Calculate the repr representation, using masked for fill if
+           it is enabled. Otherwise fill with fill value.
+        """
+        desc = """\
+timeseries(data =
+ %(data)s,
+           mask =
+ %(mask)s, 
+           date = 
+ %(time)s, )
+"""
+        desc_short = """\
+timeseries(data = %(data)s,
+           mask = %(mask)s,
+           date = %(time)s,)
+"""
+#        if (self._mask is nomask) and (not self._mask.any()):
+#            if self.ndim <= 1:
+#                return without_mask1 % {'data':str(self.filled()),
+#                                        'time':str(self._dates.asstrings())}
+#            return without_mask % {'data':str(self.filled()),
+#                                   'time':str(self._dates.asstrings())}
+#        else:
+        if self.ndim <= 1:
+            return desc_short % {
+                'data': str(self),
+                'mask': str(self._mask),
+                'time': str(self.dates),
+                }
+        return desc % {
+            'data': str(self),
+            'mask': str(self._mask),
+            'time': str(self.dates),
+            }
+    #............................................
+    def ids (self):
+        """Return the ids of the data, dates and mask areas"""
+        return (id(self._series), id(self.dates), id(self._mask))
+    #............................................
+    @property
+    def maskedseries(self):
+        """Returns a masked array of the series (dates are omitteed)."""
+        return masked_array(self._series, mask=self._mask)
+    _mseries = maskedseries
+    #............................................
+    def filled(self, fill_value=None):
+        """A numeric array with masked values filled. If fill_value is None,
+           use self.fill_value().
+
+           If mask is nomask, copy data only if not contiguous.
+           Result is always a contiguous, numeric array.
+# Is contiguous really necessary now?
+        """
+        (d, m) = (self._data, self._mask)
+        if m is nomask:
+            return d
+        #
+        if fill_value is None:
+            value = self._fill_value
+        else:
+            value = fill_value
+        #
+        if self is masked_singleton:
+            return numeric.array(value)
+        #
+        result = d.copy()
+        try:
+            result.__setitem__(m, value)
+        except (TypeError, AttributeError):
+            #ok, can't put that value in here
+            value = numeric.array(value, dtype=object)
+            d = d.astype(object)
+            result = fromnumeric.choose(m, (d, value))
+        except IndexError:
+            #ok, if scalar
+            if d.shape:
+                raise
+            elif m:
+                result = numeric.array(value, dtype=d.dtype)
+            else:
+                result = d
+        return result
+    #............................................
+    def sum(self, axis=None, dtype=None):
+        """a.sum(axis=None, dtype=None) 
+Sums the array `a` over the given axis `axis`.
+Masked values are set to 0.
+If `axis` is None, applies to a flattened version of the array.
+    """
+        if self._mask is nomask:
+            return self._data.sum(axis, dtype=dtype)
+        else:
+            if axis is None:
+                return self.filled(0).sum(None, dtype=dtype)
+            return MaskedArray(self.filled(0).sum(axis, dtype=dtype),
+                               mask=self._mask.all(axis))
+            
+    def cumsum(self, axis=None, dtype=None):
+        """a.cumprod(axis=None, dtype=None)
+Returns the cumulative sum of the elements of array `a` along the given axis `axis`. 
+Masked values are set to 0.
+If `axis` is None, applies to a flattened version of the array.
+        """
+        if self._mask is nomask:
+            return self._data.cumsum(axis=axis, dtype=dtype)
+        else:
+            if axis is None:
+                return self.filled(0).cumsum(None, dtype=dtype)
+            return MaskedArray(self.filled(0).cumsum(axis=axis, dtype=dtype),
+                               mask=self._mask)
+        
+    def prod(self, axis=None, dtype=None):
+        """a.prod(axis=None, dtype=None)
+Returns the product of the elements of array `a` along the given axis `axis`. 
+Masked elements are set to 1.
+If `axis` is None, applies to a flattened version of the array.
+        """
+        if self._mask is nomask:
+            return self._data.prod(axis=axis, dtype=dtype)
+        else:
+            if axis is None:
+                return self.filled(1).prod(None, dtype=dtype)
+            return MaskedArray(self.filled(1).prod(axis=axis, dtype=dtype),
+                               mask=self._mask.all(axis))
+    product = prod
+            
+    def cumprod(self, axis=None, dtype=None):
+        """a.cumprod(axis=None, dtype=None)
+Returns the cumulative product of ethe lements of array `a` along the given axis `axis`. 
+Masked values are set to 1.
+If `axis` is None, applies to a flattened version of the array.
+        """
+        if self._mask is nomask:
+            return self._data.cumprod(axis=axis, dtype=dtype)
+        else:
+            if axis is None:
+                return self.filled(1).cumprod(None, dtype=dtype)
+            return MaskedArray(self.filled(1).cumprod(axis=axis, dtype=dtype),
+                               mask=self._mask)        
+            
+    def mean(self, axis=None, dtype=None):
+        """mean(a, axis=None, dtype=None)
+Returns the arithmetic mean.
+
+The mean is the sum of the elements divided by the number of elements.
+        """
+        if self._mask is nomask:
+            return self._data.mean(axis=axis, dtype=dtype)
+        else:
+            sum = N.sum(self.filled(0), axis=axis, dtype=dtype)
+            cnt = self.count(axis=axis)
+            if axis is None:
+                if self._mask.all(None):
+                    return masked
+                else:
+                    return sum*1./cnt
+            return MaskedArray(sum*1./cnt, mask=self._mask.all(axis))
+
+    def anom(self, axis=None, dtype=None):
+        """a.anom(axis=None, dtype=None)
+Returns the anomalies, or deviation from the average.
+        """       
+        m = self.mean(axis, dtype)
+        if not axis:
+            return (self - m)
+        else:
+            return (self - N.expand_dims(m,axis))
+ 
+    def var(self, axis=None, dtype=None):
+        """a.var(axis=None, dtype=None)
+Returns the variance, a measure of the spread of a distribution.
+
+The variance is the average of the squared deviations from the mean,
+i.e. var = mean((x - x.mean())**2).
+        """
+        if self._mask is nomask:
+            return MaskedArray(self._data.var(axis=axis, dtype=dtype),
+                               mask=nomask)
+        else:
+            cnt = self.count(axis=axis)
+            anom = self.anom(axis=axis, dtype=dtype)
+            anom *= anom
+            dvar = anom.sum(axis)
+            dvar /= cnt
+            if axis is None:
+                return dvar
+            return MaskedArray(dvar, mask=mask_or(self._mask.all(axis), (cnt==1)))
+            
+    def std(self, axis=None, dtype=None):
+        """a.std(axis=None, dtype=None)
+Returns the standard deviation, a measure of the spread of a distribution.
+
+The standard deviation is the square root of the average of the squared
+deviations from the mean, i.e. std = sqrt(mean((x - x.mean())**2)).
+        """
+        var = self.var(axis,dtype)
+        if axis is None:
+            if var is masked:
+                return masked
+            else:
+                return N.sqrt(var)
+        return MaskedArray(N.sqrt(var._data), mask=var._mask)
+    
+    def varu(self, axis=None, dtype=None):
+        """a.var(axis=None, dtype=None)
+Returns an unbiased estimate of the variance.
+
+Instead of dividing the sum of squared anomalies by n, the number of elements,
+this sum is divided by n-1.
+        """
+        cnt = self.count(axis=axis)
+        anom = self.anom(axis=axis, dtype=dtype)
+        anom *= anom
+        var = anom.sum(axis)
+        var /= (cnt-1)
+        if axis is None:
+            return var
+        return MaskedArray(var, mask=mask_or(self._mask.all(axis), (cnt==1)))
+            
+    def stdu(self, axis=None, dtype=None):
+        """a.var(axis=None, dtype=None)
+Returns an unbiased estimate of the standard deviation.
+        """
+        var = self.varu(axis,dtype)
+        if axis is None:
+            if var is masked:
+                return masked
+            else:
+                return N.sqrt(var)
+        return MaskedArray(N.sqrt(var._data), mask=var._mask)
+    #............................................
+    def asrecords(self):
+        """Returns the masked time series as a recarray.
+Fields are `_dates`, `_data` and _`mask`.
+        """
+        desctype = [('_dates','|O8'), ('_series',self.dtype), ('_mask',N.bool_)]
+        flat = self.ravel()
+        if flat.size > 0:
+            return recfromarrays([flat._dates, flat._series, getmaskarray(flat)],
+                                 dtype=desctype,
+                                 shape = (flat.size,),  
+                                 )
+        else:
+            return recfromarrays([[], [], []], dtype=desctype, 
+                                 shape = (flat.size,),  
+                                 )
+            
+            
+#    def reshape (self, *s):
+#        """This array reshaped to shape s"""
+#        self._data = self._data.reshape(*s)
+#        self._dates = self._dates.reshape(*s)
+#        if self._mask is not nomask:
+#            self._mask = self._mask.reshape(*s)
+#        return self.view()
+#### --------------------------------------------------------------------------
+#--- ... Pickling ...
+#### --------------------------------------------------------------------------
+def _mtsreconstruct(baseclass, datesclass, baseshape, basetype, fill_value):
+    """Internal function that builds a new MaskedArray 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, '|O8')
+    _mask = ndarray.__new__(ndarray, baseshape, '|O8')
+    return baseclass.__new__(baseclass, _series, dates=_dates, mask=_mask, 
+                             dtype=basetype, fill_value=fill_value)
+#    
+def _mtsgetstate(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,
+             records.flags.fnc,
+             a.fill_value,
+             records
+             )
+    return state
+#    
+def _mtssetstate(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, isf, flv, rec) = state
+    a.fill_value = flv
+    a._data._series = a._series = N.asarray(rec['_series'])
+    a._data._series.shape = a._series.shape = shp
+    a._data._dates = a._dates = a._dates.__class__(rec['_dates'])
+    a._data._dates.shape = a._dates.shape = shp
+    (a._dates)._asstrings = None
+    a._mask = N.array(rec['_mask'], dtype=MA.MaskType)
+    a._mask.shape = shp
+#        
+def _mtsreduce(a):
+    """Returns a 3-tuple for pickling a MaskedArray."""
+    return (_mtsreconstruct,
+            (a.__class__, a.dates.__class__, (0,), 'b', -9999),
+            a.__getstate__())
+#    
+MaskedTimeSeries.__getstate__ = _mtsgetstate
+MaskedTimeSeries.__setstate__ = _mtssetstate
+MaskedTimeSeries.__reduce__ = _mtsreduce
+MaskedTimeSeries.__dump__ = dump
+MaskedTimeSeries.__dumps__ = dumps
+
+##### -------------------------------------------------------------------------
+#---- --- TimeSeries creator ---
+##### -------------------------------------------------------------------------
+def time_series(data, dates=None, mask=nomask, copy=False, fill_value=None):
+    """Creates a TimeSeries object
+    
+:Parameters:
+    `dates` : ndarray
+        Array of dates.
+    `data` : 
+        Array of data.
+    """
+    if isinstance(data, MaskedTimeSeries):
+        if not copy:
+            data._mask = mask_or(data._mask, mask)
+            return data
+        _data = data._data
+        _mask = mask_or(data._mask, mask)
+        _dates = _data.dates
+    elif isinstance(data, TimeSeries):
+        _data = data._series
+        _mask = make_mask(mask)
+        _dates = data.dates    
+    else:
+        data = masked_array(data, copy=False)
+        _data = data._data
+        _mask = mask_or(data._mask, mask)
+        if dates is None:
+            _dates = fake_dates(data.size)
+        else:
+            _dates = time_array(dates)
+    return MaskedTimeSeries(_data, dates=_dates, mask=_mask, copy=copy, 
+                            fill_value=fill_value)
+
+
+#
+
+#### --------------------------------------------------------------------------
+#--- ... Additional functions ...
+#### --------------------------------------------------------------------------
+def check_dates(a,b):
+    """Returns the array of dates from the two objects `a` or `b` (or None)."""
+    if isTimeSeries(a):
+        if isTimeSeries(b) and (a._dates == b._dates).all() is False:
+            raise ValueError, "Incompatible dates !"
+        return a._dates
+    elif isTimeSeries(b):
+        return b._dates
+    else:
+        return
+         
+def parse_period(period):
+    """Returns a TimeArray couple (starting date; ending date) from the arguments."""
+####    print "........DEBUG PARSE DATES: period %s is %s" % (period, type(period))
+#    if isinstance(period,TimeArray) or isinstance(period,Dates):
+####        print "........DEBUG PARSE_PERIOD: OK"
+    if isinstance(period,TimeArray):
+        return (period[0],period[-1])
+    elif hasattr(period,"__len__"):
+        if not isinstance(period[0], TimeArray):
+            tstart = TimeArray(period[0])
+        else:
+            tstart = period[0] 
+        if not isinstance(period[-1], TimeArray):
+            tend = TimeArray(period[-1])
+        else:
+            tend = period[-1] 
+        return (tstart, tend)
+    else:
+        p = N.asarray(period)
+        if N.all(p < 9999):
+            p = N.array(period,dtype="|S4")
+        p = time_array(p)
+        return (p[0], p[-1])
+
+def where_period(period, dates, *choices):
+    """Returns choices fro True/False, whether dates fall during a given period.
+If no choices are given, outputs the array indices  for the dates falling in the
+period.
+
+:Parameters:
+    `period` : Sequence
+        Selection period, as a sequence (starting date, ending date).
+    `dates` : TimeArray
+        Array of dates.
+    `choices` : *(optional)*
+        Arrays to select from when the condition is True/False.
+    """
+    (tstart, tend) = parse_period(period)
+    condition = ascondition((dates>=tstart)&(dates<=tend))
+    condition = (dates>=tstart)&(dates<=tend)
+    return N.where(condition, *choices)
+
+def masked_inside_period(data, period, dates=None):
+    """Returns x as an array masked where dates fall inside the selection period,
+as well as where data are initially missing (masked)."""
+    (tstart, tend) = parse_period(period)
+    # Get dates ..................
+    if hasattr(data, "_dates"):
+        dates = data._dates
+    elif dates is None:
+        raise ValueError,"Undefined dates !"
+    else:
+        assert(N.size(dates)==N.size(data), 
+               "Inconsistent data and dates sizes!")
+    # where_period yields True inside the period, when mask should yield False
+    condition = ascondition(N.logical_and((dates>=tstart), (dates<=tend)))
+    cm = filled(condition,True).reshape(data.shape)
+    mask = mask_or(MA.getmaskarray(data), cm, copy=True)
+    if isinstance(data, MaskedTimeSeries):
+        return data.__class__(data._data, dates=dates, mask=mask, copy=True)
+    if isinstance(data, TimeSeries):
+        return MaskedTimeSeries(data, dates=dates, mask=mask, copy=True)
+    else:
+        return masked_array(data, mask=mask, copy=True) 
+
+def masked_outside_period(data, period, dates=None):
+    """Returns x as an array masked where dates fall outside the selection period,
+as well as where data are initially missing (masked)."""
+    (tstart, tend) = parse_period(period)
+    if hasattr(data, "_dates"):
+        dates = data._dates
+    elif dates is None:
+        raise ValueError,"Undefined dates !"
+    else:
+        assert(N.size(dates)==N.size(data), 
+               "Inconsistent data and dates sizes!")
+    #................
+    condition = ascondition(N.logical_or((dates<tstart),(dates>tend)))
+    cm = filled(condition,True).reshape(data.shape)
+    mask = mask_or(MA.getmaskarray(data), cm, copy=True)
+    if isinstance(data, MaskedTimeSeries):
+        return data.__class__(data._data, dates=dates, mask=mask, copy=True)
+    if isinstance(data, TimeSeries):
+        return MaskedTimeSeries(data, dates=dates, mask=mask, copy=True)
+    else:
+        return masked_array(data, mask=mask, copy=True) 
+
+#..............................................................................
+def fill_missing_dates(dates,data,resolution=None,fill_value=None):
+    """Finds and fills the missing dates in a time series, and allocates a 
+default filling value to the data corresponding to the newly added dates.
+    
+:Parameters:
+    `dates` 
+        Initial array of dates.
+    `data`
+        Initial array of data.
+    `resolution` : float *[None]*
+        New date resolutions, in years. For example, a value of 1/365.25 indicates
+        a daily resolution. If *None*, the initial resolution is used instead.
+    `fill_value` : float
+        Default value for missing data.
+    """
+    if not isinstance(dates, TimeArray):
+        print "DEBUG FILL_MISSING_DATES: dates was %s" % type(dates)
+        dates = TimeArray(dates)
+    print "DEBUG FILL_MISSING_DATES: dates is %s" % type(dates)
+    dflat = dates.ravel()
+    n = len(dflat)
+    # Get data ressolution .......
+    if resolution is None:
+        resolution = dflat.resolution
+        if resolution >= 28 and resolution <= 31:
+            resolution = 30
+    else:
+        resolution = int(1./float(resolution))
+    # Find on what to fill .......
+    if resolution == 1:
+        (resol, freq, refdelta) = (DAILY, 1, dtmdelta.relativedelta(days=+1))    
+    elif resolution == 7:
+        (resol, freq, refdelta) = (WEEKLY, 1, dtmdelta.relativedelta(days=+7))
+    elif resolution == 30:
+        (resol, freq, refdelta) = (MONTHLY, 1, dtmdelta.relativedelta(months=+1))
+    elif resolution == 365:
+        (resol, freq, refdelta) = (YEARLY, 1, dtmdelta.relativedelta(years=+1))
+    else:
+        raise ValueError,\
+              "Unable to define a proper date resolution (found %s)." % resolution
+    # ...and now, fill it ! ......
+    (tstart, tend) = dflat.asobjects()[[0,-1]].tolist()
+    gaprule = dtmrule.rrule(resol, interval=freq, dtstart=tstart, until=tend)
+    newdates = dates.__class__(list(gaprule))
+    #.............................
+    # Get the steps between consecutive data. We need relativedelta to deal w/ months
+    delta = N.array([dtmdelta.relativedelta(b,a) 
+                         for (b,a) in N.broadcast(dflat[1:],dflat[:-1])])
+    dOK = N.equal(delta,refdelta)
+    slcid = N.r_[[0,], N.arange(1,n).compress(-dOK), [n,]]
+    oldslc = N.array([slice(i,e) for (i,e) in N.broadcast(slcid[:-1],slcid[1:])])
+    if resolution == 1:
+        addidx = N.cumsum([d.days for d in N.diff(dflat).compress(-dOK)])
+    elif resolution == 30:
+        addidx = N.cumsum([d.years*12+d.months for d in delta.compress(-dOK)])
+    elif resolution == 365:
+        addidx = N.cumsum([d.years for d in delta.compress(-dOK)])
+    addidx -= N.arange(len(addidx))
+    newslc = N.r_[[oldslc[0]], 
+                  [slice(i+d-1,e+d-1) for (i,e,d) in \
+                       N.broadcast(slcid[1:-1],slcid[2:],addidx)] 
+                 ]
+#    misslc = [slice(i,i+d-1) 
+#                       for (i,d) in N.broadcast(slcid[1:-1],addidx)]
+    #.............................
+    # Just a quick check
+    for (osl,nsl) in zip(oldslc,newslc):
+        assert N.equal(dflat[osl],newdates[nsl]).all(),\
+            "Slicing mishap ! Please check %s (old) and %s (new)" % (osl,nsl)
+    #.............................
+    data = MA.asarray(data)
+    oldmask = MA.getmaskarray(data)
+    newdata = N.empty(newdates.size,data.dtype)
+    newmask = N.ones(newdates.size, 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)
+    newdata.fill(fill_value)
+    #....
+    for (new,old) in zip(newslc,oldslc):
+        newdata[new] = data[old]
+        newmask[new] = oldmask[old]
+#    for mis in misslc:
+#        newdata[mis].fill(fill_value)
+    # Get new shape ..............
+    if data.ndim == 1:
+        nshp = (newdates.size,)
+    else:
+        nshp = tuple([-1,] + list(data.shape[1:]))
+    return MaskedTimeSeries(newdata.reshape(nshp),
+                            dates=newdates.reshape(nshp),
+                            mask=newmask.reshape(nshp),
+                            fill_value=fill_value)
+
+######--------------------------------------------------------------------------
+##---- --- Archiving ---
+######--------------------------------------------------------------------------
+#import iodata.iotools as iotools
+#def archive(timeseries,filename,compression=None):
+#    data = timeseries.asrecords()
+#    iotools.archive(data, filename, compression)
+#
+#def unarchive(filename):
+#    raise NotImplementedError
+    
+
+###############################################################################
\ No newline at end of file

Added: trunk/Lib/sandbox/timeseries/mtimeseries/tscore.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/tscore.py	2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/tscore.py	2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,159 @@
+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 ---
+#####---------------------------------------------------------------------------
+#def flatten(listOfLists):
+#    return list(chain(*listOfLists))
+#http://aspn.activestate.com/ASPN/Mail/Message/python-tutor/2302348
+def flatten(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(elm):
+                yield f
+        else:
+            yield elm
+        
+def flatargs(*args):
+    "Flattens the arguments."
+    if not hasattr(args, '__iter__'):
+        return args
+    else:
+        return flatten(args)
+        
+

Added: trunk/Lib/sandbox/timeseries/mtimeseries/tsdate.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/tsdate.py	2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/tsdate.py	2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,1054 @@
+"""
+: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 tscore as corelib
+#reload(corelib)
+from tscore import isDateType
+
+#from corelib import fmtFreq, freqToType
+import mx.DateTime as mxD
+from mx.DateTime.Parser import DateFromString as mxDFromString
+
+
+import logging
+logging.basicConfig(level=logging.DEBUG,
+                    format='%(name)-15s %(levelname)s %(message)s',)
+daflog = logging.getLogger('darray_from')
+dalog = logging.getLogger('DateArray')
+
+#TODO: - it's possible to change the freq on the fly, e.g. mydateD.freq = 'A'
+#TODO:   ...We can prevent that by making freq a readonly property, or call dateOf
+#TODO:   ...or decide it's OK
+#TODO: - Do we still need dateOf, or should we call cseries.convert instead ?
+#TODO: - It'd be really nice if  cseries.convert could accpt an array, 
+#TODO:   ...that way, we could call it instead of looping in asfreq
+    
+secondlyOriginDate = mxD.Date(1980) - mxD.DateTimeDeltaFrom(seconds=1)
+minutelyOriginDate = mxD.Date(1980) - mxD.DateTimeDeltaFrom(minute=1)
+hourlyOriginDate = mxD.Date(1980) - mxD.DateTimeDeltaFrom(hour=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':
+                value -= 1
+                self.mxDate = mxD.DateTimeFromAbsDays(value + (value//5)*7 - (value//5)*5)
+            elif self.freq in ['D','U']:
+                self.mxDate = mxD.DateTimeFromAbsDays(value-1)
+            elif self.freq == 'H':
+                self.mxDate = hourlyOriginDate + mxD.DateTimeDeltaFrom(hours=value)
+            elif self.freq == 'M':
+                self.mxDate = mxD.Date(0) + \
+                              mxD.RelativeDateTime(months=value-1, day=-1)
+            elif self.freq == 'Q':
+                self.mxDate = mxD.Date(0) + \
+                              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(0) + \
+                              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 = 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 = 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 = 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 = int((weeks*5) + (days - weeks*7))
+        # Daily/undefined
+        elif self.freq in ['D', 'U']:
+            val = self.mxDate.absdate
+        # Hourly........
+        elif self.freq == 'H':
+            val = int((self.mxDate - hourlyOriginDate).hours)
+        # Monthly.......
+        elif self.freq == 'M':
+            val = self.mxDate.year*12 + self.mxDate.month
+        # Quarterly.....
+        elif self.freq == 'Q':
+            val = int(self.mxDate.year*4 + self.mxDate.month/3)
+        # Secondly......
+        elif self.freq == 'S':
+            val = int((self.mxDate - secondlyOriginDate).seconds)
+        # Minutely......
+        elif self.freq == 'T':
+            val = int((self.mxDate - minutelyOriginDate).minutes)
+        # Weekly........
+        elif self.freq == 'W':
+            val = int(self.mxDate.year*365.25/7.-1) + self.mxDate.iso_week[1]
+        return 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 in ("H","S","T"):
+            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 dateOf(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 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 dateOf(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):
+        raise DateError, "Date should be a valid Date instance!"
+
+    if date.freq == toFreq:
+        return date
+    # 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 isDateType(index):
+            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(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 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 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:
+            steps = numeric.asarray(numpy.diff(self))
+            if steps.size > 0:
+                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 isDateType(other):
+            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 isDateType(template):
+            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):
+        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:
+        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')
+
+
+################################################################################
+if __name__ == '__main__':
+#    from maskedarray.testutils import assert_equal
+    import datetime
+    if 1:
+        # get the last day of this year, at daily frequency
+        dLastDayOfYear = dateOf(thisday('A'),'D','AFTER')
+    if 0:
+        # get the first day of this year, at business frequency
+        bFirstDayOfYear = dateOf(thisday('A'),'B','BEFORE')
+        # get the last day of the previous quarter, business frequency
+        bLastDayOfLastQuarter = dateOf(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 = (thisday('Q') == dateOf(thisday('b'),'Q'))
+        # dates of the same frequency can be subtracted (but not added obviously)
+        numberOfBusinessDaysPassedThisYear = thisday('b') - bFirstDayOfYear
+    
+        # integers can be added/substracted to/from dates
+        fiveDaysFromNow = thisday('d') + 5
+    
+        # get the previous business day, where business day is considered to
+        # end at day_end_hour and day_end_min
+        pbd = prevbusday(day_end_hour=18,day_end_min=0)
+    
+        # construct a date object explicitly
+        myDateQ = Date(freq='Q',year=2004,quarter=3)
+        myDateD = Date(freq='D',year=1985,month=10,day=4)
+        
+        #------------------------------------------------

Added: trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py
===================================================================
--- trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py	2007-01-02 17:39:21 UTC (rev 2475)
+++ trunk/Lib/sandbox/timeseries/mtimeseries/tseries.py	2007-01-02 17:54:31 UTC (rev 2476)
@@ -0,0 +1,1506 @@
+# 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 tsdate
+reload(tsdate)
+from tsdate import DateError, InsufficientDateError
+from tsdate 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.shape != b.shape:
+        raise TimeSeriesCompatibilityError('size', str(a.shape), 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', dsize, tsize)
+        
+
+##### --------------------------------------------------------------------------
+##--- ... Time Series ...
+##### --------------------------------------------------------------------------
+if oldma:
+    parentclass = ndarray
+else:
+    parentclass = MaskedArray
+#
+class TimeSeries(parentclass, 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:
+                if numeric.ndim(data) >= 2:
+                    length = numeric.asarray(numeric.shape(data))[1:].prod()
+                else:
+                    length = numeric.size(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.transpose = _tsarraymethod('transpose', ondates=True)
+TimeSeries.swapaxes = _tsarraymethod('swapaxes', ondates=True)
+TimeSeries.copy = _tsarraymethod('copy', ondates=True)
+TimeSeries.compress = _tsarraymethod('compress', 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 == 0:
+                    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')
+
+#####---------------------------------------------------------------------------
+#---- --- 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='%s', 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
+
+##### ---------------------------------------------------------------------------
+##--- ... Pickling ...
+##### ---------------------------------------------------------------------------
+##FIXME: We're kinda stuck with forcing the mask to have the same shape as the data
+#def _tsreconstruct(baseclass, datesclass, baseshape, basetype):
+#    """Internal function that builds a new MaskedArray from the information stored
+#in a pickle."""
+#    _series = ndarray.__new__(ndarray, baseshape, basetype)
+#    _dates = ndarray.__new__(datesclass, baseshape, basetype)
+#    return TimeSeries.__new__(baseclass, _series, dates=_dates, dtype=basetype)
+#
+#def _tsgetstate(a):
+#    "Returns the internal state of the TimeSeries, for pickling purposes."
+#    #TODO: We should prolly go through a recarray here as well.
+#    state = (1,
+#             a.shape, 
+#             a.dtype,
+#             a.flags.fnc,
+#             (a._series).__reduce__()[-1][-1],
+#             (a._dates).__reduce__()[-1][-1])
+#    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, isf, raw, dti) = state
+#    (a._series).__setstate__((shp, typ, isf, raw))
+#    (a._dates).__setstate__((shp, N.dtype('|O8'), isf, dti))
+#    (a._dates)._asstrings = None
+#        
+#def _tsreduce(a):
+#    """Returns a 3-tuple for pickling a MaskedArray."""
+#    return (_tsreconstruct,
+#            (a.__class__, a._dates.__class__, (0,), 'b', ),
+#            a.__getstate__())
+#
+#TimeSeries.__getstate__ = _tsgetstate
+#TimeSeries.__setstate__ = _tssetstate
+#TimeSeries.__reduce__ = _tsreduce
+#TimeSeries.__dump__ = dump
+#TimeSeries.__dumps__ = dumps
+#
+##................................................
+
+#
+##### --------------------------------------------------------------------------
+##--- ... Shortcuts ...
+##### --------------------------------------------------------------------------  
+#def isTimeSeries(x):
+#    """Checks whether `x` is a time series (an instance of `TimeSeries` )."""
+#    return isinstance(x, TimeSeries)        
+#
+##### --------------------------------------------------------------------------
+##--- ... MaskedTimeSeries class ...
+##### --------------------------------------------------------------------------
+#class MaskedTimeSeries(MaskedArray, TimeSeries): 
+#    """Base class for the definition of time series.
+#A time series is here defined as the combination of two arrays:
+#    
+#    - an array storing the time information (as a `TimeArray` instance);
+#    - an array storing the data (as a `MaskedArray` instance.
+#    """
+#    def __new__(cls, data, dates=None, mask=nomask, 
+#                dtype=None, copy=True, fill_value=-9999):
+#        mtslog.log(5, "__new__: data types %s, %s" % (type(data), dtype))
+##        if isinstance(data, TimeSeries):
+#        #....................
+#        if isinstance(data, TimeSeries):  
+#            if isinstance(data, MaskedTimeSeries):
+#                _data = data._data
+#            else:
+#                _data = data
+#            _dates = data._dates
+#            _series = data._series
+#            mtslog.log(5, "__new__ from TS: data %i - %s - %s" % \
+#                          (id(_data._series), type(_data._series), _data.ravel()))
+#            mtslog.log(5,"__new__ from TS: dates %i - %s - %s" % \
+#                          (id(_dates), type(_dates), _dates.ravel()))
+#        elif isinstance(data, recarray):
+#            assert(data.dtype.names == ('_dates', '_series', '_mask'),
+#                   "Invalid fields names (got %s)" % (data.dtype.names,))
+#            _dates = data['_dates']
+#            _series = data['_series']
+#            _mask = data['_mask']
+#        else:
+#            if hasattr(data, "_data"):
+#                _data = TimeSeries(data._data, dates=dates, 
+#                                       dtype=dtype, copy=copy)
+#            else:
+#                _data = TimeSeries(data, dates=dates, 
+#                                       dtype=dtype, copy=copy)
+#            _dates = _data._dates    
+#            _series = _data._series
+#            mtslog.log(5,"__new__ from scratch: data %i - %s - %s" % \
+#                         (id(_data._series), type(_data._series), _data.ravel()))
+#            mtslog.log(5,"__new__ from TS: dates %i - %s - %s" % \
+#                         (id(_dates), type(_dates), _dates.ravel()))
+#        #.....................
+#        if mask is nomask:
+#            if hasattr(data, "_mask"):
+#                _mask = data._mask
+#            else:
+#                _mask = nomask
+#        else:
+#            _mask = make_mask(mask, copy=copy, flag=True)
+#        #....Check shapes compatibility
+#        if _mask is not nomask:
+#            (nd, nm) = (_data.size, _mask.size)
+#            if (nm != nd):
+#                if nm == 1:
+#                    _mask = N.resize(_mask, _data.shape)
+#                elif nd == 1:
+#                    _data = N.resize(_data, _mask.shape)
+#                else:
+#                    msg = "Mask and data not compatible (size issues: %i & %i)."
+#                    raise MAError, msg % (nm, nd)
+#            elif (_mask.shape != _data.shape):
+#                mtslog.log(5,"__new__ from scratch: force _mask shape %s > %s" % \
+#                             (_mask.shape, _data.shape))
+#                _mask.shape = _data.shape
+#        #....
+#        cls._fill_value = fill_value
+#        cls._basemask = _mask
+#        cls._basedates = _dates
+#        cls._baseseries = _series
+#        return _data.view(cls)
+##        return _series.view(cls)
+#    #..............
+#    def __array_wrap__(self, obj, context=None):
+#        """Special hook for ufuncs.
+#Wraps the numpy array and sets the mask according to context.
+#        """
+##        return MaskedArray.__array_wrap__(obj, context=None)
+#        return MaskedTimeSeries(obj, dates=self._dates, mask=self._mask,
+#                                fill_value=self._fill_value)
+#        
+#    #..............
+#    def __array_finalize__(self,obj):
+#        mtslog.log(5, "__array_finalize__: obj is %s" % (type(obj), ))
+#        if not hasattr(self, "_data"):
+#            self._data = obj
+#        if not hasattr(self, "_dates"):
+#            self._dates = self._basedates
+#            mtslog.log(5, "__array_finalize__: set dates to: %s - %s" % \
+#                          (id(self._dates), self._dates.ravel() ))
+#        if not hasattr(self, "_mask"):
+#            self._mask = self._basemask
+#            mtslog.log(5, "__array_finalize__: set mask to: %s - %s" % \
+#                          (id(self._mask), self._mask.ravel() ))
+#        if not hasattr(self, "_series"):
+#            if hasattr(obj, "_series"):
+#                self._series = obj._series
+#            else:
+#                self._series = obj
+#        self.fill_value = self._fill_value
+#        return
+#    #------------------------------------------------------
+#    def __str__(self):
+#        """Calculate the str representation, using masked for fill if
+#           it is enabled. Otherwise fill with fill value.
+#        """
+#        if masked_print_option.enabled():
+#            f = masked_print_option
+#            # XXX: Without the following special case masked
+#            # XXX: would print as "[--]", not "--". Can we avoid
+#            # XXX: checks for masked by choosing a different value
+#            # XXX: for the masked singleton? 2005-01-05 -- sasha
+#            if self is masked:
+#                return str(f)
+#            m = self._mask
+#            if m is nomask:
+#                res = self._data
+#            else:
+#                if m.shape == () and m:
+#                    return str(f)
+#                # convert to object array to make filled work
+#                res = (self._series).astype("|O8")
+#                res[self._mask] = f
+#        else:
+#            res = self.filled(self.fill_value)
+#        return str(res)
+#    #............................................
+#    def ids (self):
+#        """Return the ids of the data, dates and mask areas"""
+#        return (id(self._series), id(self.dates), id(self._mask))
+#    #............................................
+#    @property
+#    def maskedseries(self):
+#        """Returns a masked array of the series (dates are omitteed)."""
+#        return masked_array(self._series, mask=self._mask)
+#    _mseries = maskedseries
+#    #............................................
+#    def filled(self, fill_value=None):
+#        """A numeric array with masked values filled. If fill_value is None,
+#           use self.fill_value().
+#
+#           If mask is nomask, copy data only if not contiguous.
+#           Result is always a contiguous, numeric array.
+## Is contiguous really necessary now?
+#        """
+#        (d, m) = (self._data, self._mask)
+#        if m is nomask:
+#            return d
+#        #
+#        if fill_value is None:
+#            value = self._fill_value
+#        else:
+#            value = fill_value
+#        #
+#        if self is masked_singleton:
+#            return numeric.array(value)
+#        #
+#        result = d.copy()
+#        try:
+#            result.__setitem__(m, value)
+#        except (TypeError, AttributeError):
+#            #ok, can't put that value in here
+#            value = numeric.array(value, dtype=object)
+#            d = d.astype(object)
+#            result = fromnumeric.choose(m, (d, value))
+#        except IndexError:
+#            #ok, if scalar
+#            if d.shape:
+#                raise
+#            elif m:
+#                result = numeric.array(value, dtype=d.dtype)
+#            else:
+#                result = d
+#        return result
+#    #............................................
+
+#    #............................................
+#    def asrecords(self):
+#        """Returns the masked time series as a recarray.
+#Fields are `_dates`, `_data` and _`mask`.
+#        """
+#        desctype = [('_dates','|O8'), ('_series',self.dtype), ('_mask',N.bool_)]
+#        flat = self.ravel()
+#        if flat.size > 0:
+#            return recfromarrays([flat._dates, flat._series, getmaskarray(flat)],
+#                                 dtype=desctype,
+#                                 shape = (flat.size,),  
+#                                 )
+#        else:
+#            return recfromarrays([[], [], []], dtype=desctype, 
+#                                 shape = (flat.size,),  
+#                                 )
+#            
+#            
+##    def reshape (self, *s):
+##        """This array reshaped to shape s"""
+##        self._data = self._data.reshape(*s)
+##        self._dates = self._dates.reshape(*s)
+##        if self._mask is not nomask:
+##            self._mask = self._mask.reshape(*s)
+##        return self.view()
+##### --------------------------------------------------------------------------
+##--- ... Pickling ...
+##### --------------------------------------------------------------------------
+#def _mtsreconstruct(baseclass, datesclass, baseshape, basetype, fill_value):
+#    """Internal function that builds a new MaskedArray 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, '|O8')
+#    _mask = ndarray.__new__(ndarray, baseshape, '|O8')
+#    return baseclass.__new__(baseclass, _series, dates=_dates, mask=_mask, 
+#                             dtype=basetype, fill_value=fill_value)
+##    
+#def _mtsgetstate(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,
+#             records.flags.fnc,
+#             a.fill_value,
+#             records
+#             )
+#    return state
+##    
+#def _mtssetstate(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, isf, flv, rec) = state
+#    a.fill_value = flv
+#    a._data._series = a._series = N.asarray(rec['_series'])
+#    a._data._series.shape = a._series.shape = shp
+#    a._data._dates = a._dates = a._dates.__class__(rec['_dates'])
+#    a._data._dates.shape = a._dates.shape = shp
+#    (a._dates)._asstrings = None
+#    a._mask = N.array(rec['_mask'], dtype=MA.MaskType)
+#    a._mask.shape = shp
+##        
+#def _mtsreduce(a):
+#    """Returns a 3-tuple for pickling a MaskedArray."""
+#    return (_mtsreconstruct,
+#            (a.__class__, a.dates.__class__, (0,), 'b', -9999),
+#            a.__getstate__())
+##    
+#MaskedTimeSeries.__getstate__ = _mtsgetstate
+#MaskedTimeSeries.__setstate__ = _mtssetstate
+#MaskedTimeSeries.__reduce__ = _mtsreduce
+#MaskedTimeSeries.__dump__ = dump
+#MaskedTimeSeries.__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:
+        if numeric.ndim(data) >= 2:
+            length = numeric.asarray(numeric.shape(data))[1:].prod()
+        else:
+            length = numeric.size(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 check_dates(a,b):
+#    """Returns the array of dates from the two objects `a` or `b` (or None)."""
+#    if isTimeSeries(a):
+#        if isTimeSeries(b) and (a._dates == b._dates).all() is False:
+#            raise ValueError, "Incompatible dates !"
+#        return a._dates
+#    elif isTimeSeries(b):
+#        return b._dates
+#    else:
+#        return
+#         
+#def parse_period(period):
+#    """Returns a TimeArray couple (starting date; ending date) from the arguments."""
+#####    print "........DEBUG PARSE DATES: period %s is %s" % (period, type(period))
+##    if isinstance(period,TimeArray) or isinstance(period,Dates):
+#####        print "........DEBUG PARSE_PERIOD: OK"
+#    if isinstance(period,TimeArray):
+#        return (period[0],period[-1])
+#    elif hasattr(period,"__len__"):
+#        if not isinstance(period[0], TimeArray):
+#            tstart = TimeArray(period[0])
+#        else:
+#            tstart = period[0] 
+#        if not isinstance(period[-1], TimeArray):
+#            tend = TimeArray(period[-1])
+#        else:
+#            tend = period[-1] 
+#        return (tstart, tend)
+#    else:
+#        p = N.asarray(period)
+#        if N.all(p < 9999):
+#            p = N.array(period,dtype="|S4")
+#        p = time_array(p)
+#        return (p[0], p[-1])
+#
+#def where_period(period, dates, *choices):
+#    """Returns choices fro True/False, whether dates fall during a given period.
+#If no choices are given, outputs the array indices  for the dates falling in the
+#period.
+#
+#:Parameters:
+#    `period` : Sequence
+#        Selection period, as a sequence (starting date, ending date).
+#    `dates` : TimeArray
+#        Array of dates.
+#    `choices` : *(optional)*
+#        Arrays to select from when the condition is True/False.
+#    """
+#    (tstart, tend) = parse_period(period)
+#    condition = ascondition((dates>=tstart)&(dates<=tend))
+#    condition = (dates>=tstart)&(dates<=tend)
+#    return N.where(condition, *choices)
+#
+#def masked_inside_period(data, period, dates=None):
+#    """Returns x as an array masked where dates fall inside the selection period,
+#as well as where data are initially missing (masked)."""
+#    (tstart, tend) = parse_period(period)
+#    # Get dates ..................
+#    if hasattr(data, "_dates"):
+#        dates = data._dates
+#    elif dates is None:
+#        raise ValueError,"Undefined dates !"
+#    else:
+#        assert(N.size(dates)==N.size(data), 
+#               "Inconsistent data and dates sizes!")
+#    # where_period yields True inside the period, when mask should yield False
+#    condition = ascondition(N.logical_and((dates>=tstart), (dates<=tend)))
+#    cm = filled(condition,True).reshape(data.shape)
+#    mask = mask_or(MA.getmaskarray(data), cm, copy=True)
+#    if isinstance(data, MaskedTimeSeries):
+#        return data.__class__(data._data, dates=dates, mask=mask, copy=True)
+#    if isinstance(data, TimeSeries):
+#        return MaskedTimeSeries(data, dates=dates, mask=mask, copy=True)
+#    else:
+#        return masked_array(data, mask=mask, copy=True) 
+#
+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."
+    # 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    
+    if start_lag >= 0:
+        if end_lag == 0:
+            return a[start_lag:]
+        elif end_lag < 0:
+            return a[start_lag:end_lag]
+
+    newdates = date_array(start_date=start_date, end_date=end_date)
+    newshape = list(a.shape)
+    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']
+        
+    tempData = masked_array(_values, mask=_mask)
+
+    if tempData.ndim == 2:
+#        tempData = tempData.T
+        if func is not None:
+            tempData = MA.apply_along_axis(func, 1, tempData)
+        else:
+            tempData = tempData.T
+            
+    #startIndex = cseries.convert(start_date, fromFreq, toFreq)
+    newStart = series._dates[0].asfreq(toFreq, "BEFORE")
+    newEnd = series._dates[-1].asfreq(toFreq, "AFTER")
+    
+    newseries = TimeSeries(tempData, freq=toFreq, 
+                           observed=series.observed, 
+                           start_date=newStart)
+    return newseries
+#    return adjust_endpoints(newseries, end_date=newEnd)
+#    return (tempData, newStart, toFreq, newseries, _values, _mask)
+
+
+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
+        data = data._series
+        freq = dates.freq
+    elif not isinstance(dates, DateArray):
+        dates = DateArray(dates, freq)
+    dflat = dates.ravel().asfreq(freq)
+    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. We need relativedelta to deal w/ months
+    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
+    for (osl,nsl) in zip(oldslc,newslc):
+        assert numpy.equal(dflat[osl],newdates[nsl]).all(),\
+            "Slicing mishap ! Please check %s (old) and %s (new)" % (osl,nsl)
+    #.............................
+    data = MA.asarray(data)
+    newdata = MA.masked_array(numeric.empty(nsize, data.dtype),
+                              mask=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)
+    #....
+    for (new,old) in zip(newslc,oldslc):
+        newdata[new] = data[old]
+    # 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)
+
+
+#######--------------------------------------------------------------------------
+###---- --- Archiving ---
+#######--------------------------------------------------------------------------
+
+
+
+
+
+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:
+        mser3 = time_series(MA.mr_[malg1._series, malg2._series].reshape(2,-1), 
+                            dates=malg1.dates)
+ 
+




More information about the Scipy-svn mailing list