Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 6 Min.

Genau genommen

In vielen Branchen läuft die Planung hauptsächlich über die Kalenderwoche. Doch wie berechnet man sie richtig?
© dotnetpro
Die Kalenderwoche (KW) ist eine Woche, die mit dem Montag beginnt und mit dem Sonntag endet. Das Jahr hat insgesamt meist 52, nie weniger, aber manchmal auch 53 Kalenderwochen. Die nächsten Jahre mit 53 KW sind 2026, 2032, 2037, 2043, 2048 und 2054. Klingt kompliziert – und ist es irgendwie auch. Sehen wir uns die Definition an.Die erste Kalenderwoche eines Jahres ist die Woche, die mindestens vier Tage des neuen Jahres beinhaltet. Fällt also beispielsweise der 1. Januar auf einen Dienstag, beginnt die erste Kalenderwoche mit Montag, dem 31.12., da diese Woche sechs Tage des neuen Jahres enthält (Dienstag, Mittwoch, Donnerstag, Freitag, Samstag und Sonntag).Fällt der 1. Januar hingegen auf einen Freitag, dann beginnt die erste Kalenderwoche des neuen Jahres mit Montag, dem 4. Januar, da die Vorwoche nur drei Tage des neuen Jahres enthält (Freitag, Samstag, Sonntag). Geregelt ist das Ganze in der ISO 8601, gut aufbereitet ist es in der deutschen Wikipedia unter [1].Wichtig zu wissen ist außerdem, dass die Definitionen von Kalenderwochen je nach Kulturkreis unterschiedlich sind, beispielsweise gelten in Nordamerika und Australien andere Regeln. Insbesondere gilt dort der Sonntag als der erste Tag der Woche.Wie berechnet man die Kalenderwoche zu einem Datum mit .NET? Sucht man danach im Netz, findet man schnell ­eine Lösung über die .NET-Funktion GetWeekOfYear, die sich in der Klasse System.Globalization.CultureInfo.CurrentCulture.Calendar befindet und die oben erwähnte Vier-Tage-Regel (hier: CalendarWeekRule.FirstFourDayWeek) benötigt sowie die Info, dass hierzulande der Montag als der erste Tag der Woche gilt. Das sieht dann so aus:

Public Function Kalenderwoche( 
  ByVal d As Date) As Integer 

  Dim culture = System.Globalization.CultureInfo. 
    CurrentCulture 
  Return culture.Calendar.GetWeekOfYear( 
    d, System.Globalization. 
    CalendarWeekRule.FirstFourDayWeek, 
    DayOfWeek.Monday) 
End Function 
 
Doch halt: Wäre es nicht fatal, die Rechenergebnisse seines Programms von der auf dem Rechner eingestellten Culture abhängig zu machen? Man stelle sich vor, dieselbe Anwendung würde auf dem Rechner eines deutschen Angestellten andere Ergebnisse liefern als auf dem Rechner des amerikanischen Firmenchefs. Das will niemand, eine Abhängigkeit von CurrentCulture sollte tabu sein, auch wenn in diesem Beispiel wegen der Vorgaben FirstFourDayWeek und Monday keine anderen Ergebnisse berechnet werden.Noch einen weiteren Grund gibt es, die .NET-Funktion Calender.GetWeekOfYear nicht zu verwenden: Ein kurzer Test mit Vergleichsdaten von der Webseite aktuelle-kalenderwoche.org zeigt, dass Microsofts Algorithmus zumindest beim Berechnen der hiesigen Kalenderwochenzählung Fehler aufweist (getestet unter .NET 6): Immer dann nämlich, wenn das Jahresende in die erste Kalenderwoche des Folgejahres fällt, meldet er für den 31.12. fälschlicherweise die KW 53.Die Fehler betreffen allerdings nur die „zwischen den Jahren“ genannten Tage, genauer die Tage vom 29.12. bis einschließlich dem 31.12. Viele können mit diesem Fehler also durchaus leben.

ISOWeek oder externe Bibliotheken

