calendar.py 7.14 KB
Newer Older
1 2 3 4 5 6
"""Calendar printing functions

Note when comparing these calendars to the ones printed by cal(1): By
default, these calendars have Monday as the first day of the week, and
Sunday as the last (the European convention). Use setfirstweekday() to
set the first day of the week (0=Monday, 6=Sunday)."""
Guido van Rossum's avatar
Guido van Rossum committed
7

Jeremy Hylton's avatar
Jeremy Hylton committed
8
# Revision 2: uses functions from built-in time module
Guido van Rossum's avatar
Guido van Rossum committed
9

10
# Import functions and variables from time module
11
from time import localtime, mktime, strftime
Guido van Rossum's avatar
Guido van Rossum committed
12

13 14 15 16
__all__ = ["error","setfirstweekday","firstweekday","isleap",
           "leapdays","weekday","monthrange","monthcalendar",
           "prmonth","month","prcal","calendar","timegm"]

17
# Exception raised for bad input (with string parameter for details)
18
error = ValueError
19

Guido van Rossum's avatar
Guido van Rossum committed
20 21 22 23 24
# Constants for months referenced later
January = 1
February = 2

# Number of days per month (except for February in leap years)
25
mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
Guido van Rossum's avatar
Guido van Rossum committed
26

27
class _localized_name:
28
    def __init__(self, format, len):
29
        self.format = format
30
        self.len = len
31
    def __getitem__(self, item):
32 33 34 35
        if item > self.len-1 or item < -self.len:
            raise IndexError
        if item < 0:
            item += self.len
36
        return strftime(self.format, (item,)*9).capitalize()
37 38
    def __len__(self):
        return self.len
39

Guido van Rossum's avatar
Guido van Rossum committed
40
# Full and abbreviated names of weekdays
41 42
day_name = _localized_name('%A', 7)
day_abbr = _localized_name('%a', 7)
Guido van Rossum's avatar
Guido van Rossum committed
43

44
# Full and abbreviated names of months (1-based arrays!!!)
45 46
month_name = _localized_name('%B', 12)
month_abbr = _localized_name('%b', 12)
Guido van Rossum's avatar
Guido van Rossum committed
47

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
# Constants for weekdays
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)

_firstweekday = 0                       # 0 = Monday, 6 = Sunday

def firstweekday():
    return _firstweekday

def setfirstweekday(weekday):
    """Set weekday (Monday=0, Sunday=6) to start each week."""
    global _firstweekday
    if not MONDAY <= weekday <= SUNDAY:
        raise ValueError, \
              'bad weekday number; must be 0 (Monday) to 6 (Sunday)'
    _firstweekday = weekday

64
def isleap(year):
Guido van Rossum's avatar
Guido van Rossum committed
65
    """Return 1 for leap years, 0 for non-leap years."""
66
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Guido van Rossum's avatar
Guido van Rossum committed
67

68
def leapdays(y1, y2):
Guido van Rossum's avatar
Guido van Rossum committed
69
    """Return number of leap years in range [y1, y2).
70 71 72 73
       Assume y1 <= y2."""
    y1 -= 1
    y2 -= 1
    return (y2/4 - y1/4) - (y2/100 - y1/100) + (y2/400 - y1/400)
Guido van Rossum's avatar
Guido van Rossum committed
74 75

def weekday(year, month, day):
76 77
    """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
       day (1-31)."""
Guido van Rossum's avatar
Guido van Rossum committed
78 79 80
    secs = mktime((year, month, day, 0, 0, 0, 0, 0, 0))
    tuple = localtime(secs)
    return tuple[6]
Guido van Rossum's avatar
Guido van Rossum committed
81 82

def monthrange(year, month):
83 84 85 86
    """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
       year, month."""
    if not 1 <= month <= 12:
        raise ValueError, 'bad month number'
Guido van Rossum's avatar
Guido van Rossum committed
87 88 89
    day1 = weekday(year, month, 1)
    ndays = mdays[month] + (month == February and isleap(year))
    return day1, ndays
Guido van Rossum's avatar
Guido van Rossum committed
90

91
def monthcalendar(year, month):
Guido van Rossum's avatar
Guido van Rossum committed
92
    """Return a matrix representing a month's calendar.
93
       Each row represents a week; days outside this month are zero."""
Guido van Rossum's avatar
Guido van Rossum committed
94 95 96
    day1, ndays = monthrange(year, month)
    rows = []
    r7 = range(7)
97
    day = (_firstweekday - day1 + 6) % 7 - 5   # for leading 0's in first week
Guido van Rossum's avatar
Guido van Rossum committed
98 99 100 101 102 103 104 105
    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

106
def _center(str, width):
Guido van Rossum's avatar
Guido van Rossum committed
107 108
    """Center a string in a field."""
    n = width - len(str)
109 110
    if n <= 0:
        return str
Guido van Rossum's avatar
Guido van Rossum committed
111
    return ' '*((n+1)/2) + str + ' '*((n)/2)
Guido van Rossum's avatar
Guido van Rossum committed
112

113
def prweek(theweek, width):
Guido van Rossum's avatar
Guido van Rossum committed
114
    """Print a single week (no newline)."""
115 116 117 118 119 120 121 122 123 124 125 126
    print week(theweek, width),

