Laden...

Und nochmal eine Feiertagsberechnung

Erstellt von Lars Schmitt vor 16 Jahren Letzter Beitrag vor 7 Jahren 30.098 Views
Lars Schmitt Themenstarter:in
2.223 Beiträge seit 2005
vor 16 Jahren
Und nochmal eine Feiertagsberechnung

Beschreibung:

Jedoch kann bei dieser Feiertagsberechnung das jeweilige Land mit berücksichtigt werden

using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;



namespace BlackDragon.FeierTage {

    /// <summary>
    /// Und nochmal eine Feiertagsberechnung 
    /// </summary>
    public class FeierTage {
        private List<FeierTag> feiertage = new List<FeierTag>();

        public FeierTage() {
            Initial();
        }


        /// <summary>
        /// Den möglichen Feiertags anhand des Datums und des jeweiligen Bundes Landes ermitteln
        /// </summary>
        /// <param name="datum">Das Datum, daß zur ermittlung herrangezogen werden soll</param>
        /// <param name="land">Das jeweilige Land</param>
        /// <returns>Der jeweilige Feiertag als Text</returns>
        public String GetFeiertag(DateTime datum, Land land) {

            // Liste der Feiertage durchgehen
            foreach (FeierTag f in feiertage) {
                if (datum.ToShortDateString().Equals(f.GetDatum(GetOstersonntag(datum.Year)).ToShortDateString())) {
                    // Prüfen ob das Land enthalten ist
                    foreach (Land l in f.Länder) {
                        if (land == l) {
                            return f.Feiertag;
                        }
                    }
                }
            }
            return "";
        }

        /// <summary>
        /// Einfache Abfrage ob das übergeben Datum in dem jeweiligen Land ein Feiertag ist
        /// </summary>
        /// <param name="datum">Das Datum, daß zur ermittlung herrangezogen werden soll</param>
        /// <param name="land">Das jeweilige Land</param>
        /// <returns>Wahr oder Falsch</returns>
        public Boolean IsFeiertag(DateTime date, Land land) {
            return (GetFeiertag(date, land).Length > 0);
        }

        /// <summary>
        /// Errechnet das Datum des Ostersonntags aus dem übergebenen Jahr
        /// </summary>
        /// <param name="int">Das Jahr in YYYY Schreibweise</param>
        /// <returns>Das errechnete Datum des Ostersonnsags in dem angegebene Jahr</returns>
        private DateTime GetOstersonntag(int jahr) {
            int c;
            int i;
            int j;
            int k;
            int l;
            int n;
            int OsterTag;
            int OsterMonat;
           
            c = jahr / 100;
            n = jahr - 19 * ((int)(jahr / 19));
            k = (c - 17) / 25;
            i = c - c / 4 - ((int)(c - k) / 3) + 19 * n + 15;
            i = i - 30 * ((int)(i / 30));
            i = i - (i / 28) * ((int)(1 - (i / 28)) * ((int)(29 / (i + 1))) * ((int)(21 - n) / 11));
            j = jahr + ((int)jahr / 4) + i + 2 - c + ((int)c / 4);
            j = j - 7 * ((int)(j / 7));
            l = i - j;

            OsterMonat = 3 + ((int)(l + 40) / 44);
            OsterTag = l + 28 - 31 * ((int)OsterMonat / 4);

            return Convert.ToDateTime(OsterTag.ToString() + "." + OsterMonat + "." + jahr);
        }


