4 This module contains functions for manipulating terms, such as determining
5 the current term, finding the next or previous term, converting dates to
8 import time, datetime, re
10 # year to count terms from
14 SEASONS = [ 'w', 's', 'f' ]
19 Determines whether a term is well-formed.
22 term - the term string
24 Returns: whether the term is valid (boolean)
26 Example: validate("f2006") -> True
29 regex = '^[wsf][0-9]{4}$'
30 return re.match(regex, term) is not None
34 """Helper function to convert a term string to the number of terms
35 since the epoch. Such numbers are intended for internal use only."""
37 if not validate(term):
38 raise Exception("malformed term: %s" % term)
40 year = int( term[1:] )
41 season = SEASONS.index( term[0] )
43 return (year - EPOCH) * len(SEASONS) + season
47 """Helper function to convert a year and season to a term string."""
49 year = int(term / len(SEASONS)) + EPOCH
50 season = term % len(SEASONS)
52 return "%s%04d" % ( SEASONS[season], year )
57 Returns the next term. (convenience function)
60 term - the term string
62 Retuns: the term string of the following term
64 Example: next("f2006") -> "w2007"
72 Returns the previous term. (convenience function)
75 term - the term string
77 Returns: the term string of the preceding term
79 Example: previous("f2006") -> "s2006"
85 def add(term, offset):
87 Calculates a term relative to some base term.
91 offset - the number of terms since term (may be negative)
93 Returns: the term that comes offset terms after term
96 return generate(parse(term) + offset)
99 def delta(initial, final):
101 Calculates the distance between two terms.
102 It should be true that add(a, delta(a, b)) == b.
105 initial - the base term
106 final - the term at some offset from the base term
108 Returns: the offset of final relative to initial
111 return parse(final) - parse(initial)
114 def compare(first, second):
116 Compares two terms. This function is suitable
117 for use with list.sort().
120 first - base term for comparison
121 second - term to compare to
123 Returns: > 0 (if first > second)
124 = 0 (if first == second)
125 < 0 (if first < second)
127 return delta(second, first)
130 def interval(base, count):
132 Returns a list of adjacent terms.
135 base - the first term in the interval
136 count - the number of terms to include
138 Returns: a list of count terms starting with initial
140 Example: interval('f2006', 3) -> [ 'f2006', 'w2007', 's2007' ]
145 for num in xrange(count):
146 terms.append( add(base, num) )
151 def tstamp(timestamp):
152 """Helper to convert seconds since the epoch
153 to terms since the epoch."""
155 # let python determine the month and year
156 date = datetime.date.fromtimestamp(timestamp)
160 season = SEASONS.index('w')
161 elif date.month <= 8:
162 season = SEASONS.index('s')
164 season = SEASONS.index('f')
166 return (date.year - EPOCH) * len(SEASONS) + season
169 def from_timestamp(timestamp):
171 Converts a number of seconds since
172 the epoch to a number of terms since
175 This function notes that:
176 WINTER = JANUARY to APRIL
177 SPRING = MAY to AUGUST
178 FALL = SEPTEMBER to DECEMBER
181 timestamp - number of seconds since the epoch
183 Returns: the number of terms since the epoch
185 Example: from_timestamp(1166135779) -> 'f2006'
188 return generate( tstamp(timestamp) )
192 """Helper to determine the current term."""
194 return tstamp( time.time() )
199 Determines the current term.
201 Returns: current term
203 Example: current() -> 'f2006'
206 return generate( curr() )
209 def next_unregistered(registered):
211 Find the first future or current unregistered term.
212 Intended as the 'default' for registrations.
215 registered - a list of terms a member is registered for
217 Returns: the next unregistered term
220 # get current term number
223 # never registered -> current term is next
224 if len( registered) < 1:
225 return generate( now )
227 # return the first unregistered, or the current term (whichever is greater)
228 return generate(max([max(map(parse, registered))+1, now]))
234 if __name__ == '__main__':
236 from csc.common.test import *
238 test(parse); assert_equal(110, parse('f2006')); success()
239 test(generate); assert_equal('f2006', generate(110)); success()
240 test(next); assert_equal('w2007', next('f2006')); success()
241 test(previous); assert_equal('s2006', previous('f2006')); success()
242 test(delta); assert_equal(1, delta('f2006', 'w2007')); success()
243 test(compare); assert_equal(-1, compare('f2006', 'w2007')); success()
244 test(add); assert_equal('w2010', add('f2006', delta('f2006', 'w2010'))); success()
245 test(interval); assert_equal(['f2006', 'w2007', 's2007'], interval('f2006', 3)); success()
246 test(from_timestamp); assert_equal('f2006', from_timestamp(1166135779)); success()
247 test(current); assert_equal(True, parse( current() ) >= 110 ); success()
249 test(next_unregistered)
250 assert_equal( next(current()), next_unregistered([ current() ]))
251 assert_equal( current(), next_unregistered([]))
252 assert_equal( current(), next_unregistered([ previous(current()) ]))
253 assert_equal( current(), next_unregistered([ add(current(), -2) ]))