Eine mögliche Lösung bieten spezielle Date-Time-Bibliotheken. Infrage kommen beispielsweise die Bibliotheken Noda Time [2] oder TimePeriod [3], welche kostenfrei zu haben sind und jeweils zusätzliche Möglichkeiten eröffnen. Aber wer intensiver sucht (oder diesen Text liest ;-), der findet auch in der .NET-Dokumentation eine Lösung.Seit .NET Core 3 und seither in allen Versionen der Core-Schiene, also auch in .NET 5, 6 und 7, gibt es die Klasse ISOWeek im Namensraum System.Globalization [4]. Diese Klasse gehört zum .NET Standard 2.1 und stellt die folgenden Methoden zur Verfügung, welche das ISO-Wochendatumssystem unterstützen und richtig rechnen:
  • GetWeekOfYear liefert die Kalenderwoche zu einem übergebenen Datum.
  • GetWeeksInYear berechnet die Anzahl der Wochen eines Jahres gemäß der ISO-Wochennummerierung.
  • GetYear berechnet das Jahr gemäß der ISO-Wochennummerierung (informell auch als ISO-Jahr bezeichnet) für das übergebene Datum.
  • GetYearStart berechnet das Datum, an dem das ISO-Jahr beginnt.
  • GetYearEnd berechnet das Datum, an dem das ISO-Jahr endet.
  • ToDateTime ordnet das ISO-Wochendatum in Form eines angegebenen ISO-Jahres, der Wochenzahl und des Wochentags dem entsprechenden Datum zu.
Soll die Ausgabe im Format KW.Jahr erfolgen, also beispielsweise als 01.2025, dann taugt dazu der folgende Code:
 
Public Function MS_ISO_Kalenderwoche( 
  ByVal d As Date) As String 

  Return System.Globalization.ISOWeek. 
    GetWeekOfYear(d).ToString.PadLeft(2, "0") + "." + 
    System.Globalization.ISOWeek.GetYear(d).ToString 
End Function 
 
Ein Versuch, die Methode durch Umstellen der CurrentCulture (war de-DE) auf us-US zu irritieren, war übrigens erfolglos; die Ergebnisse blieben dieselben. Offenbar besteht keine Abhängigkeit von der eingestellten Culture.

Selbst gebaute Funktion

Selten werden Artikel geschrieben, weil der Autor alles schon vorher weiß. So auch hier. Am Anfang stand das Entdecken des Fehlers, es folgte ein (erfolgreicher) Korrekturversuch, nur damit der Ehrgeiz geweckt wurde, eine eigene Funktion Kalenderwoche zu schreiben. Erst nachdem diese fertig war, entdeckte ich die ISOWeek-Funktion. Für alle, die es interessiert, hier eine Kurzfassung der selbst gebauten Version einer Kalenderwochen-Methode.Im Rückblick war das gar nicht so schwer, wenn man die zwischenzeitlichen Irrwege ausblendet. Die hier gezeigte Lösung setzt auf folgende Erkenntnisse [1]:
  • Eine Kalenderwoche hat sieben Tage und beginnt mit einem Montag.
  • Jedes Jahr hat 52 oder 53 Kalenderwochen.
  • Der 29., 30. und 31. Dezember können schon zur Kalenderwoche 1 des Folgejahres gehören.
  • Der 1., 2. und 3. Januar können noch zur letzten Kalenderwoche des Vorjahres gehören.
  • Der 4. Januar liegt immer in Kalenderwoche 1.
  • Jahre, die mit einem Donnerstag beginnen oder enden, haben 53 Kalenderwochen.
Die Erweiterungsfunktion Kalenderwoche nimmt ein Datum entgegen, ermittelt die Kalenderwoche zu diesem Datum und liefert das Ergebnis als String in der Form KW.JJJJ zurück. Für das Datum 12.11.2026 lautet das Ergebnis also 46.2026. Für numerische Zwecke lässt sich das Ergebnis leicht wieder auseinandernehmen. Ein Ergebnis ohne Angabe des Jahres wäre dagegen nicht immer eindeutig.Der Code dazu besteht aus zwei Abschnitten. Im ersten Abschnitt (Listing 1) wird für das übergebene Datum ermittelt, zu welchem Kalenderwochenraster dieses gehört – entweder zum Jahr des übergebenen Datums, zum Jahr danach oder zum Jahr davor.
Listing 1: Kalenderwoche ermitteln, Abschnitt 1
Public Function Kalenderwoche( 
  ByVal d As Date) As String 

  Dim erg As String = "" 
  Dim kwJahr As Integer = d.Year 

  ' Testen für d.Year, d.Year-1 und d.Year + 1 
  ' ob der gesuchte Tag darin liegt. 
  If d.DateIsBetween(StartTagKW1(d.Year), 
    EndTagKW5x(d.Year)) Then 
    ' Das Datum gehört in eine KW im Jahr d.Year 
    kwJahr = d.Year 
  ElseIf d.DateIsBetween(StartTagKW1(d.Year - 1), 
    EndTagKW5x(d.Year - 1)) Then 
    ' Das Datum gehört in eine KW im Jahr d.Year - 1 
    kwJahr = d.Year - 1 
  ElseIf d.DateIsBetween(StartTagKW1(d.Year + 1), 
    EndTagKW5x(d.Year + 1)) Then 
    ' Das Datum gehört in eine KW im Jahr d.Year + 1 
    kwJahr = d.Year + 1 
  End If   
