classification
Title: Remove year limits from calendar
Type: enhancement Stage: commit review
Components: Versions: Python 3.7
process
Status: open Resolution:
Dependencies: 28253 Superseder:
Assigned To: belopolsky Nosy List: Mariatta, belopolsky, doerwalter, golly, matrixise, rhettinger, serhiy.storchaka, xiang.zhang
Priority: normal Keywords: easy, patch

Created on 2016-09-26 20:44 by belopolsky, last changed 2016-10-22 22:22 by belopolsky.

Files
File name Uploaded Description Edit
calendar-no-year-limits.patch golly, 2016-10-02 00:15 Remove year (0...9999) limit on weekday() function review
Messages (12)
msg277467 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2016-09-26 20:44
The calendar module currently relies on datetime for some calculations and is therefore restricted to years 1 through 9999.  With exception to some public methods that are documented to return datetime.date instances, this dependence on the datetime module can be removed.
msg277503 - (view) Author: Walter Dörwald (doerwalter) * (Python committer) Date: 2016-09-27 08:14
I don't think that's necessary. What's the use case for this?

And if we want to to this, wouldn't it be better to enhance datetime, so that this use case is supported too?
msg277526 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2016-09-27 17:17
> What's the use case for this?

Why did George Mallory climb Mount Everest? "Because it's there."

We have two open issues complaining about calendar behavior for years 1 and 9999: #28253 and #26650.  There is also a closed issue #15421 that attempted to fix an overflow in the year 9999 only to introduce a silent error.

I don't think there are use cases beyond educational (demonstrating calendar periodicity?) or testing, but since several users bothered to report edge case bugs, it is probably easier to remove the limits than to properly document them.

Fortunately, extending calendar to arbitrary years does not require climbing Mount Everest.  Since the proleptic calendar repeats every 400 years, all we need (assuming issue 28253 patch is applied) is to fix the weekday() function as follows:

 def weekday(year, month, day):
     """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
        day (1-31)."""
+    if not 0 < year < 10000:
+        year = 2000 + year % 400
     return datetime.date(year, month, day).weekday()

$ ./python.exe -mcalendar 40000 12
   December 40000
Mo Tu We Th Fr Sa Su
             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

$ ./python.exe -mcalendar -104 2
   February -104
Mo Tu We Th Fr Sa Su
                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

We can also treat negative years "properly" so that year 1 is preceded by year -1, but I don't think there is any value in this.
msg277541 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-09-27 19:10
I think data range is limited by year 1 due to ambiguity of year -1. Is it 1 B.D. or 2 years before year 1 (2 B.D.)?
msg277544 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2016-09-27 19:34
Under my proposal year=-1 is 2 B.C. and year 0 is a leap year

$ ./python.exe -mcalendar 0 2
     February 0
Mo Tu We Th Fr Sa Su
    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

This is the simplest extension and people who require other variants can make their own adjustments.  (This suggests that we should probably bring weekday(), isleap(), etc. methods to the Calendar class to allow users to override them.)
msg277634 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2016-09-28 14:57
> Under my proposal year=-1 is 2 B.C. and year 0 is a leap year

We should refuse the temptation to guess (i.e. make up our own arbitrary interpretations).   Instead, just document the known limits.
msg277641 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2016-09-28 16:16
The proposed interpretation of nonpositive years is not arbitrary, it is a natural extension of the same formulas that we use for positive years.  On the other hand 1-9999 limits are arbitrary.  If we wanted to restrict calendars to 4-digit years we would limit years to the 1000-9999 range.

Note that interpreting year 0 as 1 BCE is not without a precedent.  ISO 8601 standard does exactly that with the caveat that "values in the range [0000] through [1582] shall only be used by mutual agreement of the partners in information interchange." Furthermore, "an expanded year representation [±YYYYY] must have an agreed-upon number of extra year digits beyond the four-digit minimum, and it must be prefixed with a + or − sign instead of the more common AD/BC (or BCE/CE) notation; by convention 1 BC is labelled +0000, 2 BC is labeled -0001, and so on."  See <https://en.wikipedia.org/wiki/ISO_8601#Years> citing ISO 8601:2004 sections 3.4.2, 4.1.2.4 and Annex B.1.1.

