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@7575
Revision 6873, 5.0 KB checked in by astephen, 9 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.