Kalender wiederverwenden – der pythonische Weg

English Deutsch

Gestern habe ich einen Kalender für 2016 bekommen. Eine interessante Frage kam mir in den Sinn: Wann kann ich diesen Kalender wiederverwenden, und für welches Jahr kann ich welchen alten Kalender wiederverwenden?

Der 1. Januar 2015 war ein Donnerstag. Derselbe Tag in 2016 ist ein Freitag. Wenn man diesem Muster folgt, erkennt man schnell, dass die Basisperiode von sieben Jahren durch Schaltjahre unterbrochen wird.

Es stellt sich schnell heraus, dass es für einige Jahre Jahrzehnte dauert, bis man einen Kalender wiederverwenden kann: 2016 ist ein Schaltjahr, also kann man ihn nicht für 2044 wiederverwenden.

Es gibt jedoch einen netten Trick, der derzeit in Online-Diensten wie whencanireusethiscalendar.com nicht implementiert ist: Man kann einen Kalender teilweise wiederverwenden.

Der Grund, warum zwei Kalender nicht übereinstimmen, ist oft ein Schalttag in einem von beiden. Oft stimmen sie jedoch bis zum Schalttag oder ab dem Tag nach dem Schalttag überein. Wir nennen diese Jahre partielle Wiederverwendungsjahre (A) bzw. (B). Wenn man diese Option berücksichtigt (d.h. man tauscht den Kalender irgendwo um den Schalttag herum aus, vorausgesetzt, man hat sowohl einen (A)- als auch einen (B)-Wiederverwendungskalender für ein Jahr), kann man Kalender wiederverwenden – insbesondere Schaltjahr-Kalender wie 2016.

Basierend auf dieser Erkenntnis habe ich ein einfaches Brute-Force-Python-Skript geschrieben, das nur Kernbibliotheken verwendet. Man kann es einfach auf der Kommandozeile mit dem Jahr des Kalenders aufrufen, den man wiederverwenden möchte:

reuse-calendars.sh
python3 reuse-calendars.py 2015

Das Skript verwendet einen Trial-and-Error-Ansatz, da der Suchraum für einen modernen Computer recht klein ist:

reuse-calendars.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Berechnet eine Liste von Jahren, in denen man einen Kalender wiederverwenden kann.
Dieses Skript berechnet drei Arten von Wiederverwendungsjahren:
    - Volle Wiederverwendungsjahre, bei denen jeder Wochentag übereinstimmt
    - Partielle Wiederverwendungsjahre (A), bei denen jeder Tag bis zum
        Schalttag (29. Feb) übereinstimmt
    - Partielle Wiederverwendungsjahre (B), bei denen jeder Tag ab dem Tag
        nach dem Schalttag (29. Feb) übereinstimmt
Partielle Wiederverwendungsjahre enthalten volle Wiederverwendungsjahre bewusst nicht.
"""
__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):
    """
    Gibt Jahre zurück, in denen man einen Kalender wiederverwenden kann, als ReuseYears-Objekt.
    """
    # ISO-Wochentagsnummer für eines von zwei Referenzdaten ermitteln
    # Referenzdatum 1 ist der erste Tag des Jahres
    # Referenzdatum 2 ist ein beliebiges Datum NACH einem Schalttag
    refdate1 = lambda y: date(y, 1, 1).isoweekday()
    refdate2 = lambda y: date(y, 5, 1).isoweekday()
    fullYears = []
    partialYearsA = []  # Bis Schalttag
    partialYearsB = []  # Ab Schalttag
    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='Das Jahr, für das ') 
    args = parser.parse_args()

    print(getReuseYears(args.year))

Check out similar posts by category: Algorithms