Feedback

C# - Zellers Kongruenz

Veröffentlicht von am 16.07.2014
(0 Bewertungen)
Berechnet den Wochentag zu einem gegebenen Datum im Gregorianischen oder Julianischen Kalender. Kann diesen dann per String ausgeben.

Um beispielsweise einen Wochentag im Gregorianischen Kalender zu berechnen führt man folgenden Befehl aus:

Console.WriteLine(ZellersCongruenceToString(ZellersCongruenceGregorian(dd,mm,yyyy)));

        public static int ZellersCongruenceGregorian(double dd, double mm, double yyyy)
        {
            int h = 0;
            double q = dd;
            double m = mm;
            double y = yyyy;
            double J = 0;
            double K = 0;

            AdjustMonth(ref m, ref y);            
            GetJK(y, ref J, ref K);

            //Die Formel selbst kann in einer einzelnen Zeile implementiert werden.
            h = (int)((q + Math.Floor(((m + 1) * 26) / 10) + K + Math.Floor(K / 4) + Math.Floor(J / 4) - 2 * J) % 7);

            return h < 0 ? h + 7 : h;
        }

        //Erhält Tag, Monat und Jahr als Argument
        public static int ZellersCongruenceJulian(double dd, double mm, double yyyy)
        {
            int h = 0;
            double q = dd;
            double m = mm;
            double y = yyyy;
            double J = 0;
            double K = 0;

            AdjustMonth(ref m, ref y);
            GetJK(y, ref J, ref K);            

            //Wir müssen die Formel nur minimal ändern um einen Wochentag im Julianischen Kalender zu berechnen.
            h = (int)((q + Math.Floor(((m + 1) * 26) / 10) + K + Math.Floor(K / 4) + 5 - J) % 7);

            return h < 0 ? h + 7 : h;
        }

        private static void GetJK(double y, ref double J, ref double K)
        {
            J = Math.Floor(y / 100);
            K = y - J * 100;
        }

        private static void AdjustMonth(ref double m, ref double y)
        {
            #region Hier werden Monat und Jahr angepasst für den Fall, dass es sich um den Januar oder Februar handelt
            switch ((int)m)
            {
                case 1:
                    m = 13;
                    y--;
                    break;
                case 2:
                    m = 14;
                    y--;
                    break;
            }
            #endregion
        }

        public static string ZellersCongruenceToString(int h)
        {
            //Hier entscheiden wir welchem Wochentag der gegebene Wert entspricht
            //und liefern den entsprechenden Wochentag als String aus.
            switch (h)
            {
                case 0:
                    return "Saturday";
                case 1:
                    return "Sunday";
                case 2:
                    return "Monday";
                case 3:
                    return "Tuesday";
                case 4:
                    return "Wednesday";
                case 5:
                    return "Thursday";
                case 6:
                    return "Friday";
                default:
                    return "Could not convert given number to a String.";
            }
        }
Abgelegt unter Kalender, Wochentag, ZellersKongruenz.

5 Kommentare zum Snippet

