Guido van Rossum | c636014 | 1990-10-13 19:23:40 +0000 | [diff] [blame] | 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 |