        /// <summary>
        /// Die einzelnen möglichen Feiertage 
        /// kan natürlich noch erweitert werden
        /// </summary>
        private void Initial() {
            Land[] alle = new Land[] { Land.Baden_Würtenberg, Land.Bayern, Land.Berlin, Land.Brandenburg, Land.Bremen, Land.Hamburg, Land.Hessen, Land.Mecklenburg_Vorpommern, Land.Niedersachsen, Land.Nordrhein_Westfalen, Land.Rheinland_Pfalz, Land.Saarland, Land.Sachsen, Land.Sachsen_Anhalt, Land.Schleswig_Holstein, Land.Thüringen };

            feiertage.Add(new FeierTag("Neujahr","01.01", FeiertagsArt.Fester_Feiertag, alle));
            feiertage.Add(new FeierTag("Heiligen Drei Könige", "06.01", FeiertagsArt.Fester_Feiertag, new Land[] { Land.Baden_Würtenberg, Land.Bayern, Land.Sachsen_Anhalt }));
            feiertage.Add(new FeierTag("Karfreitag", -2, FeiertagsArt.Bewegliche_Feiertag, alle));
            feiertage.Add(new FeierTag("Ostersonntag", 0, FeiertagsArt.Bewegliche_Feiertag, alle));
            feiertage.Add(new FeierTag("Ostermontag", 1, FeiertagsArt.Bewegliche_Feiertag, alle));
            feiertage.Add(new FeierTag("Tag der Arbeit", "01.05", FeiertagsArt.Fester_Feiertag, alle));
            feiertage.Add(new FeierTag("Christi Himmelfahrt", 39, FeiertagsArt.Bewegliche_Feiertag , alle));
            feiertage.Add(new FeierTag("Pfingstsonntag", 49, FeiertagsArt.Bewegliche_Feiertag, alle));
            feiertage.Add(new FeierTag("Pfingstmontag", 50, FeiertagsArt.Bewegliche_Feiertag, alle));
            feiertage.Add(new FeierTag("Fronleichnam", 60, FeiertagsArt.Bewegliche_Feiertag, new Land[] { Land.Baden_Würtenberg, Land.Bayern, Land.Hessen, Land.Nordrhein_Westfalen, Land.Rheinland_Pfalz,Land.Saarland}));
            feiertage.Add(new FeierTag("Mariä Himmelfahrt", "15.08", FeiertagsArt.Fester_Feiertag, new Land[] { Land.Saarland }));
            feiertage.Add(new FeierTag("Tag der dt. Einheit", "03.10", FeiertagsArt.Fester_Feiertag, alle ));
            feiertage.Add(new FeierTag("Allerheiligen", "01.11", FeiertagsArt.Fester_Feiertag, new Land[] { Land.Baden_Würtenberg,Land.Bayern,Land.Nordrhein_Westfalen,Land.Rheinland_Pfalz,Land.Saarland }));
            feiertage.Add(new FeierTag("1. Weinachtstag", "25.12", FeiertagsArt.Fester_Feiertag, alle));
            feiertage.Add(new FeierTag("2. Weinachtstag", "26.12", FeiertagsArt.Fester_Feiertag, alle));

        }
    }

    /// <summary>
    /// Die eigentliche Klasse der einzelnen Feiertage
    /// </summary>
    internal class FeierTag {
        private FeiertagsArt art;
        private string feiertag;
        private DateTime datum;
        private string testDatum;
        private int tageHinzu;
        private Land[] länder;

        public String Feiertag {
            get {
                return this.feiertag;
            }
        }
        public DateTime Datum {
            get {
                return this.datum;
            }
        }
        public Land[] Länder {
            get {
                return länder;
            }
        }

        internal FeierTag(String feiertag, String testDatum, FeiertagsArt art, Land[] länder) {
            this.feiertag = feiertag;
            this.testDatum = testDatum;
            this.tageHinzu = 0;
            this.art = art;
            this.länder = länder;
        }
        internal FeierTag(String feiertag, int tageHinzu, FeiertagsArt art, Land[] länder) {
            this.feiertag = feiertag;
            this.tageHinzu = tageHinzu;
            this.art = art;
            this.länder = länder;
        }
        
        public DateTime GetDatum(DateTime osterSonntag) {
            if (this.art != FeiertagsArt.Fester_Feiertag) {
                datum = osterSonntag.AddDays(this.tageHinzu);
            } else {
                datum = DateTime.Parse(testDatum + "." + osterSonntag.Year);
            }

            return DateTime.Parse(datum.Day.ToString() + "." + datum.Month.ToString() +"."+ osterSonntag.Year);
        }
    }

