Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,14 @@ New modules
Improved modules
================

calendar
--------

* Calendar pages generated by the :class:`calendar.HTMLCalendar` class now support
dark mode and have been migrated to the HTML5 standard for improved accessibility.
(Contributed by Jiahao Li and Hugo van Kemenade in :gh:`137634`.)


collections
-----------

Expand Down
42 changes: 21 additions & 21 deletions Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,51 +498,48 @@ def formatday(self, day, weekday):
"""
if day == 0:
# day outside month
return '<td class="%s">&nbsp;</td>' % self.cssclass_noday
return f'<td class="{self.cssclass_noday}">&nbsp;</td>'
else:
return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
return f'<td class="{self.cssclasses[weekday]}">{day}</td>'

def formatweek(self, theweek):
"""
Return a complete week as a table row.
"""
s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
return '<tr>%s</tr>' % s
return f'<tr>{s}</tr>'

def formatweekday(self, day):
"""
Return a weekday name as a table header.
"""
return '<th class="%s">%s</th>' % (
self.cssclasses_weekday_head[day], day_abbr[day])
return f'<th class="{self.cssclasses_weekday_head[day]}">{day_abbr[day]}</th>'

def formatweekheader(self):
"""
Return a header for a week as a table row.
"""
s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
return '<tr>%s</tr>' % s
return f'<tr>{s}</tr>'

def formatmonthname(self, theyear, themonth, withyear=True):
"""
Return a month name as a table row.
"""
_validate_month(themonth)
if withyear:
s = '%s %s' % (standalone_month_name[themonth], theyear)
s = f'{standalone_month_name[themonth]} {theyear}'
else:
s = standalone_month_name[themonth]
return '<tr><th colspan="7" class="%s">%s</th></tr>' % (
self.cssclass_month_head, s)
return f'<tr><th colspan="7" class="{self.cssclass_month_head}">{s}</th></tr>'

def formatmonth(self, theyear, themonth, withyear=True):
"""
Return a formatted month as a table.
"""
v = []
a = v.append
a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % (
self.cssclass_month))
a(f'<table class="{self.cssclass_month}">')
a('\n')
a(self.formatmonthname(theyear, themonth, withyear=withyear))
a('\n')
Expand All @@ -562,11 +559,9 @@ def formatyear(self, theyear, width=3):
v = []
a = v.append
width = max(width, 1)
a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' %
self.cssclass_year)
a(f'<table class="{self.cssclass_year}">')
a('\n')
a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
width, self.cssclass_year_head, theyear))
a(f'<tr><th colspan="{width}" class="{self.cssclass_year_head}">{theyear}</th></tr>')
for i in range(JANUARY, JANUARY+12, width):
# months in this row
months = range(i, min(i+width, 13))
Expand All @@ -587,14 +582,19 @@ def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
encoding = 'utf-8'
v = []
a = v.append
a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
a('<html>\n')
a('<!DOCTYPE html>\n')
a('<html lang="en">\n')
a('<head>\n')
a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
a(f'<meta charset="{encoding}">\n')
a('<meta name="viewport" content="width=device-width, initial-scale=1">\n')
a(f'<title>Calendar for {theyear}</title>\n')
a('<style>\n')
a(':root { color-scheme: light dark; }\n')
a('table.year { border: solid; }\n')
a('table.year > tbody > tr > td { border: solid; vertical-align: top; }\n')
a('</style>\n')
if css is not None:
a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
a('<title>Calendar for %d</title>\n' % theyear)
a(f'<link rel="stylesheet" href="{css}">\n')
a('</head>\n')
a('<body>\n')
a(self.formatyear(theyear, width))
Expand Down
58 changes: 34 additions & 24 deletions Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,25 @@

default_format = dict(year="year", month="month", encoding="ascii")

result_2004_css = """<style>
:root { color-scheme: light dark; }
table.year { border: solid; }
table.year > tbody > tr > td { border: solid; vertical-align: top; }
</style>"""

result_2004_html = """\
<?xml version="1.0" encoding="{encoding}"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset={encoding}" />
<link rel="stylesheet" type="text/css" href="calendar.css" />
<meta charset="{encoding}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar for 2004</title>
{css_styles}
<link rel="stylesheet" href="calendar.css">
</head>
<body>
<table border="0" cellpadding="0" cellspacing="0" class="{year}">
<tr><th colspan="3" class="{year}">2004</th></tr><tr><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
<table class="{year}">
<tr><th colspan="3" class="{year}">2004</th></tr><tr><td><table class="{month}">
<tr><th colspan="7" class="{month}">January</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="thu">1</td><td class="fri">2</td><td class="sat">3</td><td class="sun">4</td></tr>
Expand All @@ -133,7 +140,7 @@
<tr><td class="mon">19</td><td class="tue">20</td><td class="wed">21</td><td class="thu">22</td><td class="fri">23</td><td class="sat">24</td><td class="sun">25</td></tr>
<tr><td class="mon">26</td><td class="tue">27</td><td class="wed">28</td><td class="thu">29</td><td class="fri">30</td><td class="sat">31</td><td class="noday">&nbsp;</td></tr>
</table>
</td><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td><td><table class="{month}">
<tr><th colspan="7" class="{month}">February</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="sun">1</td></tr>
Expand All @@ -142,7 +149,7 @@
<tr><td class="mon">16</td><td class="tue">17</td><td class="wed">18</td><td class="thu">19</td><td class="fri">20</td><td class="sat">21</td><td class="sun">22</td></tr>
<tr><td class="mon">23</td><td class="tue">24</td><td class="wed">25</td><td class="thu">26</td><td class="fri">27</td><td class="sat">28</td><td class="sun">29</td></tr>
</table>
</td><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td><td><table class="{month}">
<tr><th colspan="7" class="{month}">March</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="mon">1</td><td class="tue">2</td><td class="wed">3</td><td class="thu">4</td><td class="fri">5</td><td class="sat">6</td><td class="sun">7</td></tr>
Expand All @@ -151,7 +158,7 @@
<tr><td class="mon">22</td><td class="tue">23</td><td class="wed">24</td><td class="thu">25</td><td class="fri">26</td><td class="sat">27</td><td class="sun">28</td></tr>
<tr><td class="mon">29</td><td class="tue">30</td><td class="wed">31</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>
</table>
</td></tr><tr><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td></tr><tr><td><table class="{month}">
<tr><th colspan="7" class="{month}">April</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="thu">1</td><td class="fri">2</td><td class="sat">3</td><td class="sun">4</td></tr>
Expand All @@ -160,7 +167,7 @@
<tr><td class="mon">19</td><td class="tue">20</td><td class="wed">21</td><td class="thu">22</td><td class="fri">23</td><td class="sat">24</td><td class="sun">25</td></tr>
<tr><td class="mon">26</td><td class="tue">27</td><td class="wed">28</td><td class="thu">29</td><td class="fri">30</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>
</table>
</td><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td><td><table class="{month}">
<tr><th colspan="7" class="{month}">May</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="sat">1</td><td class="sun">2</td></tr>
Expand All @@ -170,7 +177,7 @@
<tr><td class="mon">24</td><td class="tue">25</td><td class="wed">26</td><td class="thu">27</td><td class="fri">28</td><td class="sat">29</td><td class="sun">30</td></tr>
<tr><td class="mon">31</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>
</table>
</td><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td><td><table class="{month}">
<tr><th colspan="7" class="{month}">June</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="tue">1</td><td class="wed">2</td><td class="thu">3</td><td class="fri">4</td><td class="sat">5</td><td class="sun">6</td></tr>
Expand All @@ -179,7 +186,7 @@
<tr><td class="mon">21</td><td class="tue">22</td><td class="wed">23</td><td class="thu">24</td><td class="fri">25</td><td class="sat">26</td><td class="sun">27</td></tr>
<tr><td class="mon">28</td><td class="tue">29</td><td class="wed">30</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>
</table>
</td></tr><tr><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td></tr><tr><td><table class="{month}">
<tr><th colspan="7" class="{month}">July</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="thu">1</td><td class="fri">2</td><td class="sat">3</td><td class="sun">4</td></tr>
Expand All @@ -188,7 +195,7 @@
<tr><td class="mon">19</td><td class="tue">20</td><td class="wed">21</td><td class="thu">22</td><td class="fri">23</td><td class="sat">24</td><td class="sun">25</td></tr>
<tr><td class="mon">26</td><td class="tue">27</td><td class="wed">28</td><td class="thu">29</td><td class="fri">30</td><td class="sat">31</td><td class="noday">&nbsp;</td></tr>
</table>
</td><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td><td><table class="{month}">
<tr><th colspan="7" class="{month}">August</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="sun">1</td></tr>
Expand All @@ -198,7 +205,7 @@
<tr><td class="mon">23</td><td class="tue">24</td><td class="wed">25</td><td class="thu">26</td><td class="fri">27</td><td class="sat">28</td><td class="sun">29</td></tr>
<tr><td class="mon">30</td><td class="tue">31</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>
</table>
</td><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td><td><table class="{month}">
<tr><th colspan="7" class="{month}">September</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="wed">1</td><td class="thu">2</td><td class="fri">3</td><td class="sat">4</td><td class="sun">5</td></tr>
Expand All @@ -207,7 +214,7 @@
<tr><td class="mon">20</td><td class="tue">21</td><td class="wed">22</td><td class="thu">23</td><td class="fri">24</td><td class="sat">25</td><td class="sun">26</td></tr>
<tr><td class="mon">27</td><td class="tue">28</td><td class="wed">29</td><td class="thu">30</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>
</table>
</td></tr><tr><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td></tr><tr><td><table class="{month}">
<tr><th colspan="7" class="{month}">October</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="fri">1</td><td class="sat">2</td><td class="sun">3</td></tr>
Expand All @@ -216,7 +223,7 @@
<tr><td class="mon">18</td><td class="tue">19</td><td class="wed">20</td><td class="thu">21</td><td class="fri">22</td><td class="sat">23</td><td class="sun">24</td></tr>
<tr><td class="mon">25</td><td class="tue">26</td><td class="wed">27</td><td class="thu">28</td><td class="fri">29</td><td class="sat">30</td><td class="sun">31</td></tr>
</table>
</td><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td><td><table class="{month}">
<tr><th colspan="7" class="{month}">November</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="mon">1</td><td class="tue">2</td><td class="wed">3</td><td class="thu">4</td><td class="fri">5</td><td class="sat">6</td><td class="sun">7</td></tr>
Expand All @@ -225,7 +232,7 @@
<tr><td class="mon">22</td><td class="tue">23</td><td class="wed">24</td><td class="thu">25</td><td class="fri">26</td><td class="sat">27</td><td class="sun">28</td></tr>
<tr><td class="mon">29</td><td class="tue">30</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td></tr>
</table>
</td><td><table border="0" cellpadding="0" cellspacing="0" class="{month}">
</td><td><table class="{month}">
<tr><th colspan="7" class="{month}">December</th></tr>
<tr><th class="mon">Mon</th><th class="tue">Tue</th><th class="wed">Wed</th><th class="thu">Thu</th><th class="fri">Fri</th><th class="sat">Sat</th><th class="sun">Sun</th></tr>
<tr><td class="noday">&nbsp;</td><td class="noday">&nbsp;</td><td class="wed">1</td><td class="thu">2</td><td class="fri">3</td><td class="sat">4</td><td class="sun">5</td></tr>
Expand Down Expand Up @@ -385,10 +392,12 @@ def check_htmlcalendar_encoding(self, req, res):
cal = calendar.HTMLCalendar()
format_ = default_format.copy()
format_["encoding"] = req or 'utf-8'
format_with_css = {**format_, "css_styles": result_2004_css}
formatted_html = result_2004_html.format(**format_with_css)
output = cal.formatyearpage(2004, encoding=req)
self.assertEqual(
output,
result_2004_html.format(**format_).encode(res)
formatted_html.encode(res)
)

