255 lines
6.0 KiB
Python
255 lines
6.0 KiB
Python
"""
|
|
Terms Routines
|
|
|
|
This module contains functions for manipulating terms, such as determining
|
|
the current term, finding the next or previous term, converting dates to
|
|
terms, and more.
|
|
"""
|
|
import time, datetime, re
|
|
|
|
# year to count terms from
|
|
EPOCH = 1970
|
|
|
|
# seasons list
|
|
SEASONS = [ 'w', 's', 'f' ]
|
|
|
|
|
|
def validate(term):
|
|
"""
|
|
Determines whether a term is well-formed.
|
|
|
|
Parameters:
|
|
term - the term string
|
|
|
|
Returns: whether the term is valid (boolean)
|
|
|
|
Example: validate("f2006") -> True
|
|
"""
|
|
|
|
regex = '^[wsf][0-9]{4}$'
|
|
return re.match(regex, term) is not None
|
|
|
|
|
|
def parse(term):
|
|
"""Helper function to convert a term string to the number of terms
|
|
since the epoch. Such numbers are intended for internal use only."""
|
|
|
|
if not validate(term):
|
|
raise Exception("malformed term: %s" % term)
|
|
|
|
year = int( term[1:] )
|
|
season = SEASONS.index( term[0] )
|
|
|
|
return (year - EPOCH) * len(SEASONS) + season
|
|
|
|
|
|
def generate(term):
|
|
"""Helper function to convert a year and season to a term string."""
|
|
|
|
year = int(term / len(SEASONS)) + EPOCH
|
|
season = term % len(SEASONS)
|
|
|
|
return "%s%04d" % ( SEASONS[season], year )
|
|
|
|
|
|
def next(term):
|
|
"""
|
|
Returns the next term. (convenience function)
|
|
|
|
Parameters:
|
|
term - the term string
|
|
|
|
Retuns: the term string of the following term
|
|
|
|
Example: next("f2006") -> "w2007"
|
|
"""
|
|
|
|
return add(term, 1)
|
|
|
|
|
|
def previous(term):
|
|
"""
|
|
Returns the previous term. (convenience function)
|
|
|
|
Parameters:
|
|
term - the term string
|
|
|
|
Returns: the term string of the preceding term
|
|
|
|
Example: previous("f2006") -> "s2006"
|
|
"""
|
|
|
|
return add(term, -1)
|
|
|
|
|
|
def add(term, offset):
|
|
"""
|
|
Calculates a term relative to some base term.
|
|
|
|
Parameters:
|
|
term - the base term
|
|
offset - the number of terms since term (may be negative)
|
|
|
|
Returns: the term that comes offset terms after term
|
|
"""
|
|
|
|
return generate(parse(term) + offset)
|
|
|
|
|
|
def delta(initial, final):
|
|
"""
|
|
Calculates the distance between two terms.
|
|
It should be true that add(a, delta(a, b)) == b.
|
|
|
|
Parameters:
|
|
initial - the base term
|
|
final - the term at some offset from the base term
|
|
|
|
Returns: the offset of final relative to initial
|
|
"""
|
|
|
|
return parse(final) - parse(initial)
|
|
|
|
|
|
def compare(first, second):
|
|
"""
|
|
Compares two terms. This function is suitable
|
|
for use with list.sort().
|
|
|
|
Parameters:
|
|
first - base term for comparison
|
|
second - term to compare to
|
|
|
|
Returns: > 0 (if first > second)
|
|
= 0 (if first == second)
|
|
< 0 (if first < second)
|
|
"""
|
|
return delta(second, first)
|
|
|
|
|
|
def interval(base, count):
|
|
"""
|
|
Returns a list of adjacent terms.
|
|
|
|
Parameters:
|
|
base - the first term in the interval
|
|
count - the number of terms to include
|
|
|
|
Returns: a list of count terms starting with initial
|
|
|
|
Example: interval('f2006', 3) -> [ 'f2006', 'w2007', 's2007' ]
|
|
"""
|
|
|
|
terms = []
|
|
|
|
for num in xrange(count):
|
|
terms.append( add(base, num) )
|
|
|
|
return terms
|
|
|
|
|
|
def tstamp(timestamp):
|
|
"""Helper to convert seconds since the epoch
|
|
to terms since the epoch."""
|
|
|
|
# let python determine the month and year
|
|
date = datetime.date.fromtimestamp(timestamp)
|
|
|
|
# determine season
|
|
if date.month <= 4:
|
|
season = SEASONS.index('w')
|
|
elif date.month <= 8:
|
|
season = SEASONS.index('s')
|
|
else:
|
|
season = SEASONS.index('f')
|
|
|
|
return (date.year - EPOCH) * len(SEASONS) + season
|
|
|
|
|
|
def from_timestamp(timestamp):
|
|
"""
|
|
Converts a number of seconds since
|
|
the epoch to a number of terms since
|
|
the epoch.
|
|
|
|
This function notes that:
|
|
WINTER = JANUARY to APRIL
|
|
SPRING = MAY to AUGUST
|
|
FALL = SEPTEMBER to DECEMBER
|
|
|
|
Parameters:
|
|
timestamp - number of seconds since the epoch
|
|
|
|
Returns: the number of terms since the epoch
|
|
|
|
Example: from_timestamp(1166135779) -> 'f2006'
|
|
"""
|
|
|
|
return generate( tstamp(timestamp) )
|
|
|
|
|
|
def curr():
|
|
"""Helper to determine the current term."""
|
|
|
|
return tstamp( time.time() )
|
|
|
|
|
|
def current():
|
|
"""
|
|
Determines the current term.
|
|
|
|
Returns: current term
|
|
|
|
Example: current() -> 'f2006'
|
|
"""
|
|
|
|
return generate( curr() )
|
|
|
|
|
|
def next_unregistered(registered):
|
|
"""
|
|
Find the first future or current unregistered term.
|
|
Intended as the 'default' for registrations.
|
|
|
|
Parameters:
|
|
registered - a list of terms a member is registered for
|
|
|
|
Returns: the next unregistered term
|
|
"""
|
|
|
|
# get current term number
|
|
now = curr()
|
|
|
|
# never registered -> current term is next
|
|
if len( registered) < 1:
|
|
return generate( now )
|
|
|
|
# return the first unregistered, or the current term (whichever is greater)
|
|
return generate(max([max(map(parse, registered))+1, now]))
|
|
|
|
|
|
|
|
### Tests ###
|
|
|
|
if __name__ == '__main__':
|
|
|
|
from ceo.test import test, assert_equal, success
|
|
|
|
test(parse); assert_equal(110, parse('f2006')); success()
|
|
test(generate); assert_equal('f2006', generate(110)); success()
|
|
test(next); assert_equal('w2007', next('f2006')); success()
|
|
test(previous); assert_equal('s2006', previous('f2006')); success()
|
|
test(delta); assert_equal(1, delta('f2006', 'w2007')); success()
|
|
test(compare); assert_equal(-1, compare('f2006', 'w2007')); success()
|
|
test(add); assert_equal('w2010', add('f2006', delta('f2006', 'w2010'))); success()
|
|
test(interval); assert_equal(['f2006', 'w2007', 's2007'], interval('f2006', 3)); success()
|
|
test(from_timestamp); assert_equal('f2006', from_timestamp(1166135779)); success()
|
|
test(current); assert_equal(True, parse( current() ) >= 110 ); success()
|
|
|
|
test(next_unregistered)
|
|
assert_equal( next(current()), next_unregistered([ current() ]))
|
|
assert_equal( current(), next_unregistered([]))
|
|
assert_equal( current(), next_unregistered([ previous(current()) ]))
|
|
assert_equal( current(), next_unregistered([ add(current(), -2) ]))
|
|
success()
|