|
| 1 | +# module calendar |
| 2 | + |
| 3 | +############################## |
| 4 | +# Calendar support functions # |
| 5 | +############################## |
| 6 | + |
| 7 | +# This is based on UNIX ctime() et al. (also Standard C and POSIX) |
| 8 | +# Subtle but crucial differences: |
| 9 | +# - the order of the elements of a 'struct tm' differs, to ease sorting |
| 10 | +# - months numbers are 1-12, not 0-11; month arrays have a dummy element 0 |
| 11 | +# - Monday is the first day of the week (numbered 0) |
| 12 | + |
| 13 | +# These are really parameters of the 'time' module: |
| 14 | +epoch = 1970 # Time began on January 1 of this year (00:00:00 UCT) |
| 15 | +day_0 = 3 # The epoch begins on a Thursday (Monday = 0) |
| 16 | + |
| 17 | +# Return 1 for leap years, 0 for non-leap years |
| 18 | +def isleap(year): |
| 19 | + return year % 4 = 0 and (year % 100 <> 0 or year % 400 = 0) |
| 20 | + |
| 21 | +# Constants for months referenced later |
| 22 | +January = 1 |
| 23 | +February = 2 |
| 24 | + |
| 25 | +# Number of days per month (except for February in leap years) |
| 26 | +mdays = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) |
| 27 | + |
| 28 | +# Exception raised for bad input (with string parameter for details) |
| 29 | +error = 'calendar error' |
| 30 | + |
| 31 | +# Turn seconds since epoch into calendar time |
| 32 | +def gmtime(secs): |
| 33 | + if secs < 0: raise error, 'negative input to gmtime()' |
| 34 | + mins, secs = divmod(secs, 60) |
| 35 | + hours, mins = divmod(mins, 60) |
| 36 | + days, hours = divmod(hours, 24) |
| 37 | + wday = (days + day_0) % 7 |
| 38 | + year = epoch |
| 39 | + # XXX Most of the following loop can be replaced by one division |
| 40 | + while 1: |
| 41 | + yd = 365 + isleap(year) |
| 42 | + if days < yd: break |
| 43 | + days = days - yd |
| 44 | + year = year + 1 |
| 45 | + yday = days |
| 46 | + month = January |
| 47 | + while 1: |
| 48 | + md = mdays[month] + (month = February and isleap(year)) |
| 49 | + if days < md: break |
| 50 | + days = days - md |
| 51 | + month = month + 1 |
| 52 | + return year, month, days + 1, hours, mins, secs, yday, wday |
| 53 | + # XXX Week number also? |
| 54 | + |
| 55 | +# Return number of leap years in range [y1, y2) |
| 56 | +# Assume y1 <= y2 and no funny (non-leap century) years |
| 57 | +def leapdays(y1, y2): |
| 58 | + return (y2+3)/4 - (y1+3)/4 |
| 59 | + |
| 60 | +# Inverse of gmtime(): |
| 61 | +# Turn UCT calendar time (less yday, wday) into seconds since epoch |
| 62 | +def mktime(year, month, day, hours, mins, secs): |
| 63 | + days = day - 1 |
| 64 | + for m in range(January, month): days = days + mdays[m] |
| 65 | + if isleap(year) and month > February: days = days+1 |
| 66 | + days = days + (year-epoch)*365 + leapdays(epoch, year) |
| 67 | + return ((days*24 + hours)*60 + mins)*60 + secs |
| 68 | + |
| 69 | +# Full and abbreviated names of weekdays |
| 70 | +day_name = ('Monday', 'Tuesday', 'Wednesday', 'Thursday') |
| 71 | +day_name = day_name + ('Friday', 'Saturday', 'Sunday') |
| 72 | +day_abbr = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun') |
| 73 | + |
| 74 | +# Full and abbreviated of months (1-based arrays!!!) |
| 75 | +month_name = ('', 'January', 'February', 'March', 'April') |
| 76 | +month_name = month_name + ('May', 'June', 'July', 'August') |
| 77 | +month_name = month_name + ('September', 'October', 'November', 'December') |
| 78 | +month_abbr = (' ', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun') |
| 79 | +month_abbr = month_abbr + ('Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') |
| 80 | + |
| 81 | +# Zero-fill string to two positions (helper for asctime()) |
| 82 | +def dd(s): |
| 83 | + while len(s) < 2: s = '0' + s |
| 84 | + return s |
| 85 | + |
| 86 | +# Blank-fill string to two positions (helper for asctime()) |
| 87 | +def zd(s): |
| 88 | + while len(s) < 2: s = ' ' + s |
| 89 | + return s |
| 90 | + |
| 91 | +# Turn calendar time as returned by gmtime() into a string |
| 92 | +# (the yday parameter is for compatibility with gmtime()) |
| 93 | +def asctime(year, month, day, hours, mins, secs, yday, wday): |
| 94 | + s = day_abbr[wday] + ' ' + month_abbr[month] + ' ' + zd(`day`) |
| 95 | + s = s + ' ' + dd(`hours`) + ':' + dd(`mins`) + ':' + dd(`secs`) |
| 96 | + return s + ' ' + `year` |
| 97 | + |
| 98 | +# Localization: Minutes West from Greenwich |
| 99 | +# timezone = -2*60 # Middle-European time with DST on |
| 100 | +timezone = 5*60 # EST (sigh -- THINK time() doesn't return UCT) |
| 101 | + |
| 102 | +# Local time ignores DST issues for now -- adjust 'timezone' to fake it |
| 103 | +def localtime(secs): |
| 104 | + return gmtime(secs - timezone*60) |
| 105 | + |
| 106 | +# UNIX-style ctime (except it doesn't append '\n'!) |
| 107 | +def ctime(secs): |
| 108 | + return asctime(localtime(secs)) |
| 109 | + |
| 110 | +###################### |
| 111 | +# Non-UNIX additions # |
| 112 | +###################### |
| 113 | + |
| 114 | +# Calendar printing etc. |
| 115 | + |
| 116 | +# Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12), day (1-31) |
| 117 | +def weekday(year, month, day): |
| 118 | + secs = mktime(year, month, day, 0, 0, 0) |
| 119 | + days = secs / (24*60*60) |
| 120 | + return (days + day_0) % 7 |
| 121 | + |
| 122 | +# Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for year, month |
| 123 | +def monthrange(year, month): |
| 124 | + day1 = weekday(year, month, 1) |
| 125 | + ndays = mdays[month] + (month = February and isleap(year)) |
| 126 | + return day1, ndays |
| 127 | + |
| 128 | +# Return a matrix representing a month's calendar |
| 129 | +# Each row represents a week; days outside this month are zero |
| 130 | +def _monthcalendar(year, month): |
| 131 | + day1, ndays = monthrange(year, month) |
| 132 | + rows = [] |
| 133 | + r7 = range(7) |
| 134 | + day = 1 - day1 |
| 135 | + while day <= ndays: |
| 136 | + row = [0, 0, 0, 0, 0, 0, 0] |
| 137 | + for i in r7: |
| 138 | + if 1 <= day <= ndays: row[i] = day |
| 139 | + day = day + 1 |
| 140 | + rows.append(row) |
| 141 | + return rows |
| 142 | + |
| 143 | +# Caching interface to _monthcalendar |
| 144 | +mc_cache = {} |
| 145 | +def monthcalendar(year, month): |
| 146 | + key = `year` + month_abbr[month] |
| 147 | + try: |
| 148 | + return mc_cache[key] |
| 149 | + except RuntimeError: |
| 150 | + mc_cache[key] = ret = _monthcalendar(year, month) |
| 151 | + return ret |
| 152 | + |
| 153 | +# Center a string in a field |
| 154 | +def center(str, width): |
| 155 | + n = width - len(str) |
| 156 | + if n < 0: return str |
| 157 | + return ' '*(n/2) + str + ' '*(n-n/2) |
| 158 | + |
| 159 | +# XXX The following code knows that print separates items with space! |
| 160 | + |
| 161 | +# Print a single week (no newline) |
| 162 | +def prweek(week, width): |
| 163 | + for day in week: |
| 164 | + if day = 0: print ' '*width, |
| 165 | + else: |
| 166 | + if width > 2: print ' '*(width-3), |
| 167 | + if day < 10: print '', |
| 168 | + print day, |
| 169 | + |
| 170 | +# Return a header for a week |
| 171 | +def weekheader(width): |
| 172 | + str = '' |
| 173 | + for i in range(7): |
| 174 | + if str: str = str + ' ' |
| 175 | + str = str + day_abbr[i%7][:width] |
| 176 | + return str |
| 177 | + |
| 178 | +# Print a month's calendar |
| 179 | +def prmonth(year, month): |
| 180 | + print weekheader(3) |
| 181 | + for week in monthcalendar(year, month): |
| 182 | + prweek(week, 3) |
| 183 | + print |
| 184 | + |
| 185 | +# Spacing between month columns |
| 186 | +spacing = ' ' |
| 187 | + |
| 188 | +# 3-column formatting for year calendars |
| 189 | +def format3c(a, b, c): |
| 190 | + print center(a, 20), spacing, center(b, 20), spacing, center(c, 20) |
| 191 | + |
| 192 | +# Print a year's calendar |
| 193 | +def prcal(year): |
| 194 | + header = weekheader(2) |
| 195 | + format3c('', `year`, '') |
| 196 | + for q in range(January, January+12, 3): |
| 197 | + print |
| 198 | + format3c(month_name[q], month_name[q+1], month_name[q+2]) |
| 199 | + format3c(header, header, header) |
| 200 | + data = [] |
| 201 | + height = 0 |
| 202 | + for month in range(q, q+3): |
| 203 | + cal = monthcalendar(year, month) |
| 204 | + if len(cal) > height: height = len(cal) |
| 205 | + data.append(cal) |
| 206 | + for i in range(height): |
| 207 | + for cal in data: |
| 208 | + if i >= len(cal): |
| 209 | + print ' '*20, |
| 210 | + else: |
| 211 | + prweek(cal[i], 2) |
| 212 | + print spacing, |
| 213 | + print |
0 commit comments