def week(theweek, width):
    """Returns a single week in a string (no newline)."""
    days = []
    for day in theweek:
        if day == 0:
            s = ''
        else:
            s = '%2i' % day             # right-align single-digit days
        days.append(_center(s, width))
    return ' '.join(days)
Guido van Rossum's avatar
Guido van Rossum committed
127 128

def weekheader(width):
Guido van Rossum's avatar
Guido van Rossum committed
129
    """Return a header for a week."""
130 131 132 133 134 135 136 137 138 139
    if width >= 9:
        names = day_name
    else:
        names = day_abbr
    days = []
    for i in range(_firstweekday, _firstweekday + 7):
        days.append(_center(names[i%7][:width], width))
    return ' '.join(days)

def prmonth(theyear, themonth, w=0, l=0):
Guido van Rossum's avatar
Guido van Rossum committed
140
    """Print a month's calendar."""
141 142 143 144
    print month(theyear, themonth, w, l),

def month(theyear, themonth, w=0, l=0):
    """Return a month's calendar string (multi-line)."""
Guido van Rossum's avatar
Guido van Rossum committed
145 146
    w = max(2, w)
    l = max(1, l)
Tim Peters's avatar
Tim Peters committed
147
    s = (_center(month_name[themonth] + ' ' + `theyear`,
148 149 150 151 152 153 154
                 7 * (w + 1) - 1).rstrip() +
         '\n' * l + weekheader(w).rstrip() + '\n' * l)
    for aweek in monthcalendar(theyear, themonth):
        s = s + week(aweek, w).rstrip() + '\n' * l
    return s[:-l] + '\n'

# Spacing of month columns for 3-column year calendar
Guido van Rossum's avatar
Guido van Rossum committed
155
_colwidth = 7*3 - 1         # Amount printed by prweek()
156
_spacing = 6                # Number of spaces between columns
Guido van Rossum's avatar
Guido van Rossum committed
157

158 159 160
def format3c(a, b, c, colwidth=_colwidth, spacing=_spacing):
    """Prints 3-column formatting for year calendars"""
    print format3cstring(a, b, c, colwidth, spacing)
Guido van Rossum's avatar
Guido van Rossum committed
161

162 163 164 165 166 167
def format3cstring(a, b, c, colwidth=_colwidth, spacing=_spacing):
    """Returns a string formatted from 3 strings, centered within 3 columns."""
    return (_center(a, colwidth) + ' ' * spacing + _center(b, colwidth) +
            ' ' * spacing + _center(c, colwidth))

def prcal(year, w=0, l=0, c=_spacing):
Guido van Rossum's avatar
Guido van Rossum committed
168
    """Print a year's calendar."""
169 170 171 172 173 174 175 176 177 178 179
    print calendar(year, w, l, c),

def calendar(year, w=0, l=0, c=_spacing):
    """Returns a year's calendar as a multi-line string."""
    w = max(2, w)
    l = max(1, l)
    c = max(2, c)
    colwidth = (w + 1) * 7 - 1
    s = _center(`year`, colwidth * 3 + c * 2).rstrip() + '\n' * l
    header = weekheader(w)
    header = format3cstring(header, header, header, colwidth, c).rstrip()
Guido van Rossum's avatar
Guido van Rossum committed
180
    for q in range(January, January+12, 3):
181 182
        s = (s + '\n' * l +
             format3cstring(month_name[q], month_name[q+1], month_name[q+2],
Tim Peters's avatar
Tim Peters committed
183
                            colwidth, c).rstrip() +
184
             '\n' * l + header + '\n' * l)
Guido van Rossum's avatar
Guido van Rossum committed
185 186
        data = []
        height = 0
187 188 189 190
        for amonth in range(q, q + 3):
            cal = monthcalendar(year, amonth)
            if len(cal) > height:
                height = len(cal)
Guido van Rossum's avatar
Guido van Rossum committed
191 192
            data.append(cal)
        for i in range(height):
193
            weeks = []
Guido van Rossum's avatar
Guido van Rossum committed
194 195
            for cal in data:
                if i >= len(cal):
196
                    weeks.append('')
Guido van Rossum's avatar
Guido van Rossum committed
197
                else:
198
                    weeks.append(week(cal[i], w))
Tim Peters's avatar
Tim Peters committed
199
            s = s + format3cstring(weeks[0], weeks[1], weeks[2],
200 201
                                   colwidth, c).rstrip() + '\n' * l
    return s[:-l] + '\n'
Guido van Rossum's avatar
Guido van Rossum committed
202

203 204
EPOCH = 1970
def timegm(tuple):
Guido van Rossum's avatar
Guido van Rossum committed
205 206 207 208 209 210 211 212 213 214 215 216 217 218
    """Unrelated but handy function to calculate Unix timestamp from GMT."""
    year, month, day, hour, minute, second = tuple[:6]
    assert year >= EPOCH
    assert 1 <= month <= 12
    days = 365*(year-EPOCH) + leapdays(EPOCH, year)
    for i in range(1, month):
        days = days + mdays[i]
    if month > 2 and isleap(year):
        days = days + 1
    days = days + day - 1
    hours = days*24 + hour
    minutes = hours*60 + minute
    seconds = minutes*60 + second
    return seconds