Kalender wiederverwenden – der pythonische Weg
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:
python3 reuse-calendars.py 2015Das Skript verwendet einen Trial-and-Error-Ansatz, da der Suchraum für einen modernen Computer recht klein ist:
#!/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))