Moved files into their new locations prior to commit of 0.2.
[public/pyceo-broken.git] / pylib / csc / adm / terms.py
1 # $Id: terms.py 44 2006-12-31 07:09:27Z mspang $
2 """
3 Terms Routines
4
5 This module contains functions for manipulating
6 terms, such as determining the current term,
7 finding the next or previous term, converting
8 dates to terms, and more.
9 """
10 import time, datetime, re
11
12 # year to count terms from
13 EPOCH = 1970
14
15 # seasons list
16 SEASONS = [ 'w', 's', 'f' ]
17
18
19 def valid(term):
20     """
21     Determines whether a term is well-formed:
22
23     Parameters:
24         term - the term string
25
26     Returns: whether the term is valid (boolean)
27
28     Example: valid("f2006") -> True
29     """
30
31     regex = '^[wsf][0-9]{4}$'
32     return re.match(regex, term) != None
33
34
35 def parse(term):
36     """Helper function to convert a term string to the number of terms
37        since the epoch. Such numbers are intended for internal use only."""
38
39     if not valid(term):
40         raise Exception("malformed term: %s" % term)
41
42     year = int( term[1:] )
43     season = SEASONS.index( term[0] )
44
45     return (year - EPOCH) * len(SEASONS) + season
46
47
48 def generate(term):
49     """Helper function to convert a year and season to a term string."""
50     
51     year = int(term / len(SEASONS)) + EPOCH
52     season = term % len(SEASONS)
53     
54     return "%s%04d" % ( SEASONS[season], year )
55
56
57 def next(term):
58     """
59     Returns the next term. (convenience function)
60
61     Parameters:
62         term - the term string
63
64     Retuns: the term string of the following term
65
66     Example: next("f2006") -> "w2007"
67     """
68     
69     return add(term, 1)
70
71
72 def previous(term):
73     """
74     Returns the previous term. (convenience function)
75
76     Parameters:
77         term - the term string
78
79     Returns: the term string of the preceding term
80
81     Example: previous("f2006") -> "s2006"
82     """
83
84     return add(term, -1)
85
86
87 def add(term, offset):
88     """
89     Calculates a term relative to some base term.
90     
91     Parameters:
92         term   - the base term
93         offset - the number of terms since term (may be negative)
94
95     Returns: the term that comes offset terms after term
96     """
97
98     return generate(parse(term) + offset)
99
100
101 def delta(initial, final):
102     """
103     Calculates the distance between two terms.
104     It should be true that add(a, delta(a, b)) == b.
105
106     Parameters:
107         initial - the base term
108         final   - the term at some offset from the base term
109
110     Returns: the offset of final relative to initial
111     """
112
113     return parse(final) - parse(initial)
114
115
116 def compare(first, second):
117     """
118     Compares two terms. This function is suitable
119     for use with list.sort().
120
121     Parameters:
122         first  - base term for comparison
123         second - term to compare to
124
125     Returns: > 0 (if first >  second)
126              = 0 (if first == second)
127              < 0 (if first <  second)
128     """
129     return delta(second, first)
130              
131
132 def interval(base, count):
133     """
134     Returns a list of adjacent terms.
135
136     Parameters:
137         base    - the first term in the interval
138         count   - the number of terms to include
139
140     Returns: a list of count terms starting with initial
141
142     Example: interval('f2006', 3) -> [ 'f2006', 'w2007', 's2007' ]
143     """
144     
145     terms = []
146
147     for num in xrange(count):
148         terms.append( add(base, num) )
149     
150     return terms
151         
152
153 def tstamp(timestamp):
154     """Helper to convert seconds since the epoch
155     to terms since the epoch."""
156
157     # let python determine the month and year
158     date = datetime.date.fromtimestamp(timestamp)
159
160     # determine season
161     if date.month <= 4:
162         season = SEASONS.index('w')
163     elif date.month <= 8:
164         season = SEASONS.index('s')
165     else:
166         season = SEASONS.index('f')
167
168     return (date.year - EPOCH) * len(SEASONS) + season
169
170
171 def from_timestamp(timestamp):
172     """
173     Converts a number of seconds since
174     the epoch to a number of terms since
175     the epoch.
176
177     This function notes that:
178         WINTER = JANUARY to APRIL
179         SPRING = MAY TO AUGUST
180         FALL   = SEPTEMBER TO DECEMBER
181     
182     Parameters:
183         timestamp - number of seconds since the epoch
184
185     Returns: the number of terms since the epoch
186
187     Example: from_timestamp(1166135779) -> 'f2006'
188     """
189
190     return generate( tstamp(timestamp) )
191     
192
193 def curr():
194     """Helper to determine the current term."""
195
196     return tstamp( time.time() )
197
198
199 def current():
200     """
201     Determines the current term.
202
203     Returns: current term
204
205     Example: current() -> 'f2006'
206     """
207
208     return generate( curr() )
209     
210
211 def next_unregistered(registered):
212     """
213     Find the first future or current unregistered term.
214     Intended as the 'default' for registrations.
215
216     Parameters:
217         registered - a list of terms a member is registered for
218
219     Returns: the next unregistered term
220     """
221     
222     # get current term number
223     now = curr()
224
225     # never registered -> current term is next
226     if len( registered) < 1:
227         return generate( now )
228
229     # return the first unregistered, or the current term (whichever is greater)
230     return generate(max([max(map(parse, registered))+1, now]))
231
232
233
234 ### Tests ###
235
236 if __name__ == '__main__':
237
238     assert parse('f2006') == 110
239     assert generate(110) == 'f2006'
240     assert next('f2006') == 'w2007'
241     assert previous('f2006') == 's2006'
242     assert delta('f2006', 'w2007') == 1
243     assert add('f2006', delta('f2006', 'w2010')) == 'w2010'
244     assert interval('f2006', 3) == ['f2006', 'w2007', 's2007']
245     assert from_timestamp(1166135779) == 'f2006'
246     assert parse( current() ) >= 110
247     assert next_unregistered( [current()] ) == next( current() )
248     assert next_unregistered( [] ) == current()
249     assert next_unregistered( [previous(current())] ) == current()
250     assert next_unregistered( [add(current(), -2)] ) == current()
251
252     print "All tests passed." "\n"