Furthermore, documenting the existing limits is not an easy task.  For example

>>> from calendar import *
>>> cal = TextCalendar(1)
>>> cal.prmonth(1, 1)
     January 1
Tu We Th Fr Sa Su Mo
                   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

displays 6 days in December of 1 BCE as blanks.  Similarly itermonthdays() and itermonthdays2() generators yield objects corresponding to these days.  Are these days within the calendar limits or not?

Note that I am not proposing to extend the range of the datetime.date objects.  Doing that would be a much more difficult task, but defining calendars for out of range years as those for year % 400 is trivial and much easier than to figure out and document the edge behaviors.
msg277661 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-09-28 20:21
Okay, if this is specified by ISO 8601 standard, I think we can extend calendar below year 1. But the meaning of non-positive years should be documented. And maybe even provide a way to customize the representation of year (this is a separate issue).

Standard Unix utilities cal and ncal support only years in range 1..9999.
msg277835 - (view) Author: Mark Gollahon (golly) * Date: 2016-10-02 00:15
First time patch for CPython.  I followed instructions given by belopolsky and added tests.  Please critique.
msg279136 - (view) Author: Stéphane Wirtel (matrixise) * Date: 2016-10-21 15:40
Who can merge this patch ?
msg279225 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-10-22 21:53
Alexander as core developer and the creator of this issue can merge the patch after making his review. But first we should make a decision whether it is worth to do at all. I'm +0 now. Raymond seems has objections.
msg279228 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2016-10-22 22:22
The patch should include an update to documentation.

1. We should probably explain that python -mcalendar does not reproduce the output of UNIX cal.  For example, on Mac OS (and various Linux variants): 

$ cal 9 1752
   September 1752
Su Mo Tu We Th Fr Sa
       1  2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30


but 

$ python3 -mcalendar 1752 9
   September 1752
Mo Tu We Th Fr Sa Su
             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

2. We should explain that while the calendar module relies on datetime, it implements an infinite calendar with a period of 400 years.

3. A reference should be made to ISO 8601 for our treatment of nonpositive years.

Given ISO 8601 and the simplicity of this change, I don't think Raymond will insist that we continue imposing datetime-like limits, but I would like to give him a chance to renew his objection once the no-limits calendar is documented.
History
Date User Action Args
2016-10-22 22:22:46belopolskysetmessages: + msg279228
2016-10-22 21:53:42serhiy.storchakasetmessages: + msg279225
2016-10-21 15:40:53matrixisesetnosy: + matrixise
messages: + msg279136
2016-10-21 15:40:01matrixisesetstage: needs patch -> commit review
2016-10-02 00:15:57gollysetfiles: + calendar-no-year-limits.patch

nosy: + golly
messages: + msg277835

keywords: + patch
2016-09-28 20:21:03serhiy.storchakasetmessages: + msg277661
2016-09-28 17:37:06Mariattasetnosy: + Mariatta
2016-09-28 16:16:03belopolskysetmessages: + msg277641
2016-09-28 14:57:18rhettingersetnosy: + rhettinger
messages: + msg277634
2016-09-27 19:34:59belopolskysetmessages: + msg277544
2016-09-27 19:10:04serhiy.storchakasetmessages: + msg277541
2016-09-27 17:17:35belopolskysetnosy: + xiang.zhang
messages: + msg277526

dependencies: + calendar.prcal(9999) output has a problem
keywords: + easy
2016-09-27 08:14:01doerwaltersetmessages: + msg277503
2016-09-26 21:42:22serhiy.storchakasetnosy: + doerwalter
2016-09-26 20:44:06belopolskycreate