# module calendar ############################## # Calendar support functions # ############################## # This is based on UNIX ctime() et al. (also Standard C and POSIX) # Subtle but crucial differences: # - the order of the elements of a 'struct tm' differs, to ease sorting # - months numbers are 1-12, not 0-11; month arrays have a dummy element 0 # - Monday is the first day of the week (numbered 0) # These are really parameters of the 'time' module: epoch = 1970 # Time began on January 1 of this year (00:00:00 UCT) day_0 = 3 # The epoch begins on a Thursday (Monday = 0) # Return 1 for leap years, 0 for non-leap years def isleap(year): return year % 4 == 0 and (year % 100 <> 0 or year % 400 == 0) # Constants for months referenced later January = 1 February = 2 # Number of days per month (except for February in leap years) mdays = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) # Exception raised for bad input (with string parameter for details) error = 'calendar error' # Turn seconds since epoch into calendar time def gmtime(secs): if secs < 0: raise error, 'negative input to gmtime()' mins, secs = divmod(secs, 60) hours, mins = divmod(mins, 60) days, hours = divmod(hours, 24) wday = (days + day_0) % 7 year = epoch # XXX Most of the following loop can be replaced by one division while 1: yd = 365 + isleap(year) if days < yd: break days = days - yd year = year + 1 yday = days month = January while 1: md = mdays[month] + (month == February and isleap(year)) if days < md: break days = days - md month = month + 1 return year, month, days + 1, hours, mins, secs, yday, wday # XXX Week number also? # Return number of leap years in range [y1, y2) # Assume y1 <= y2 and no funny (non-leap century) years def leapdays(y1, y2): return (y2+3)/4 - (y1+3)/4 # Inverse of gmtime(): # Turn UCT calendar time (less yday, wday) into seconds since epoch def mktime(year, month, day, hours, mins, secs): days = day - 1 for m in range(January, month): days = days + mdays[m] if isleap(year) and month > February: days = days+1 days = days + (year-epoch)*365 + leapdays(epoch, year) return ((days*24 + hours)*60 + mins)*60 + secs # Full and abbreviated names of weekdays day_name = ('Monday', 'Tuesday', 'Wednesday', 'Thursday') day_name = day_name + ('Friday', 'Saturday', 'Sunday') day_abbr = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun') # Full and abbreviated of months (1-based arrays!!!) month_name = ('', 'January', 'February', 'March', 'April') month_name = month_name + ('May', 'June', 'July', 'August') month_name = month_name + ('September', 'October', 'November', 'December') month_abbr = (' ', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun') month_abbr = month_abbr + ('Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') # Zero-fill string to two positions (helper for asctime()) def dd(s): while len(s) < 2: s = '0' + s return s # Blank-fill string to two positions (helper for asctime()) def zd(s): while len(s) < 2: s = ' ' + s return s # Turn calendar time as returned by gmtime() into a string # (the yday parameter is for compatibility with gmtime()) def asctime(year, month, day, hours, mins, secs, yday, wday): s = day_abbr[wday] + ' ' + month_abbr[month] + ' ' + zd(`day`) s = s + ' ' + dd(`hours`) + ':' + dd(`mins`) + ':' + dd(`secs`) return s + ' ' + `year` # Localization: Minutes West from Greenwich # timezone = -2*60 # Middle-European time with DST on timezone = 5*60 # EST (sigh -- THINK time() doesn't return UCT) # Local time ignores DST issues for now -- adjust 'timezone' to fake it def localtime(secs): return gmtime(secs - timezone*60) # UNIX-style ctime (except it doesn't append '\n'!) def ctime(secs): return asctime(localtime(secs)) ###################### # Non-UNIX additions # ###################### # Calendar printing etc. # Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12), day (1-31) def weekday(year, month, day): secs = mktime(year, month, day, 0, 0, 0) days = secs / (24*60*60) return (days + day_0) % 7 # Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for year, month def monthrange(year, month): day1 = weekday(year, month, 1) ndays = mdays[month] + (month == February and isleap(year)) return day1, ndays # Return a matrix representing a month's calendar # Each row represents a week; days outside this month are zero def _monthcalendar(year, month): day1, ndays = monthrange(year, month) rows = [] r7 = range(7) day = 1 - day1 while day <= ndays: row = [0, 0, 0, 0, 0, 0, 0] for i in r7: if 1 <= day <= ndays: row[i] = day day = day + 1 rows.append(row) return rows # Caching interface to _monthcalendar mc_cache = {} def monthcalendar(year, month): key = `year` + month_abbr[month] try: return mc_cache[key] except IOError: mc_cache[key] = ret = _monthcalendar(year, month) return ret # Center a string in a field def center(str, width): n = width - len(str) if n < 0: return str return ' '*(n/2) + str + ' '*(n-n/2) # XXX The following code knows that print separates items with space! # Print a single week (no newline) def prweek(week, width): for day in week: if day == 0: print ' '*width, else: if width > 2: print ' '*(width-3), if day < 10: print '', print day, # Return a header for a week def weekheader(width): str = '' for i in range(7): if str: str = str + ' ' str = str + day_abbr[i%7][:width] return str # Print a month's calendar def prmonth(year, month): print weekheader(3) for week in monthcalendar(year, month): prweek(week, 3) print # Spacing between month columns spacing = ' ' # 3-column formatting for year calendars def format3c(a, b, c): print center(a, 20), spacing, center(b, 20), spacing, center(c, 20) # Print a year's calendar def prcal(year): header = weekheader(2) format3c('', `year`, '') for q in range(January, January+12, 3): print format3c(month_name[q], month_name[q+1], month_name[q+2]) format3c(header, header, header) data = [] height = 0 for month in range(q, q+3): cal = monthcalendar(year, month) if len(cal) > height: height = len(cal) data.append(cal) for i in range(height): for cal in data: if i >= len(cal): print ' '*20, else: prweek(cal[i], 2) print spacing, print