    public enum Land {
        Baden_Würtenberg,
        Bayern,
        Berlin,
        Brandenburg,
        Bremen,
        Hamburg,
        Hessen,
        Mecklenburg_Vorpommern,
        Niedersachsen,
        Nordrhein_Westfalen,
        Rheinland_Pfalz,
        Saarland,
        Sachsen,
        Sachsen_Anhalt,
        Schleswig_Holstein,
        Thüringen
    }
    public enum FeiertagsArt {
        Fester_Feiertag,
        Bewegliche_Feiertag
    }
}

Schlagwörter: Feiertage, Osterformel

Quelle: .NET-Snippets

J
3.331 Beiträge seit 2006
vor 16 Jahren

Hallo,

das ist eine schöne Zusammenstellung. Ich möchte sie gerne erweitern:
* bundesweite Feiertage vereinfacht bestimmen
* als Singleton-Klasse
* mit statischen Abfragen
Wenn ich sowieso schon dabei bin, würde ich die Bundesländer nach den Nummern des Statistischen Bundesamtes indizieren.

Gibt es Interesse daran, diese Änderungen hier bereitzustellen? Jürgen

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo juetho,

für mich klingt das nach zwar nicht nach Erweiterungen, sondern "nur" nach Änderungen, aber es spricht auch nichts dagegen, deinen "Konkurrenzentwurf" hier zu veröffentlichen.

herbivore

B
114 Beiträge seit 2007
vor 16 Jahren

Hallo,

also is ja an sich nur Rechnerei, weil ja wohl durch Kirche u. Co alles irgendwie von Ostern ausgeht. Aber sehr interessant find ich ja wie du überhaupt auf das Datum vom Ostersonntag kommst. Is doch eigentlich vom Mond abhängig oder gibts da ne allgemeine Regel für?
Bräuchte das auch für nen Projekt.
Hast da vlt ma nen Algorithmus im Wortlaut bzw nich so böse hingecodet?
Konnte bisher rausfinden wieviele Tage von Ostern die anderen Feiertage entfernt sind, aber halt nich Ostern an sich.

Gruß
Black

664 Beiträge seit 2005
vor 16 Jahren

Original von blackman1983
Is doch eigentlich vom Mond abhängig oder gibts da ne allgemeine Regel für?

Osterformel von C. F. Gauss -> http://www.nabkal.de/gauss2.html

J
3.331 Beiträge seit 2006
vor 16 Jahren

Überlegungen für Änderungen

Wie ich schon schrieb, wollte ich Blackcoin's Lösung ergänzen. Die folgenden Überlegungen haben mich dabei zu entsprechenden Änderungen geführt.

Art der Feiertage
Das habe ich zu einer bool-Variable "beweglich" vereinfacht. Andere Varianten gibt es schließlich nicht. Außerdem habe ich zur Vereinfachung der Prüfungen die bool-Variable "bundesweit" hinzufügt.

Für die Bezeichnung eines Feiertags (bei Blackcoin als "feiertag" bzw. "GetFeiertag" bezeichnet) benutze ich "name" bzw. "Name" oder "GetName".

Singleton-Klasse
Für die Feiertagsberechnung gibt es "inhaltlich" innerhalb einer Anwendung (eigentlich: in einem lokalen Netzwerk) zwangsläufig nur eine Variante. Es bietet sich deshalb an, den Aufruf über eine Singleton-Klasse zu steuern. Beim erstmaligen Aufruf wird die interne Klasse erstellt. Alle Methoden für Prüfungen stehen als "Schnittstelle" in der Singleton-Klasse.

public sealed class Feiertagsprüfung
{
	private static Feiertagsprüfung
		instance = new Feiertagsprüfung();
	private Feiertage interna;
		
	public static Feiertagsprüfung Instance {
		get {	
			if (instance == null)
				instance = new Feiertagsprüfung();
			return instance;			
		}
	}
		
