summaryrefslogtreecommitdiff
path: root/Lib/calendar.py
blob: f00268b269d995d95a8d19ffe029eec7bada4e36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# 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