Open Source im professionellen Einsatz
Linux-Magazin 12/2007

Perl-Skript prüft Rechentrick

Datumsarithmetik

Gehirnakrobaten rechnen gerne vor Publikum, auf welchen Wochentag ein zugerufenes Datum fällt. Den Trick kann auch Otto Normalrechner mit etwas Übung lernen. Ob er zuverlässig funktioniert, prüft das im Snapshot vorgestellte Skript mit aktueller Perl-Technologie.

771

Neulich las ich auf dem Weg zur Arbeit im Zug das Buch "Mind Performance Hacks" [2] und stieß auf den Hack Nummer 43, der zeigt, wie jedermann mit etwas Übung den Wochentag zu jedem beliebigen Datum im Kopf ausrechnen kann. Das Verfahren geht zurück auf Lewis Carroll, den Autor des Romans "Alice im Wunderland".

Vier Werte sind der Reihe nach zu berechnen: der Jahreswert, der Monatswert, der Tageswert und ein vierter Wert zur Anpassung. Man addiert diese Werte anschließend, bestimmt den Restwert nach einer Division durch 7 und erhält verblüffenderweise den gewünschten Wochentag als Zahl zwischen 0 (Sonntag) und 6 (Samstag).

Als Beispiel dient der Entstehungstag dieses Textes (4.10.2007). Der Jahreswert bestimmt sich aus der folgenden Formel:

(YY + (YY div 4)) mod 7

Dabei ist »YY« die zweistellige Jahreszahl, also 07 für 2007. Der »div«-Operator führt eine Division ohne Restwert aus, 7 div 4 gleich 1, da 7 geteilt durch 4 den Wert 1 ergibt und der Rest 3 wegfällt. Das Ergebnis wird jetzt noch modulo 7 genommen: 1 modulo 7 ergibt 1. Der Jahreswert ist also 1.

Durch die Jahrhunderte

Zum Monatswert: Der ermittelt sich aus Tabelle 1, die auch Nicht-Gehirnakrobaten mit Hilfe einiger später zu erläuternder mnemotechnischer Tricks im Kopf behalten können. Der Oktober hat laut Tabelle den Monatswert 0. Der Tageswert ist einfach der fortlaufend nummerierte Tag des Monats, für den 4. Oktober also der Wert 4.

Tabelle 1:
Monatswerte

Wert

Monat

Januar

3

Februar

3

März

6

April

1

Mai

4

Juni

6

Juli

2

August

5

September

Oktober

3

November

5

Dezember

Der vierte Wert für die Kopfrechnung ergibt sich aus Tabelle 2, die für Jahreszahlen im 21. Jahrhundert (2000 bis 2099) den Wert 6 angibt. Diese Tabelle braucht man sich nicht ganz zu merken, es genügt, die Werte für 2000 (also 6) und 1900 (0) im Kopf zu behalten. In Schaltjahren wäre vom gefundenen Wert noch 1 abzuziehen, wenn das gesuchte Datum im Januar oder Februar liegt. Da aber 2007 kein Schaltjahr ist, entfällt das hier glücklicherweise.

Tabelle 2:
Jahreswerte

Wert

Jahr

1700

4

1800

2

1900

2000

6

2100

4

2200

2

2300

Die vier gefundenen Werte sind also 1 (Jahreswert), 0 (Monatswert), 4 (Tageswert) und 6 (Anpassung). Die Summe ist 11, und 11 modulo 7 ergibt den Wert 4. Ein Blick in Tabelle 3 mit den Wochentagen offenbart, dass - Trommelwirbel - der 4.10.2007 auf einen Donnerstag fällt, und das ist tatsächlich richtig!

Tabelle 3:
Wochentagswerte

Wert

Wochentag

Sonntag

1

Montag

2

Dienstag

3

Mittwoch

4

Donnerstag

5

Freitag

6

Samstag

Probe aufs Exempel

Jetzt stellt sich natürlich die Frage, ob diese Berechnung auch tatsächlich für jedes beliebige Datum zum richtigen Wochentag führt. Das Skript in Listing 1 schraubt sich daher zur Probe vom 1.1.1700 an durch sämtliche Tage der Neuzeit, über die Entdeckung Amerikas und den Goldrausch, durch den ersten und den zweiten Weltkrieg bis in die heutige Zeit und auch noch weiter in die Zukunft, bis zum Raumschiff Enterprise in der nächsten Generation mit Jean-Luc Picard auf dem Kommandosessel im 24. Jahrhundert.

Listing 1:
»mindcal«