	private Feiertagsprüfung()
	{
		interna = new Feiertage();
	}
}

Prüfmethoden
Ausgehend von Blackcoin's Konzept habe ich die folgenden Verfahren übernommen und für bundesweite Feiertage ergänzt sowie in die Singleton-Klasse eingebaut.

/// Einfache Abfrage, ob das übergebene Datum überhaupt ein Feiertag ist
public static bool IsFeiertag(DateTime datum, bool bundesweit) { }
/// Einfache Abfrage, ob das Datum in dem jeweiligen Land ein Feiertag ist
public static bool IsFeiertag(DateTime datum, Land land) { }
/// Ergänzende Prüfung, ob das übergebene Datum
/// in (irgend-)einem Land teilweise ein Feiertag ist
public static bool IsTeilweiseFeiertag(DateTime datum) {  }
/// Die Bezeichnung eines bundesweiten Feiertags anhand des Datums ermitteln
public static string GetName(DateTime datum) { }
/// Die Bezeichnung eines möglichen Feiertags anhand des Datums 
/// und des jeweiligen Bundeslandes ermitteln
public static string GetName(DateTime datum, Land land) {
	return Instance.interna.GetName(datum, land);
}

Diese Methoden leiten (wie im letzten Beispiel) die Parameter an die entsprechenden Methoden in der internen Klasse weiter. Beispielhaft wird die letzte Prüfung so ausgeführt:

public string GetName(DateTime datum, Land land) {
	string Result = String.Empty;
	Feiertag f = GetFeiertag(datum);
	if (f != null) {
		if (f.Bundesweit || Array.IndexOf(f.Länder, land) >= 0)
			Result = f.Name;
	}
	return Result;
}

Interne Liste als List<T> oder Dictionary<string, T>
Blackcoin's Verfahren, bei dem "Datümer" per String verglichen werden und die Strings in jedem Schritt der foreach-Schleife neu erstellt werden, gefiel mir nicht besonders. Das Verfahren kann hingenommen werden, weil es sich nur um wenige Einträge handelt und die beweglichen Feiertage für jedes Jahr getrennt berechnet werden müssen.

Aber genau wegen der Berechnung pro Jahr scheint mir die getrennte Speicherung sinnvoll zu sein. Dann können die Listen auch konsequent als Nachschlagetabellen genutzt werden, auch wenn ihre Erstellung ein klein wenig umständlicher aussieht.

//  feste Feiertage mit Suche nach "01.01."
private Dictionary<string, Feiertag> festeFeiertage 
	= new Dictionary<string, Feiertag> ();
festeFeiertage.Add("01.01.", new Feiertag("Neujahr", "01.01."));

//  bewegliche Feiertage mit Suche nach Jahr und Datum
private Dictionary<int, Dictionary<DateTime, Feiertag>> beweglicheFeiertage
	= new Dictionary<int, Dictionary<DateTime, Feiertag>> ();
DateTime ostern = GetOstersonntag(jahr);
//  daraus alle "abhängigen" Feiertage ableiten und registrieren
aktuelleFeiertage.Add(ostern.AddDays(-2), 
	new Feiertag("Karfreitag", ostern, -2));

Die eigentliche Prüfung wird durch den Dictionary-Aufbau erheblich vereinfacht und jeweils in der folgenden Methode erledigt:

private Feiertag GetFeiertag(DateTime datum)
{
	Feiertag Result = null;
	//  erster Versuch: fester Feiertag?
	string ttmm = datum.ToString("dd.MM.");
	if (! festeFeiertage.TryGetValue(ttmm, out Result)) {
		//  zweiter Versuch: 
		//  bewegliche Feiertage des betreffenden Jahres feststellen
		Dictionary<DateTime, Feiertag> beweglich = null;
		int jahr = datum.Year;
		if (! beweglicheFeiertage.TryGetValue(jahr, out beweglich)) {
			//  noch nicht vorhanden, 
			//  also hinzufügen und verwenden
			InitBeweglicheFeiertage(jahr);
			beweglich = beweglicheFeiertage[jahr];
		}
		beweglich.TryGetValue(datum, out Result);
	}
	return Result;
}

Besondere Feiertage
Fronleichnam und Mariä Himmelfahrt sind in manchen Bundesländern nur teilweise Feiertag. Dafür habe ich in der Enumeration der Bundesländer an Position Null die Angabe "teilweise" hinzugefügt. Ein solcher Feiertag muss also bei Bedarf genauer geprüft werden.
Diese Länder können in den Methoden InitFesteFeiertage() und InitBeweglicheFeiertage() anstatt "teilweise" eingetragen werden.

Der Buß- und Bettag ist nur noch in Sachsen Feiertag und errechnet sich auf besondere Weise; dafür gibt es also analog zu GetOstersonntag() eine eigene Berechnungsmethode.

Wenn der 1. Mai gleichzeitig Tag der Arbeit und Christi Himmelfahrt ist, wird nur der feste Feiertag angezeigt, weil dies in der o.g. Prüfung Vorrang hat.

Außerdem entspricht die Reihenfolge der Liste der Bundesländer jetzt dem Gemeindeverzeichnis des Stat. Bundesamtes.

Die vollständige, aktuelle Version habe ich als Anlage beigefügt. (Die Anlage enthält außerdem eine Anwendung, die alle Methoden für alle Bundesländer in den Jahren 2006, 2007, 2008 demonstriert.)

Jürgen Thomas (juetho)

Urheber
Sowohl der Hauptgedanke mit den beiden Klassen "Einzelner Feiertag" und "Alle Feiertage" sowie den Prüfungsvorgängen und den Klassenelementen als auch die unabdingbar notwendige, wichtige und saubere Berechnung des Ostersonntags stammen von blackcoin. Die Ergänzungen mit Dictionary und Singleton-Klasse sowie dadurch einfacheren Prüfmethoden stammen von juetho.

Unabhängig davon gelten natürlich die Nutzungsbedingungen von myCSharp.de.

Nachtrag: (2016) Die Anlage Feiertage.zip wurde ersetzt durch die korrigierte Version im folgenden Beitrag.

J
3.331 Beiträge seit 2006
vor 7 Jahren

Wichtiger Hinweis: Die vorstehende Berechnung genügt nicht: Zwei Ausnahmeregeln wurden bei den obigen Lösungen nicht in die Gaußsche Formel eingearbeitet. Der Ordnung halber folgt hier die berichtigte Berechnung:


	/// <summary>
	/// Errechnet das Datum des Ostersonntags aus dem übergebenen Jahr
	/// </summary>
	/// <param name="jahr">Das Jahr YYYY als integer-Wert</param>
	/// <returns>Das errechnete Datum des Ostersonntags in dem angegebenen Jahr</returns>
	private DateTime GetOstersonntag(int jahr) 
	{
      int x = jahr;   // das Jahr
      int k;          // die Säkularzahl
      int m;          // die säkulare Mondschaltung
      int s;          // die säkulare Sonnenschaltung
      int a;          // der Mondparameter
      int d;          // der Keim für den ersten Vollmond im Frühling
      int r;          // die kalendarische Korrekturgröße
      int og;         // die Ostergrenze
      int sz;         // der ersten Sonntag im März
      int oe;         // die Entfernung des Ostersonntags von der Ostergrenze
      int os;         // das Datum des Ostersonntags als Märzdatum (32.März = 1.April)
      int OsterTag;
      int OsterMonat;
      
      k = x / 100;
      m = 15 + (3 * k + 3) / 4 - (8 * k + 13) / 25;
      s = 2 - (3 * k + 3) / 4;
      a = x % 19;
      d = (19 * a + m) % 30;
      r = (d + a / 11) / 29;
      og = 21 + d - r;
      sz = 7 - (x + x / 4 + s) % 7;
      oe = 7 - (og - sz) % 7;
      os = og + oe;
      
      OsterMonat = 2 + (int)(os + 30) / 31;
      OsterTag = os - 31 * ((int)OsterMonat / 4);
    
      return Convert.ToDateTime(OsterTag.ToString() + "." + OsterMonat + "." + jahr);
	}