DateIsBetween ist dabei eine Erweiterungsfunktion, die schlicht prüft, ob ein Datum in der übergebenen Zeitspanne liegt.StartTagKW1(Jahr) ermittelt den ersten Tag der ersten Kalenderwoche des Jahres. Dazu werden die Funktionen MoDerWoche sowie DayNrOfWeek_DE benutzt.MoDerWoche ermittelt den Montag der Woche, in welcher das übergebene Datum liegt, und DayNrOfWeek_DE sorgt dafür, dass der Sonntag nicht als Tag 0, sondern als Tag 7 behandelt wird.EndTagKW5x ermittelt den letzten Tag des Kalender­wochenrasters zum übergebenen Jahr. 2023 ist das der 31.12.2023, im Folgejahr 2024 dann der 29.12.2024.
Private Function StartTagKW1( 
  ByVal Jahr As Integer) As Date 
  Return MoDerWoche(CDate("04.01." + Jahr.ToString)) 
End Function 

Private Function MoDerWoche(ByVal d As Date) As Date 
  Return d.AddDays(-(DayNrOfWeek_DE(d) - 1)) 
End Function 

Private Function DayNrOfWeek_DE( 
  ByVal d As Date) As Integer 
  Dim dow As Integer = d.DayOfWeek 
  If dow = 0 Then dow = 7 ‚ Sonntag: US=0, DE=7 
  Return dow 
End Function 

Private Function EndTagKW5x( 
  ByVal Jahr As Integer) As Date 
  Return StartTagKW1(Jahr + 1).AddDays(-1) 
End Function 
 
Damit steht jetzt fest, welches Kalenderwochenraster für die Auswertung zu benutzen ist. Die Auswertung ermittelt die laufende Nummer des übergebenen Tages im Jahr, korrigiert diesen Wert um die Zahl der Tage, die aus dem Vor- beziehungsweise Folgejahr zu berücksichtigen sind, teilt diesen Wert, weil jede Woche sieben Tage hat, einfach durch sieben, rundet gegebenenfalls auf, baut den Ergebnisstring zusammen und schickt ihn an den Aufrufer, siehe Listing 2.
Listing 2: Kalenderwoche ermitteln, Abschnitt 2
Public Function Kalenderwoche( 
  ByVal d As Date) As String 

  Dim erg As String = "" 
  Dim kwJahr As Integer = d.Year 

  ' hierher gehört der Code aus Listing 1 

  If kwJahr = d.Year Then 
    Dim TagNr = d.DayOfYear + 
      dayOfYearKorrektur(d.Year) 
    Dim kw As Integer 
    ' Lässt sich die TagNr nicht ohne Rest 
    ' durch 7 teilen, liegt der Tag in der 
    ' nächsten KW, also + 1 
    If (TagNr Mod 7) = 0 Then 
      kw = TagNr / 7 

    Else 
      kw = Math.Truncate(TagNr / 7) + 1 
    End If 
    erg = kw.ToString.PadLeft(2, "0") + 
      "." + d.Year.ToString 

  ElseIf kwJahr = d.Year - 1 Then 
    ' KW ist die letzte KW des Vorjahres 
    erg = AnzKw(d.Year - 1).ToString + 
      "." + (d.Year - 1).ToString 

  ElseIf kwJahr = d.Year + 1 Then 
    ' KW ist die erste KW des Folgejahres 
    erg = "01." + (d.Year + 1).ToString 
  End If 

  Return erg 
