| # 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 RuntimeError: |
| 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 |