Reusing your calendars, the pythonic way

Mo 14 Dezember 2015
By Uli Köhler

In Python

Yesterday got a calendar for 2016. An interesting question came up my mind: When can I reuse this calendar, and for which year can I reuse which old calendar?

The 1st January 2015 was a Thursday. The same day in 2016 is a Friday. Once you follow this pattery you will quickly recognize that the base period of seven years is disrupted by leap years.

It quickly turns out that for some years it takes decades until you can reuse a calendar: 2016 is a leap yer, so you can not reuse it for 2044.

However, there’s a neat quirk that is currently unimplemented in online services like whencanireusethiscalendar.com: You can partially reuse a calendar.

The reason that two calendars don’t match is often a leap day in one of them. Quite often, however, they match either up to the leap day or starting from the day after the leap day. We call these years partial reuse years (A) and (B) respectively. Once you consider this option (i.e. you interchange the calendar somewhere around the leap day, assuming we have a both a (A) and a (B) reusable calendar for one year) you will be able to reuse calendars — especially leap-year calendars like 2016.

Based on this insight, I wrote a simple brute-force python script using only core libraries. You can simply call it on the command line with the year of the calendar you

CalendarReuse.py download
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Compute a list of years where you can resue a calendar.
This script computes three types of reuse years:
    - Full reuse years where every day of the week matches
    - Partial reuse years (A) where every day up to the
        leap day (Feb 29) matches
    - Partial reuse years (B) where every day starting from the day
        after he leap day (Feb 29) matches
Partial reuse years do not include full reuse years by design.
"""
__copyright__ = "Copyright (c) 2015 Uli Köhler"
__license__ = "Apache License v2.0"
__version__ = "1.1"

from datetime import date
from collections import namedtuple

ReuseYears = namedtuple('ReuseYears',
                        ['year', 'fullReuseYears',
                         'partialReuseYearsA', 'partialReuseYearsB'])

def getReuseYears(year, startYear=2005, stopYear=2050):
    """
    Get years where you can reuse a calendar as ReuseYears object.
    """
    # Get the ISO weekday number for one of two refdates
    # Refdate 1 is the first day of the year
    # Refdate 2 is any date AFTER a leap day
    refdate1 = lambda y: date(y, 1, 1).isoweekday()
    refdate2 = lambda y: date(y, 5, 1).isoweekday()
    fullYears = []
    partialYearsA = []  # Until leap day
    partialYearsB = []  # From leap day
    for otherYear in range(startYear, stopYear + 1):
        if otherYear == year: continue
        aMatch = refdate1(year) == refdate1(otherYear)
        bMatch = refdate2(year) == refdate2(otherYear)
        if aMatch and bMatch:
            fullYears.append(otherYear)
        elif aMatch:
            partialYearsA.append(otherYear)
        elif bMatch:
            partialYearsB.append(otherYear)
    return ReuseYears(year, fullYears, partialYearsA, partialYearsB)

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('year', type=int, help='The year to get ')
    args = parser.parse_args()

    print(getReuseYears(args.year))