Define constants for month numbers in calendar module

The calendar module already defines constants e.g. MONDAY (0), TUESDAY (1), etc. for the days of the week.

Since these are likely to be commonly needed too, would it make sense for the calendar module to also export constants for all of JANUARY (1), FEBRUARY (2), …, DECEMBER (12)?

This would allow one to write from calendar import APRIL and then e.g. use APRIL instead of 4 when building a datetime.date object.

Related: datetime - Python module defining constants for month numbers? - Stack Overflow

It does seem a little odd, but the days of week are in all caps, where the months aren’t.

>>> calendar.February
2
>>> calendar.FRIDAY
4

Note January and February are defined but:

  • not exported in __all__,
  • all other months are missing.

In addition to the discrepancy on case.

Oh. I didn’t even notice that. Curious. It looks like those particular constants should probably be considered internal, then.

TBH these should probably be an enum, not just loose integers in the module. I don’t use the calendar module enough to have a strong opinion, but if you look at things like re.VERBOSE there’s good precedent for using an enum for this.

3 Likes

a bit of context; some other languages do define such constants in their standard libraries:

  • go
  • Java 8
  • Rust - chrono crate is not exactly standard lib but pretty close afaik

…so for a language like Python it wouldn’t hurt in my eyes to have such a convenience :slight_smile:

+1 for using (int) enums

turns out the weekdays are derived from a range :wink:

1 Like

Yeah, deriving from a range is a pretty reasonable way to do a naive enumeration. But since we have the actual enum module now, it’s better to use that IMO.

Indeed, using a global_enum as in re.VERBOSE sounds like a great idea to me.

@smontanaro You seem to know calendar as well as any current dev. Any opinion on this idea? Would using an enum also for the days break anything?

Not Skip, but If we use an IntEnum nothing should break. Hopefully calendar has good tests!

Opened an issue.

It shouldn’t. The aforementioned regex flags were changed in that exact sort of way:

rosuav@sikorsky:~$ python3.5 -c 'import re; print(type(re.VERBOSE))'
<class 'int'>
rosuav@sikorsky:~$ python3.6 -c 'import re; print(type(re.VERBOSE))'
<enum 'RegexFlag'>
rosuav@sikorsky:~$ python3.11 -c 'import re; print(type(re.VERBOSE))'
<flag 'RegexFlag'>

where RegexFlag is an IntFlag (because they’re bitwise - for the days and months, IntEnum would be the choice).

Since calendar.January and calendar.February are intended for internal use, should they be deprecated in favour of the new enums?

Searching the top 5k packages, they only show up in jsonargparse:

$ python3 ~/github/misc/cpython/search_pypi_top.py -q . "from calendar import .*(January|February)"
./jsonargparse-4.20.1.tar.gz: jsonargparse-4.20.1/jsonargparse_tests/test_signatures.py: from calendar import Calendar, January  # type: ignore

Time: 0:00:16.076726
Found 1 matching lines in 1 projects
$ python3 ~/github/misc/cpython/search_pypi_top.py -q . "calendar\.(January|February)"
./jsonargparse-4.20.1.tar.gz: jsonargparse-4.20.1/jsonargparse_tests/test_signatures.py: self.assertRaises(ArgumentError, lambda: parser.parse_args(['--cal={"class_path":"calendar.January"}']))
./jsonargparse-4.20.1.tar.gz: jsonargparse-4.20.1/jsonargparse_tests/test_signatures.py: self.assertRaises(ArgumentError, lambda: parser.parse_args(['--cal.help=calendar.January']))

Time: 0:00:15.878868
Found 2 matching lines in 1 projects

And only 12 and 22 files found via GitHub’s beta code search.

I can see them being IntFlag – makes it easy to represent a task that happens on M/W/F.

gh-103636: add enums for days and months in calendar module by Agent-Hellboy · Pull Request #103642 · python/cpython · GitHub has added the month and day enums (JANUARY - DECEMBER, MONDAY - SUNDAY), and removed the two January and February constants.

What’s the advice for projects using the now-removed constants? Something like this?

try:
    # Python 3.12+  
    from calendar import JANUARY
except ImportError:
    # Python <= 3.11
    from calendar import January as JANUARY 

I’m pretty sure PEP-387 says that we cannot remove January and February without a deprecation period. I’d strongly consider adding back those two constants.

This was discussed in the pull request. As I understand, the old constants were never part of the public API and, as such, it was decided no deprecation warning was required.

I’m not so sure about that. Hugo’s example is perfectly valid in 3.11. Now it is not valid. I suggest asking the SC.

1 Like

Is this a hypothetical, or do you know of projects actually doing this?

Only January and February were defined (not March - December), and they were not part of the public API (so PEP 387 does not apply).

We could add a module-level __getattr__ to continue to provide January and February and issue a warning to anybody who is using it…