Auf diesen Fehler wurde ich kürzlich durch eine Änderung bei der Wikibooks-Algorithmensammlung aufmerksam gemacht.

Die hier stehende Version von Feiertage.zip habe ich entsprechend ergänzt.

Quelle: Gaußsche Osterformel (mit Ergänzung)
Hinweis: Der letzte Beitrag bei Osterformel nach Carl Friedrich Gauß enthält die vollständige Lösung.

Nachtrag: Die Anlage Feiertage.zip wurde ersetzt durch die korrigierte Version in meinem nächsten Beitrag (27.09.2016).

S
1.047 Beiträge seit 2005
vor 7 Jahren

Hallo,

wollte noch anmerken das die Buß und Bettag Berechnung auch fehlerhaft ist.
Ist mir für 2000, 2006, 2017 und 2023 aufgefallen. Es ist jedes mal der 15. statt der 22.

Laut Buß- und Bettag ist der Feiertag so definiert das es der Mittwoch vor dem 23. November ist. D.h. der Code kann auch sehr stark vereinfacht werden.

Mit folgender Hilfsmethode


public static DateTime GetLastWeekday(this DateTime startDate, DayOfWeek targetDayOfWeek) {
    DayOfWeek startDayOfWeek =  startDate.DayOfWeek;
    if (startDayOfWeek == targetDayOfWeek) {
        return startDate;
    }
    int diff = 0;
    if (startDayOfWeek < targetDayOfWeek) {
        diff = targetDayOfWeek - startDayOfWeek - 7; 
    } else if (startDayOfWeek > targetDayOfWeek) {
        diff = targetDayOfWeek - startDayOfWeek; 
    }
    return startDate.AddDays(diff);
}

Lässt sich der Buß und Bettag dann so berechnen


DateTime GetBussUndBettag(int year) {
    return new DateTime(year, 11, 22).GetLastWeekday(DayOfWeek.Wednesday);
}

@juetho
bei GetOstersonntag, die letzte Zeile, da würde ich anstelle

return Convert.ToDateTime(OsterTag.ToString() + "." + OsterMonat + "." + jahr);

einfach den Konstruktor von DateTime verwenden

return new DateTime(jahr, OsterMonat, OsterTag);

Gruß
Sven

J
3.331 Beiträge seit 2006
vor 7 Jahren

Danke für die Hinweise. Ich habe deine (sheitman) Vorschläge eingearbeitet.

Bei der Gelegenheit habe ich die Anzeige der Anwendung etwas erweitert, sodass man Ostersonntag und Buß- und Bettag für ein "beliebiges" Jahr direkt prüfen kann. Damit kommen solche Fehler hoffentlich nicht mehr vor.

Ich wundere mich, dass ich den Buß- und Bettag nicht kontrolliert hatte. Ebenso wundere ich mich über die Convert-Anweisung beim Ostersonntag; solche Konstruktionen hatte ich eigentlich schon vor neun Jahren als schlechten Stil abgelehnt. Besser spät als nie korrigieren!

Die Version von Feiertage.zip habe ich entsprechend ergänzt.

1.040 Beiträge seit 2007
vor 7 Jahren

Grade mitbekommen, dass der Reformationstag 2017 in ganz Deutschland gefeiert werden soll, siehe auch Wikipedia.

L
34 Beiträge seit 2005
vor 7 Jahren

Zu diesem Thema gibt es auch ein C# Projekt auf Github. Die Feiertage werden auch Dynamisch berechnet und es werden derzeit 23 Länder unterstützt. Deutschland ist eines davon, die Bundesländer werden auch berücksichtigt

https://github.com/tinohager/Nager.Date


var publicHolidays = DateSystem.GetPublicHoliday("DE", 2017);
foreach (var publicHoliday in publicHolidays)
{
    Console.WriteLine(publicHoliday.Name);
}