Koopakiller schrieb am 16.07.2014:
Ein paar Dinge zu deinem Code:
1.) Was soll die Neuzuweisung von y mit yyyy, obwohl du yyyy nie wieder verwendest?
2.) Ein break; hinter einem return; ist unerreichbarer Code.
3.) Eine Zahl in einen String zu wandeln und dann auf zu spalten und wieder zurück zu wandeln frist nur unnötig Leistung. Einfach Jahr/100 für das Jahrhundert und Jahr-Jahrhundert für die anderen beiden Zahlen.
4.) Warum ist m ein double, obwohl eigentlich ein int ist?
5.) Ein DateTime entgegen zu nehmen und einen Wert der DayOfWeek-Enumeration zurück zu geben wäre .NET koformer und allgemein anwendbarer.
6.) Du hast sehr viel doppelten Code, den man in eine gemeinsame Methode auslagern könnte.
7.) Ich habe deinen Code nur angetestet, aber für einige im greg. Kalender gültige Daten erhielt ich keine Wochentage sondenr die Fehlermeldung.
8.) Ich beschäftigte mich mit dieser Art von Zeitrechnung noch nicht wirklich, vermute aber das .NET auch irgend etwas auf Lager hat. Siehe Arbeiten mit Kalendern [http://msdn.microsoft.com/de-de/library/82aak18x.aspx]
Käsebrot schrieb am 17.07.2014:
Danke für die Ratschläge.Für welche Daten bekommst du eine Fehlermeldung?
Koopakiller schrieb am 17.07.2014:
Mögliche Daten:
Gregorianische Kalender:
01.03.2015 bis 06.03.2015, 01.04.2015 un im selben Bereich in den Folgejahren.
Julianischer Kalender:
Selber Bereich im Jahr, allerdings erst ab 2100. Aber auch am 02.03.2002 und am 01.03.2003.
Ich erhielt bei den Daten jeweils negative Werte von den Funktionen zurück.
Die Fehlerhaften Ergebnisse scheinen nach einem Muster zu verlaufen.

Du kannst auch nochmal selbst nach Fehlerhaften Datumsangaben suchen:
DateTime date = DateTime.Now;
int x;
while (ZellersCongruenceToString(x = ZellersCongruenceGregorian(date.Day, date.Month, date.Year)) != "Could not convert given number to a String.")
date = date.AddDays(1);//bzw. -1 um in die Vergangenheit zu gehen
if (Debugger.IsAttached) Debugger.Break();
Käsebrot schrieb am 18.07.2014:
So nochmal vielen Dank für deine Ratschläge.
1. Ich verstehe nicht ganz was du mit der Zuweisung y und yyyy meinst. Ich weise doch nur den Methodenparameter einer Variablen im Scope der Methode zu.
2. Habe die breaks entfernt. Wusste nicht, dass eine switch() auch ohne breaks auskommt ^^
3. Habe die Berechnung der Werte J und K umgeschrieben ohne, dass sie zu strings geparst werden müssen. Obwohl ich jetzt nicht finde, dass es so ein Ressourcenfresser ist ;) Aber der Ansatz ist eindeutig sauberer.
4. Die Variablen sind doubles weil in der Formel selbst abgerundet wird und ich mir um das Verhalten von Integer-Werten bei division nicht sicher war. Könnte ich das ohne Bedenken ändern?
5.Habe doppelten Code in zwei Methoden ausgelagert die Hauptmethoden sehen jetzt schlanker aus.
6. Habe die Sache mit negativen werten behoben. In diesen Fällen muss einfach h + 7 gerechnet werden, siehe 'return'.
7. Mir ist schon klar, dass man das alles mit einer einzelnen .NET-Zeile ausgeben könnte. Hier ging es mir aber hauptsächlich um die Implementierung von "Zellers Kongruenz".

Nochmal danke für deine Tipps, bin immer offen für konstruktive Kritik!
Koopakiller schrieb am 18.07.2014:
1.) Du kannst direkt den Methodenparameter yyyy verwenden. Parameter sind wie ganz normale Variablen, halt nur mit Startwert. Momentan weißt du der Variablen y den Wert von yyyy zu. Das würde Sinn machen, bräuchtest du später nochmal den Ausgangswert von yyyy. Nach der Zuweisung wird yyyy aber nie wieder verwendet. (Parameter sind in C# veränderlich, der Methodenaufrufer spürt die Änderung nur bei out und ref Parametern.)
2.) case Anweisungen können mit einem break, return oder goto enden.
3.) Resourcenfresser sind Strings immer. Nicht unbedingt vom Arbeitsspeicher her, aber von der CPU-Leistung schon. Es mag nicht wirklich etwas ausmachen, wenn man es nur einmal macht. Nur gewöhnt man sich dadurch auch unschöne Methoden an. Auf die Masse gesehen sind Strings einfach Langsam. Ein Testcode dafür:
Stopwatch sw = new Stopwatch();
int a, b;

sw.Start();
for (int x = 0; x < 10000; ++x)
for (int i = 1000; i < 10000; ++i)
{
var str = i.ToString();
a = int.Parse(str.Substring(0, 2));
b = int.Parse(str.Substring(2));
}
sw.Stop();
Console.WriteLine("Per String: {0}", sw.ElapsedMilliseconds);

sw.Reset();

sw.Start();
for (int x = 0; x < 10000; ++x)
for (int i = 1000; i < 10000; ++i)
{
a = i / 100;
b = i - a;
}
sw.Stop();
Console.WriteLine("Per numerischen Operationen: {0}", sw.ElapsedMilliseconds);

Console.ReadKey();

Die Ergebnisse für die Strings waren um den Faktor 85 langsamer.
4.) Ja, 3/2=1. Der Nachkommateil wird einfach abgeschnitten.
5.) Ist gleich viel .NET konformer :)
6.) Gut, so etwas dachte ich schon, nur konnte ich mir dabei nicht wirklich sicher sein.
7.) Das kenne ich, auch ich habe einige Snippets, die .NET Teile ersetzen. Auch ist dein Code wahrscheinlich schneller als das Initialisieren der Kalender usw. Trotzdem eine Anmerkung wert, zumindest für die, die sich intensiver in die Thematik hinein versetzen wollen.

Ich mags nicht Snippets schlecht zu bewerten ohne wenigstens auf die Fehler hin zu weisen. Darum Bitte schön :)
 

Logge dich ein, um hier zu kommentieren!