def test_output(self):
Expand Down Expand Up @@ -1183,7 +1192,7 @@ def test_option_type(self):
output = run('--type', 'text', '2004')
self.assertEqual(output, conv(result_2004_text))
output = run('--type', 'html', '2004')
self.assertStartsWith(output, b'<?xml ')
self.assertStartsWith(output, b'<!DOCTYPE html>')
self.assertIn(b'<title>Calendar for 2004</title>', output)

def test_html_output_current_year(self):
Expand All @@ -1196,15 +1205,16 @@ def test_html_output_current_year(self):
def test_html_output_year_encoding(self):
for run in self.runners:
output = run('-t', 'html', '--encoding', 'ascii', '2004')
self.assertEqual(output, result_2004_html.format(**default_format).encode('ascii'))
format_with_css = default_format.copy()
format_with_css["css_styles"] = result_2004_css
self.assertEqual(output, result_2004_html.format(**format_with_css).encode('ascii'))

def test_html_output_year_css(self):
self.assertFailure('-t', 'html', '-c')
self.assertFailure('-t', 'html', '--css')
for run in self.runners:
output = run('-t', 'html', '--css', 'custom.css', '2004')
self.assertIn(b'<link rel="stylesheet" type="text/css" '
b'href="custom.css" />', output)
self.assertIn(b'<link rel="stylesheet" href="custom.css">', output)


class MiscTestCase(unittest.TestCase):
Expand Down Expand Up @@ -1258,7 +1268,7 @@ def test_formatweek_head(self):

def test_format_year(self):
self.assertIn(
('<table border="0" cellpadding="0" cellspacing="0" class="%s">' %
('<table class="%s">' %
self.cal.cssclass_year), self.cal.formatyear(2017))

def test_format_year_head(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Calendar pages generated by the :class:`calendar.HTMLCalendar` class now support
dark mode and have been migrated to the HTML5 standard for improved accessibility.
Loading