01 #!/usr/bin/perl
02 use strict;
03 use warnings;
04 use Test::More qw(no_plan);
05 use DateTime;
06
07 my @MONTH = qw(0 3 3 6 1 4 6 2 5 0 3 5);
08 my %ADJ   = qw(1700 4 1800 2 1900 0 2000 6
09                2100 4 2200 2 2300 0);
10
11 my $dt = DateTime->new(
12     year  => 1700,
13     month => 1,
14     day   => 1,
15 );
16
17 while(1) {
18   my $calc = wday_mindcal(
19          $dt->year, $dt->month, $dt->day);
20
21   is($calc, $dt->wday() % 7, "$dt");
22
23   $dt->add(days => 1);
24
25   last if $dt->year() > 2399;
26 }
27
28 ###########################################
29 sub wday_mindcal {
30 ###########################################
31     my($year, $month, $day) = @_;
32
33     use integer;
34
35     my $year2 = $year % 100;
36     my $cent  = $year / 100;
37     my $y     = ($year2 + ($year2 / 4)) % 7;
38
39     my $m     = $MONTH[$month-1];
40     my $d     = $day;
41
42     my $adj   = $ADJ{$cent * 100};
43
44     $adj-- if leap_year($year) and
45               $month <= 2;
46
47     return( ($y+$m+$d+$adj) % 7 );
48 }
49
50 ###########################################
51 sub leap_year {
52 ###########################################
53     my($year) = @_;
54
55     return 0 if $year % 4;
56     return 1 if $year % 100;
57     return 0 if $year % 400;
58     return 1;
59 }

Das Skript definiert in Zeile 29 die Funktion »wday_mindcal()«, die das vierstellige Jahr, den Monat und den Tag eines Datums entgegennimmt, nach den oben erläuterten Regeln die Jahres- beziehungsweise Monats- und Tages-Anpassungszahlen errechnet und die notwendigen Operationen ausführt, um daraus die Wochentagsnummer zu ermitteln.

Die Monatszahlen stehen im Array »@MONTH«, von Januar bis Dezember. Da Arrays nicht von 1, sondern von 0 an durchnummeriert sind, zieht das Skript vom gesuchten Monat erst 1 ab, bevor es unter dem so erhaltenen Index in den Array hineingreift. Die Anpassungszahlen für die verschiedenen Jahrhunderte stehen im Hash »%ADJ«. Die Zeilen 8 und 9 füllen den Hash, in dem sie ihm eine Liste zuweisen, die abwechselnd Jahrhundertzahlen und die jeweils zugehörigen Anpassungswerte enthält.

Die Vergleichswerte errechnet das CPAN-Modul »DateTime«, das tageweise vom 1.1.1700 bis zum 31.12.2399 hochzählt und bei jedem Schleifendurchgang Tag, Monat und Jahr liefert. Die Endlosschleife ab Zeile 17 ist nicht wirklich endlos, denn Zeile 25 bricht den Reigen ab, falls das Jahr des aktuellen Datums 2399 überschreitet. Ist die Schleife noch nicht am Ende, addiert die Methode »add()« des »DateTime«-Objekts unten einen Tag zum aktuellen Datum - und schon nimmt eine weitere Schleifenrunde ihren Anfang.

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Datumsarithmetik

    Perls Git-Repository enthält alle Commits, seit Larry Wall Perl 1987 aus der Taufe hob. Das Statistik-Tool R gewinnt aus den historischen Daten überraschende Informationen und stellt sie grafisch dar.

  • Perlsche Zeitmaschine

    Datumsberechnungen haben ihre Tücken, denn Kalenderregeln sind historisch und sogar von politischen Entscheidungen beeinflusst. Perls »DateTime«-Modul kennt alle Tricks.

  • Bitte anklopfen

    Unsichtbare Hintertüren in standfesten Außenmauern verbinden Sicherheit und Freiheit beim Kommunizieren: Wer die Tür und den Code nicht kennt, bemerkt nicht mal, dass es sie gibt.

  • Kontrolle ist besser

    Eine Testsuite hilft Fehler zu korrigieren und Teile des Systems umzuschreiben, ohne die bestehende Codebasis dabei zu ruinieren. Die unbestechlichen Kontrolleure heißen etwa Test::More oder Test::Deep.

  • Perl-Snapshot Linux-Magazin 04/2013

    USB-Sticks und SD-Karten verlieren schnell an Wert. Verteilt ein Perlskript Daten auf mehrere solcher Speicher, taugen Flashspeicher trotz begrenzter Kapazität als schnelle und zudem stoßsichere Backupmedien.

comments powered by Disqus

Ausgabe 06/2017

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.