source: cows_wps/trunk/cows_wps/utils/duration_splitter.py @ 6873

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows_wps/trunk/cows_wps/utils/duration_splitter.py@6873
Revision 6873, 5.0 KB checked in by astephen, 10 years ago (diff)

Added and updated various code to match security, download URLs that are
secured and chunking of MIDAS outputs.

Line 
1"""
2duration_splitter.py
3====================
4
5Holds class DurationSplitter used to chop up time series into chunks of
6decades, years or months.
7"""
8
9import re
10
11
12class SimpleDate(object):
13
14    def __init__(self, y, m, d):
15        self.y = int(y)
16        self.m = int(m)
17        self.d = int(d)
18        self.date = "%4d%02d%02d" % (self.y, self.m, self.d)   
19
20    def __repr__(self):
21        return self.date   
22
23    def __lt__(self, d):
24        if self.date < d.date:
25            return True
26 
27        return False
28
29
30class DurationSplitter(object):
31    """
32    Splits into sensible time chunks based on inputs.
33    """
34    known_chunk_units = ["decade", "year", "month"]
35
36    def __init__(self, chunk_unit = "decade"):
37        """
38        Allows the setting of a persistent chunk_unit.
39        """
40        self._checkChunkUnit(chunk_unit)
41        self.chunk_unit = chunk_unit
42
43    def _convertDate(self, date):
44        """
45        Converts and returns date to a SimpleDate instance. If format is bad it raises an exception.
46        """
47        if type(date) == type("string"):
48            if len(date) != 8 or not re.match("^\d{8}$", date):
49                raise Exception("Invalid date: %s" % str(date))
50
51            return SimpleDate(date[:4], date[4:6], date[6:8])
52
53        elif type(date) in (type((1,2)), type([1,2])):
54            if len(date) != 3:
55                raise Exception("Invalid date: %s" % str(date))
56
57            for i in date:
58                if type(i) != type(1):
59                    raise Exception("Invalid date: %s" % str(date))
60           
61            return SimpleDate(date[0], date[1], date[2])
62
63        else:
64            raise Exception("Invalid date: %s" % str(date))     
65
66
67    def _checkChunkUnit(self, chunk_unit):
68        if chunk_unit not in self.known_chunk_units:
69            raise Exception("Invalid chunk unit '%s' not in list of %s." % (chunk_unit, str(self.known_chunk_units)))
70
71
72    def splitDuration(self, start_date, end_date, chunk_unit = None):
73        """
74        Splits duration into  a list of n lists of [start, end] where each is represented as a SimpleDate
75        instance with attributes of t.year, t.month, t.day. The list is returned.
76
77        All time splits are done in logical places, e.g.:
78         * decade -> at start of each year ending in 0
79         * year -> 1st of jan each year to 31st dec
80         * month -> first to last day of month
81
82        All input dates times are represented as one of the following:
83         * string: "YYYYMMDD"
84         * tuple: (y, m, d)
85        """
86        start = self._convertDate(start_date)
87        end = self._convertDate(end_date)
88
89        if chunk_unit != None:
90            self._checkChunkUnit(chunk_unit)
91        else:
92            chunk_unit = self.chunk_unit
93
94        # Assign a variable to record chunks in
95        chunks = []
96        ct = start
97
98        this_chunk = [ct]
99
100        while ct < end:
101            ct = self._addDay(ct)
102
103            if (self._isLastDayOfMonth(ct) and chunk_unit == "month") or \
104                   (self._isLastDayOfYear(ct) and chunk_unit == "year") or \
105                   (self._isLastDayOfDecade(ct) and chunk_unit == "decade"):
106
107                this_chunk.append(ct)
108                chunks.append(this_chunk[:])
109               
110                next_day = self._addDay(ct)
111                this_chunk = [next_day]
112
113        # Now add end onto last
114        this_chunk.append(ct)
115        chunks.append(this_chunk[:])
116        return chunks
117
118    def _isLastDayOfMonth(self, date):
119        "Returns True or False."
120        ndays = self._daysInMonth(date.y, date.m)
121        if date.d == ndays:
122            return True
123
124        return False
125
126    def _isLastDayOfYear(self, date):
127        "Returns True or False."
128        if date.m == 12 and date.d == 31:
129            return True
130
131        return False
132         
133    def _isLastDayOfDecade(self, date):
134        """
135        Returns True or False.
136        We define decades as 200001010000 - 200912312359
137        """
138        if self._isLastDayOfYear(date) == True and date.y % 10 == 9:
139            return True
140
141        return False
142
143    def _addDay(self, date):
144        """
145        Returns one day added to this date.
146        """
147        (y, m, d) = (date.y, date.m, date.d)
148
149        if self._isLastDayOfYear(date) == True:
150            y += 1
151            m = 1
152            d = 1
153        elif self._isLastDayOfMonth(date) == True:
154            m += 1
155            d = 1
156        else:
157            d += 1
158
159        return SimpleDate(y, m, d)   
160
161    def _daysInMonth(self, y, m):
162        if y % 4 == 0 and m == 2:
163            return 29
164        else:
165            return int("dummy 31 28 31 30 31 30 31 31 30 31 30 31".split()[m])
166
167               
168
169if __name__ == "__main__":
170
171    ds = DurationSplitter()
172    split_tests = [((1983, 2, 22), (2011, 12, 29)),
173                   ("19590101", "19740101")]
174
175    for chunk_size in ds.known_chunk_units:
176        for (s, e) in split_tests:
177            print "Testing: %s, %s - %s" % (chunk_size, s, e)
178            x = ds.splitDuration(s, e, chunk_size)
179            print "\n".join(["%s - %s" % (i[0].date, i[1].date) for i in x])
Note: See TracBrowser for help on using the repository browser.