End Function  
Ganz einfach ist die Zuordnung, wenn das Datum im Vor- oder Folgejahr liegt, dann ist die gesuchte Woche nämlich die letzte KW des Vorjahres (die durch die Donnerstag-Regel ermittelt wird, siehe oben) oder die KW 1 des Folgejahres.
Private Function AnzKw(
  ByVal Jahr As Integer) As Integer

  If CDate("01.01." + Jahr.ToString).DayOfWeek = 4 Or
    CDate("31.12." + Jahr.ToString).DayOfWeek = 4 Then 
    Return 53 Else Return 52
End Function 
Aufwendiger ist die Funktion dayOfYearKorrektur – ich denke, dass das noch einfacher gehen muss, aber derzeit sieht die Funktion, welche die Zahl der Tage aus Vor- oder Folgejahr ermittelt, die hinzugefügt beziehungsweise abgezogen werden müssen, so aus wie in Listing 3.
Listing 3: Korrekturwerte für dayOfYear ermitteln
Private Function dayOfYearKorrektur(
  ByVal Jahr As Integer) As Integer 

  Dim erg As Integer = 0 
  Dim UltimoVJ As Date = CDate("31.12." + 
    (Jahr - 1).ToString) 
  Dim d0 As Date = StartTagKW1(Jahr) 
  Dim d1 As Date = CDate("01.01." + Jahr.ToString) 
  Dim diff As Integer = 0 

  If d0 <> d1 Then 
    If d0 <= UltimoVJ Then 
      ' die ersten Tage von KW 1 (max 3) gehören
      ' zum Vorjahr, ermitteln wie viele es sind 
      Dim dx As Date = d0 

      For i = 1 To 3 
        dx = dx.AddDays(1) 
        If dx = d1 Then 
          erg = i 
          Exit For 
        End If 
      Next 

    Else 
      ' KW1 startet mit dem 1., 2., 3. oder
      ' dem 4. Januar 
      Dim dx As Date = d1 ' 1. Januar 
      For i = 1 To 3 
        dx = dx.AddDays(1) 
        If dx = d0 Then 
          erg = -i 
          Exit For 
        End If 
      Next 
    End If 
  End If 

  Return erg 
End Function  
In Bild 1 finden Sie die Ergebnisse eines Testlaufs für alle drei Methoden, der unter anderem ein paar der kritischen Datumswerte umfasst. Im Bild rot hervorgehoben sind die Fehler der Methode CurrentCulture.Calendar.GetWeekOfYear beim Berechnen der Kalenderwoche für den 31.12. der Jahre 2024, 2025, 2031 und 2036.
Die .NET-FunktionCurrentCulture.Calendar.GetWeekOfYear liefert teilweise falsche Ergebnisse(Bild 1) © Autor

Fazit

Mit der Berechnung der europäischen Kalenderwochen gibt sich die in .NET verbaute Funktion CurrentCulture.Calendar.GetWeekOfYear wenig Mühe. Außerdem sollte man die Rechenergebnisse seines Programms nicht von den Culture-Einstellungen des Rechners abhängig machen. Wer korrekte Angaben auch für Datumswerte „zwischen den Jahren“ haben möchte, kann die zum .NET Standard 2.1 gehörende Methode ISOWeek.GetWeekOfYear aus dem Namensraum System.Globalisation nutzen, sich eine eigene Routine dafür bauen oder auf eine Date-Time-Bibliothek zurückgreifen.

Fussnoten

  1. Wikipedia zur ISO 6801 sowie zur Kalenderwoche, http://www.dotnetpro.de/SL2305Kalenderwoche1
  2. Noda Time, https://nodatime.org
  3. TimePeriod, http://www.dotnetpro.de/SL2305Kalenderwoche2
  4. ISOWeek in der .NET-Dokumentation, http://www.dotnetpro.de/SL2305Kalenderwoche3

Neueste Beiträge

„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

MVVM mal ganz einfach - MVVM-Framework im Selbstbau
Warum man manchmal einfach von vorn anfangen muss.
14 Minuten
12. Mär 2018
Wenn es sich leicht anfühlt, machst du etwas falsch! - Agilität und Lernen
Was wertebasierte Frameworks leisten können – und was nicht.
12 Minuten
20. Sep 2021
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige