Japanische Ära, Feiertage und Abrechnungsstichtage in Business-Apps — Übergangsresistentes Design, JapaneseCalendar und Werktagsberechnung in der Praxis

· · C#, .NET, Japanische Ära (Wareki), Feiertage, Abrechnungsstichtag, Datums-/Zeitverarbeitung, Business-Anwendungen, Windows-Entwicklung, Technische Beratung

„Das Datum auf dem Beleg soll in der japanischen Ära stehen.“ „Die Zahlung ist bei Abrechnung am 20. zum Ende des Folgemonats fällig, oder am vorherigen Werktag, falls dieser Tag ein Feiertag ist.“ „Die Summen sollen nach Geschäftsjahr aggregiert werden.“ Business-Anwendungen in Japan bringen standardmäßig Datumsanforderungen mit, die nirgendwo sonst auf der Welt Sinn ergeben. Und in diesem Bereich lauert eine andere Art von Falle als bei der gewöhnlichen Datums-/Zeitverarbeitung. Ärannamen vermehren sich mit jedem Übergang, Feiertage verschieben sich mit jeder Gesetzesänderung, und „Monatsende“ hat je nach Monat eine andere Länge. Mit anderen Worten: All das sind „Spezifikationen, die sich später ändern“.

Im letzten Beitrag, „Datum, Uhrzeit und Zeitzonen in Business-Anwendungen“, haben wir das Prinzip der UTC-Speicherung und den Umgang mit Zeitzonen dargelegt. Diesmal behandeln wir als Fortsetzung die drei großen japanspezifischen Anforderungen: japanische Ära, Feiertage und Abrechnungsstichtage. Wir betrachten übergangsresistentes Design, warum Feiertage niemals hartkodiert werden dürfen und wie man sie stattdessen als Daten vorhält, sowie die Berechnung von Abrechnungsstichtagen unter Berücksichtigung des Rundungsverhaltens von AddMonths – jeweils mit Codebeispielen und Entscheidungstabellen.

1. Das Wichtigste zuerst

  • Interne Verarbeitung und Speicherung bleiben im gregorianischen Kalender; die japanische Ära existiert nur im Moment der Anzeige. Die Speicherung als Ären-String führt zu einem dreifachen Übel: Daten lassen sich nicht sortieren, nach einem Äraübergang vermischen sich alte und neue Schreibweisen, und es besteht die Gefahr, dass ungültige Daten eindringen.
  • Überlassen Sie die Ären-Umrechnung JapaneseCalendar statt einer selbstgebauten Zuordnungstabelle. Die Ären-Definitionen werden vom Betriebssystem geliefert, sodass ein Plattform-Update genügt, um einem Äraübergang zu folgen.1 Nur in der App verbliebene hartkodierte Strings wie „Heisei“ oder „Reiwa“ bleiben zurück.
  • Feiertage lassen sich nicht per Formel bestimmen. Die Tagundnachtgleiche-Feiertage im Frühling und Herbst werden erst im Vorjahr festgelegt, und die Feiertage selbst können durch normale Gesetzesänderungen oder Sondergesetze verschoben werden.2 Gestalten Sie dies über eine Feiertagstabelle plus Aktualisierungsprozess und nutzen Sie als Datenquelle idealerweise die vom japanischen Cabinet Office veröffentlichte Feiertags-CSV.
  • „Monatsende“, „Werktag“ und „Geschäftsjahr“ sollten erst in der Spezifikation und dann erst im Code definiert werden. Insbesondere AddMonths rundet das Monatsende automatisch, weshalb die Berechnung von Abrechnungsstichtagen stets von einem festen Bezugsdatum ausgehen sollte.3

Zunächst eine Übersicht, wie die jeweiligen Datenarten gespeichert werden sollten.

Daten Speicherform Zeitpunkt der Umrechnung/Prüfung
Das Datum selbst (Bestelldatum, Zahlungsziel) Gregorianisch als DateOnly / DateTime (DB: Spaltentyp date)
Ären-Notation (Belege, Bildschirmanzeige) Nicht speichern Umrechnung über JapaneseCalendar nur im Moment von Anzeige/Druck
Feiertage Feiertagstabelle (Tabelle oder Konfigurationsdatei) Abfrage bei der Werktagsberechnung
Betriebsferien (Sommerpause, Firmenjubiläum) Von den Feiertagen getrennte Betriebsferien-Tabelle Wie oben (nicht mit Feiertagen vermischen)
Abrechnungsstichtag / Zahlungsziel Attribut im Geschäftspartner-Stammdatensatz („Abrechnung am 20., Zahlung Ende Folgemonat“) Berechnung ab Bezugsdatum bei der Rechnungsstellung
Geschäftsjahr Nicht speichern (abgeleiteter Wert) Berechnung aus dem Datum bei Anzeige/Aggregation

2. Die japanische Ära — nur zur Anzeige

2.1 Warum intern einheitlich auf den gregorianischen Kalender setzen

Die japanische Ära ist eine menschenlesbare Notation, keine für Berechnungen geeignete Repräsentation. „Heisei 9“ und „Reiwa 9“ haben beim einfachen String-Vergleich keine definierte Reihenfolge, und jede Zeitraumberechnung, die eine Äragrenze überschreitet, benötigt letztlich ohnehin eine Umrechnung in den gregorianischen Kalender. Erschwerend kommt hinzu, dass das Ärensystem mit jedem Übergang wächst, sodass in Ären-Notation gespeicherte Daten bei jedem Wechsel eine Mischung aus alten und neuen Schreibweisen ansammeln. Auch das Einschleichen ungültiger Daten wie „Showa 64, 10. Januar“ (nicht existent, da ab dem 8. Januar bereits Heisei galt) ist ein klassisches Problem von Systemen, die roh eingegebene Ären-Angaben unverändert speichern.

Eine weitere, leicht übersehene Einschränkung: JapaneseCalendar von .NET unterstützt nur Daten ab dem 8. September des Jahres Meiji 1 (1868).4 Ältere Daten – etwa ein altes, aus einem Familienregister stammendes Geburtsdatum – lassen sich in Ären-Notation nicht korrekt verarbeiten. Vereinheitlicht man intern auf den gregorianischen Kalender, bleiben all diese Probleme allein auf die Anzeigeschicht beschränkt.

2.2 Anzeige und Eingabe mit JapaneseCalendar

Die einzigen Kalenderklassen von .NET, die mehrere Ären verarbeiten können, sind JapaneseCalendar (und JapaneseLunisolarCalendar), wobei der Formatbezeichner g dem Ärannamen entspricht.1 Der minimale Code zur Anzeige in der japanischen Ära sieht so aus:

using System.Globalization;

var jaJpWareki = new CultureInfo("ja-JP");
jaJpWareki.DateTimeFormat.Calendar = new JapaneseCalendar();

var date = new DateOnly(2026, 7, 11);
Console.WriteLine(date.ToString("ggy年M月d日", jaJpWareki));  // 令和8年7月11日
Console.WriteLine(date.ToString("ggyy/MM/dd", jaJpWareki));   // 令和08/07/11

Für die Eingabe in der japanischen Ära ist es sicherer, eine Oberfläche zu gestalten, in der die Ära über eine Combobox ausgewählt wird und Jahr, Monat und Tag als Zahlen eingegeben werden, statt freien Text zu parsen. Wenn Freitext unbedingt akzeptiert werden muss, legen Sie das Format mit ParseExact fest.

var input = "令和8年7月11日";
var parsed = DateTime.ParseExact(input, "ggy年M月d日", jaJpWareki);
Console.WriteLine(parsed.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));  // 2026-07-11

Bei der Notation des ersten Jahres einer Ära ist Vorsicht geboten. Auf Umgebungen, auf denen die Updates zur Ären-Unterstützung eingespielt sind, wird das erste Jahr jeder Ära standardmäßig als „Reiwa Gan-nen“ (元年, wörtlich „Gründungsjahr“) formatiert statt als Zahl, und es steht ein Kompatibilitätsschalter — Switch.System.Globalization.FormatJapaneseFirstYearAsANumber — zur Verfügung, um wieder die numerische Schreibweise „Reiwa 1“ herzustellen.5 Klären Sie vor der Implementierung als Teil der Spezifikation, ob das Belegformat „Reiwa 1“ oder „Reiwa Gan-nen“ erwartet. Ein weiterer Punkt: Standardmäßig ist die Prüfung des Jahresbereichs einer Ära gelockert, sodass ein nicht existierender Wert wie „Heisei 33“ akzeptiert und in das tatsächlich entsprechende Datum (Reiwa 3) umgerechnet wird. Wenn Sie eine strikte Eingabevalidierung benötigen, die solche Werte ablehnt, können Sie dies über Switch.System.Globalization.EnforceJapaneseEraYearRanges erzwingen.5 Ob Sie es locker belassen oder strikt einstellen, hängt vom Zustand der vorhandenen Daten ab — insbesondere davon, wie Sie mit einem vor dem Äraübergang gedruckten oder eingegebenen Wert wie „Heisei 32“ umgehen wollen.

2.3 Übergangsresistenter Code — und was zurückbleibt

Aus der Erfahrung mit dem Reiwa-Übergang (2019) lässt sich Folgendes ableiten: Die Plattform zieht mit, die selbstgebaute Implementierung der App jedoch nicht. Die Definition der Ärannamen ist nicht in .NET hartkodiert — der Mechanismus verweist auf die vom Betriebssystem gehaltenen Ären-Informationen —, sodass Umrechnung und Formatierung von JapaneseCalendar bei fortlaufenden OS- und .NET-Updates der neuen Ära automatisch folgen.1 Was zurückbleibt, sind anwendungsseitige Bestände wie diese:

  • Selbstgebaute Ären-Umrechnungstabellen oder -Verzweigungen, etwa if (year >= 2019) era = "令和";
  • Ärannamen, die direkt in Belegvorlagen, Excel-Zellenformate oder vorgedruckte Formulare geschrieben wurden
  • Code, der eine einzelne Ären-Buchstabenabkürzung wie „R“ oder „H“ mit eigenen Regeln behandelt (häufig in Austauschformaten mit Kernsystemen)
  • Die gespeicherten Daten selbst, weiterhin in japanischer Ären-Notation

Bei der Kostenschätzung für eine Äraübergangs-Nacharbeit beginnt man am besten damit, den gesamten Quellcode und alle Vorlagen mechanisch nach „Showa“, „Heisei“, „Reiwa“ und „Gan-nen“ zu durchsuchen. Business-Anwendungen, die noch aus der VB6- oder Access-Ära stammen, enthalten so gut wie sicher irgendwo eine selbstgebaute Umrechnungstabelle — dieses Thema wird daher häufig zusammen mit der in „Verlängerung der Lebensdauer bzw. Migration von VB6-/Access-Business-Anwendungen“ beschriebenen Entscheidung behandelt.

2.4 Eine versteckte Falle: die Windows-Ländereinstellungen

Noch eine leicht übersehene Falle. In den Windows-Ländereinstellungen kann der Benutzer den Kalendertyp des Betriebssystems auf die japanische Ära umstellen. Da CultureInfo.CurrentCulture die Einstellungsänderung des Benutzers (eine „Benutzerüberschreibung“) standardmäßig widerspiegelt,6 liefert auf einem Gerät mit dieser Einstellung DateTime.ToString() mit Kultur-Standardformat plötzlich Ären-Strings wie „令和08/07/11“. Wenn Code Datumsangaben über das Kultur-Standardformat von ToString() in Protokolle, CSV-Dateien oder die Datenbank schreibt, mischt sich nur auf diesem einen Gerät die japanische Ära in die gespeicherten Daten, was nachgelagertes Parsen zum Absturz bringt.

Die Gegenmaßnahme führt zur gleichen Schlussfolgerung wie im vorherigen Artikel: Jeder Datums-String, der eine Grenze überschreitet — Speicherung, Kommunikation, Protokollierung — muss stets mit CultureInfo.InvariantCulture und einem expliziten Format ausgegeben werden. Das Kultur-Standardformat ist ausschließlich für die Bildschirmanzeige gedacht. Für einen breiteren Blick auf den Einfluss der Kultur auf die Anzeige siehe auch „Lokalisierung von WinForms-/WPF-Anwendungen“.

3. Feiertage — warum sie niemals hartkodiert werden dürfen, und wie man eine Feiertagstabelle betreibt

3.1 Feiertage „ändern sich“

Schreiben Sie die Feiertagsprüfung niemals als switch (Monat, Tag). Feiertage sind durch das Gesetz über nationale Feiertage festgelegt und haben sich durch Gesetzesänderungen und Verwaltungspraxis wiederholt geändert.2 Allein die jüngsten Beispiele:

Jahr Änderung
2016 Neueinführung des Tags des Berges (11. August)
2019 Infolge des Äraübergangs verschiebt sich der Geburtstag des Kaisers vom 23. Dezember auf den 23. Februar. 2019 gab es überhaupt keinen Geburtstag des Kaisers
2019 Aufgrund einer Sonderregelung im Zusammenhang mit der Thronfolge werden der 1. Mai (Tag der Inthronisierung) und weitere Tage zu vorübergehenden Feiertagen
2020 Der Tag des Sports und der Gesundheit wird in Tag des Sports umbenannt
2020/2021 Aufgrund eines Sondergesetzes im Zusammenhang mit den Olympischen Spielen verschieben sich der Tag des Meeres, der Tag des Sports und der Tag des Berges nur für diese Jahre

Hinzu kommt: Für den Frühlings- und den Herbst-Tagundnachtgleiche-Feiertag ist überhaupt kein Datum im Gesetz festgelegt. Sie werden erst im Vorjahr auf Basis astronomischer Berechnung festgesetzt und bekanntgegeben, sodass der einzige verlässliche Weg, eine künftige Tagundnachtgleiche zu kennen, das Abwarten der offiziellen Bekanntgabe ist.2 Eine Berechnungsformel, die davon ausgeht, dass „Feiertage jedes Jahr gleich sind“, ist grundsätzlich nicht realisierbar.

3.2 Entscheidungstabelle für Datenquellen und Speicherform

Gestalten Sie die Feiertagsbestimmung als „Daten plus Aktualisierungsprozess“. Ein Vergleich der wichtigsten Optionen:

Ansatz Inhalt Geeignet für Zu beachten
Feiertagstabelle (eigenverwaltet) Tabelle mit Datum und Bezeichnung, Aktualisierung über eine Verwaltungsoberfläche Business-Systeme generell, die dies zusammen mit Betriebsferien verwalten möchten Aktualisierung in den Betriebsablauf integrieren (jährliche Prüfung als Kalendererinnerung)
Import der Cabinet-Office-CSV Import der vom Cabinet Office veröffentlichten Feiertags-CSV zur Aktualisierung der Tabelle Wenn eine autoritative Primärquelle für Tabellenaktualisierungen gewünscht ist (empfohlen) Enthält nur den bereits veröffentlichten Zeitraum. Auf die Zeichenkodierung achten
Feiertagsbibliothek (NuGet o. Ä.) Bestimmung über Berechnungslogik plus mitgelieferte Daten Kleinere Einsatzzwecke wie interne Tools ohne eigenen Aktualisierungsprozess Künftige Gesetzesänderungen hängen von Bibliotheks-Updates ab. Mitgelieferte Regeln müssen geprüft werden
Hartkodierung Datumsangaben direkt im Quellcode Keiner Jede Gesetzesänderung erfordert eine Neuverteilung an alle Clients

Als Primärquelle lässt sich die vom Cabinet Office auf seiner Seite „Nationale Feiertage“ veröffentlichte CSV mit Feiertagsdaten und -bezeichnungen von Showa 30 (1955) bis zum Folgejahr verwenden.2 Beim Import sind zwei praktische Punkte zu beachten: (1) Da nur der bereits bestätigte Zeitraum veröffentlicht ist, sollten Sie als Teil der Spezifikation festlegen, wie sich das System bei einer Abfrage von Feiertagen ab dem übernächsten Jahr verhält (Fehler zurückgeben oder mit einem auf dem Gesetz basierenden vorläufigen Wert samt Kennzeichnung „vorläufig“ auffüllen), und (2) die Zeichenkodierung dieser CSV gehört zur Shift_JIS-Familie, weshalb die Kodierung beim Einlesen explizit anzugeben ist, wie in „CSV ist nicht einfach nur „Text““ beschrieben.

3.3 Ersatzfeiertag, nationaler Feiertag und Werktagsberechnung

Über die reine Datumsliste hinaus definiert das Feiertagsgesetz zwei abgeleitete Regeln: den Ersatzfeiertag (fällt ein Feiertag auf einen Sonntag, wird der nächste darauffolgende, nicht selbst feiertägliche Tag arbeitsfrei) und den nationalen Feiertag (ein Werktag, der zwischen zwei Feiertagen liegt, wird arbeitsfrei).2 Die Cabinet-Office-CSV enthält diese ebenfalls als Zeilen, sodass beim CSV-Import-Ansatz allein die Abfrage der Tabelle genügt. Auch wenn Sie diese Logik selbst nachbilden, ist sie relativ stabil, da sie auf dem Gesetz beruht.

Schreiben Sie die Werktagsberechnung mit drei Bedingungen: kein Samstag/Sonntag, nicht in der Feiertagstabelle enthalten, nicht in der Betriebsferien-Tabelle enthalten.

public sealed class BusinessDayCalendar
{
    private readonly HashSet<DateOnly> _publicHolidays;   // Feiertagsstammdaten (einschließlich Ersatzfeiertage)
    private readonly HashSet<DateOnly> _companyHolidays;  // Betriebsurlaubsstammdaten

    public BusinessDayCalendar(IEnumerable<DateOnly> publicHolidays,
                               IEnumerable<DateOnly> companyHolidays)
    {
        _publicHolidays = [.. publicHolidays];
        _companyHolidays = [.. companyHolidays];
    }

    public bool IsBusinessDay(DateOnly d) =>
        d.DayOfWeek is not (DayOfWeek.Saturday or DayOfWeek.Sunday)
        && !_publicHolidays.Contains(d)
        && !_companyHolidays.Contains(d);

    // Fällt der Termin auf einen Feiertag, wird er auf den vorherigen Werktag vorgezogen. Ob stattdessen nach hinten verschoben wird, hängt von den Vertragsbedingungen ab
    public DateOnly ToPreviousBusinessDay(DateOnly d)
    {
        while (!IsBusinessDay(d)) d = d.AddDays(-1);
        return d;
    }
}

Feiertage und Betriebsferien bewusst in getrennten Tabellen zu führen, ist Absicht. „Bei der Prüfung eines Banküberweisungstermins zählen nur die gesetzlichen Feiertage (die eigene Sommerpause spielt keine Rolle)“, „bei der Prüfung eines Versanddatums zählen beide“ — welche Menge an arbeitsfreien Tagen relevant ist, hängt vom Anwendungsfall ab. Vermischt man beides in einer Tabelle, wird eine spätere Trennung zur Archäologie in bereits gespeicherten Daten.

4. Abrechnungsstichtag, Zahlungsziel, Geschäftsjahr — „Monatsende“ in der Berechnung korrekt behandeln

4.1 Das Rundungsverhalten von AddMonths

Die erste Falle bei der Berechnung von Abrechnungsstichtagen ist AddMonths. DateTime.AddMonths / DateOnly.AddMonths runden auf den letzten Tag des resultierenden Monats ab, wenn das naive Ergebnis auf einen im Monat nicht existierenden Tag fiele.3

var jan31 = new DateOnly(2026, 1, 31);
var feb = jan31.AddMonths(1);   // 2026-02-28 ── auf Monatsende gerundet (korrekt)
var mar = feb.AddMonths(1);     // 2026-03-28 ── was als „Monatsende“ gedacht war, wird stillschweigend zum 28.

Die Rundung selbst ist korrekt spezifiziert, aber wird auf ein bereits gerundetes Ergebnis erneut AddMonths angewendet, geht die Absicht „Monatsende“ verloren. Das zeigt sich in der Praxis, wenn ein monatlicher Abrechnungsplan als „letztes Ausführungsdatum plus ein Monat“ aufgebaut wird: Sobald der Februar durchlaufen ist, driftet die Ausführung dauerhaft auf den 28. jeden Monats. Zwei Gegenmaßnahmen:

  • Wiederkehrende Termine stets aus der Definition des Bezugspunkts heraus berechnen („jedes Monatsende“, „der 20. jeden Monats“), nicht durch Addition auf das vorherige Ergebnis
  • „Monatsende“ aus DateTime.DaysInMonth(y, m) ableiten, nicht aus einem Datumsliteral

4.2 Implementierung von „Abrechnung am 20., Zahlung Ende Folgemonat“

Abrechnungsstichtag und Zahlungsziel sind Attribute je Geschäftspartner. „Abrechnung am 20., Zahlung Ende Folgemonat“ als Code sieht so aus:

public static class PaymentTerms
{
    // Handelsdatum → Abschlussdatum (Beispiel: Abschluss am 20. jedes Monats)
    public static DateOnly GetClosingDate(DateOnly tradeDate, int closingDay)
    {
        var dayInMonth = Math.Min(closingDay, DateTime.DaysInMonth(tradeDate.Year, tradeDate.Month));
        var closing = new DateOnly(tradeDate.Year, tradeDate.Month, dayInMonth);
        return tradeDate <= closing ? closing : closing.AddMonths(1);
        // Hinweis: closingDay=31 (Monatsendabschluss) wird dank der Rundung von AddMonths korrekt zum Ende des Folgemonats
    }

    // Abschlussdatum → Zahlungsfälligkeit (Beispiel: Zahlung am Ende des Folgemonats). Vor der Anwendung die Spezifikation zur Feiertagsanpassung (vorziehen/verschieben) prüfen
    public static DateOnly GetPaymentDueDate(DateOnly closingDate, BusinessDayCalendar calendar)
    {
        var nextMonth = closingDate.AddMonths(1);
        var endOfMonth = new DateOnly(nextMonth.Year, nextMonth.Month,
                                      DateTime.DaysInMonth(nextMonth.Year, nextMonth.Month));
        return calendar.ToPreviousBusinessDay(endOfMonth);  // Bei Verträgen mit der Regel „bei Feiertag auf den vorherigen Werktag vorziehen“
    }
}

Wichtiger als der Code ist die vorgelagerte Klärung der Spezifikation. Ist bei „Abrechnung Monatsende“ das Monatsende ein Kalendertag oder ein Werktag? Wird der Termin bei Fall auf einen Feiertag vor- oder zurückverlegt (der Zahlende tendiert zur Vorverlegung, der Empfänger zur Rückverlegung des erwarteten Zahlungseingangs — die Praxis unterscheidet sich je nach Perspektive)? Wie wird ein Vertrag mit Abrechnung am 29.–31. im Februar interpretiert? Ist dies nicht dokumentiert, wird der Code des Implementierers unmittelbar zu „einer Spezifikation, der niemand zugestimmt hat“. Die meisten Bugs rund um Abrechnungsstichtage führen bei der Untersuchung nicht auf einen Codefehler zurück, sondern auf das Fehlen einer Spezifikation.

4.3 Geschäftsjahr und Quartal

In Japan beginnt das „Geschäftsjahr“ einer Business-Anwendung fast immer im April. Speichern Sie es nicht, sondern behandeln Sie es als aus dem Datum abgeleiteten Wert.

public static int GetFiscalYear(DateOnly d) => d.Month >= 4 ? d.Year : d.Year - 1;
// 2026-03-31 → Geschäftsjahr 2025, 2026-04-01 → Geschäftsjahr 2026

public static int GetFiscalQuarter(DateOnly d) => ((d.Month + 8) % 12) / 3 + 1;
// Apr-Jun → Q1, Jul-Sep → Q2, Okt-Dez → Q3, Jan-Mär → Q4

Achten Sie darauf, das Geschäftsjahr nicht leichtfertig redundant in der Datenbank zu speichern. Wird ein aus dem Datum stets ableitbarer Wert in einer eigenen Spalte gehalten, entstehen bei einer Datumskorrektur mit vergessener Aktualisierung Daten, bei denen Datum und Geschäftsjahr nicht mehr zusammenpassen. Wenn es aus Gründen der Aggregationsleistung unbedingt gehalten werden muss, machen Sie es zu einer berechneten (generierten) Spalte und untersagen Sie der Anwendung das direkte Schreiben.

4.4 Tests — Bugs vorwegnehmen, die „nur an diesem Tag“ auftreten

Bugs rund um Ära, Feiertage und Abrechnungsstichtage treten nur an bestimmten Daten auf. Aufbauend auf der im vorherigen Artikel vorgestellten Injektion der aktuellen Zeit über TimeProvider (Kapitel 7 von „Datum, Uhrzeit und Zeitzonen in Business-Anwendungen“) sind mindestens folgende fünf Daten in diesem Bereich zu testen:

  • Äragrenzen: Anzeige und Rückumrechnung von Ären-Eingaben rund um 1989-01-07/08 (Showa → Heisei) und 2019-04-30/05-01 (Heisei → Reiwa)
  • Erstes Jahr einer Ära: Prüfung der Beleg-Notation „Reiwa Gan-nen“ (Abschnitt 2.2)
  • Schaltjahre: Abrechnungs- und Zahlungszielberechnungen, die den 29. Februar einschließen (2024, 2028)
  • Kette von Monatsenden: Erzeugung von zwölf Monaten Abrechnungsstichtagen ausgehend vom 31. Januar, mit Prüfung, ob das Monatsende erhalten bleibt (Abschnitt 4.1)
  • Geschäftsjahresgrenzen: Bestimmung von Geschäftsjahr und Quartal für den 31. März und den 1. April

Bei der Codereview lassen sich zudem folgende Muster mechanisch per grep aufspüren.

Wird gefunden, ist verdächtig Was passieren kann Die Lösung
Ären-Literale wie "平成" oder "令和" Bleibt beim nächsten Äraübergang zurück Umstellung auf JapaneseCalendar plus Format g (Abschnitt 2.3)
Literale Feiertagsdaten (z. B. 5, 3) Fehlbestimmung nach Gesetzesänderung oder Sondergesetz Auf Feiertagstabelle umstellen (Abschnitt 3.2)
Speicher-/Ausgabepfad mit parameterlosem ToString() Japanische Ära mischt sich auf einem auf Ären-Anzeige eingestellten Gerät in die gespeicherten Daten InvariantCulture plus explizites Format (Abschnitt 2.4)
Wiederholtes vorherigesDatum.AddMonths(1) Monatsende driftet nach Durchlaufen des Februars dauerhaft weiter Jedes Mal ab einem Bezugsdatum berechnen (Abschnitt 4.1)
new DateTime(y, m, 31) Löst für Monate mit 30 oder weniger Tagen eine Ausnahme aus Aus DateTime.DaysInMonth ableiten

5. Zusammenfassung

Japanische Ära, Feiertage und Abrechnungsstichtage haben alle gemeinsam, dass sie mit „Spezifikationen umgehen, die sich später ändern“. Deshalb gilt für alle drei dasselbe Designprinzip: Was sich ändern kann, nicht in Code schreiben, sondern in Daten und einer Umrechnungsschicht isolieren. Vereinheitlichen Sie intern und bei der Speicherung auf den gregorianischen Kalender und behandeln Sie die japanische Ära als reine Anzeigeumrechnung; führen Sie Feiertage in einer Tabelle mit Aktualisierungsprozess; dokumentieren Sie „Monatsende“, „Werktag“ und „Anpassung eines Termins bei Fall auf einen Feiertag“ als vertragliche Spezifikation, bevor Sie sie ab einem Bezugsdatum berechnen. Ist all dies erledigt, werden der nächste Äraübergang und die nächste Feiertagsgesetzesänderung für Ihre Anwendung zu Ereignissen, die sich mit einer bloßen „Aktualisierung der Tabelle“ und dem „Einspielen des OS-Updates“ erledigen lassen.

Wir übernehmen die Konzeption und Implementierung der Datumsverarbeitung rund um Belege und Rechnungsstellung, die Bestandsaufnahme und Überarbeitung von in bestehenden Anwendungen fest codierten Ären- und Feiertagsangaben sowie Design-Reviews einschließlich der Klärung von Abrechnungsstichtags-Spezifikationen. Wir können auch schon in der Phase helfen, in der Sie noch nicht wissen, an wie vielen Stellen im Code eine Äraübergangs-Nacharbeit nötig ist.

Verwandte Artikel

Verwandte Beratungsbereiche

KomuraSoft LLC (合同会社小村ソフト) übernimmt die Konzeption und Implementierung der Datumsverarbeitung für Business-Anwendungen entsprechend japanischer Geschäftsgepflogenheiten (Ären-Belege, Werktagsberechnung, Abrechnungsstichtagsverarbeitung) sowie die Bestandsaufnahme und Überarbeitung der Ären- und Feiertagsbehandlung in bestehenden Anwendungen.

Referenzen

  1. Microsoft Learn, Work with eras - .NET. Dazu, dass JapaneseCalendar und JapaneseLunisolarCalendar die einzigen Kalenderklassen sind, die mehrere Ären erkennen, zur Ären-Formatierung über den Formatbezeichner „g“ sowie dazu, dass Ären-Informationen von der Plattform geliefert werden und nicht von einer Hartkodierung in der Anwendung abhängen.  2 3

  2. Cabinet Office Japan, „Über nationale Feiertage“. Zur Liste der Feiertage nach dem Gesetz über nationale Feiertage, dazu, dass die Tagundnachtgleiche-Feiertage im Frühling und Herbst erst im Vorjahr festgelegt und bekanntgegeben werden, zu den Regelungen zu Ersatzfeiertag und nationalem Feiertag sowie zur Bereitstellung einer CSV mit Feiertagsdaten und -bezeichnungen von Showa 30 bis zum Folgejahr.  2 3 4 5

  3. Microsoft Learn, DateTime.AddMonths(Int32) Method. Dazu, dass das Ergebnis auf den letzten Tag des resultierenden Monats angepasst wird, wenn der resultierende Tag die Anzahl der Tage dieses Monats überschreitet (z. B. wird der 31. Januar plus ein Monat zum 28./29. Februar).  2

  4. Microsoft Learn, JapaneseCalendar Class. Zum von JapaneseCalendar unterstützten Datumsbereich — ab dem 8. September des Jahres Meiji 1 (1868 n. Chr.) — und zum Umgang mit Ären. 

  5. Microsoft Learn, Work with calendars - .NET. Zum Standardverhalten, das erste Jahr einer Ära als „Gan-nen“ zu formatieren, zum Schalter Switch.System.Globalization.FormatJapaneseFirstYearAsANumber, der die numerische Notation wiederherstellt, sowie dazu, dass die Prüfung des Jahresbereichs einer Ära standardmäßig gelockert ist, aber über Switch.System.Globalization.EnforceJapaneseEraYearRanges strikt eingestellt werden kann.  2

  6. Microsoft Learn, CultureInfo.CurrentCulture Property. Dazu, dass CurrentCulture standardmäßig Änderungen der Ländereinstellungen eines Windows-Benutzers (eine Benutzerüberschreibung) widerspiegelt und die Standardformatierung von dieser Kultur abhängt. 

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Windows-App-Entwicklung

Die Umsetzung japanspezifischer Geschäftsanforderungen wie Ären-Anzeige auf Belegen, Werktagsberechnung und Abrechnungsstichtagsverarbeitung gehört zum Kernbereich der Beratung für die Entwicklung von Windows-Business-Anwendungen.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Ist es falsch, Datumsangaben unverändert in der japanischen Ära in der Datenbank zu speichern?
Wir raten davon ab, aus vier Gründen. Erstens verlieren Ären-Strings wie „Heisei 9“ und „Reiwa 9“ beim reinen String-Vergleich sobald sie eine Äragrenze überschreiten ihre Ordnung, sodass Sortierung und Bereichsabfragen nicht mehr funktionieren. Zweitens vermischen sich in dem Moment, in dem die nächste Ära beginnt, alte und neue Schreibweisen in den gespeicherten Daten. Drittens schleichen sich leicht fehlerhafte Daten mit nie existierenden Datumsangaben ein – etwa „Showa 64, 10. Januar“ (an diesem Tag war bereits Heisei 1, da die Ära am 8. Januar wechselte) –, was zu einer Zeitbombe beim Parsen wird. Viertens unterstützt das JapaneseCalendar von .NET nur Daten ab dem 8. September des Jahres Meiji 1 (1868); ältere Daten – etwa ein altes Geburtsdatum aus einem Familienregister – lassen sich in Ären-Notation nicht korrekt verarbeiten. Der Grundsatz lautet, die interne Verarbeitung und Speicherung einheitlich auf den gregorianischen Kalender zu stützen und erst im Moment der Anzeige oder des Drucks in die japanische Ära umzurechnen.
Wie sollte sich eine Anwendung auf den nächsten Äraübergang vorbereiten?
Die Definition der Ärannamen ist nicht in .NET selbst hartkodiert, sondern verweist auf vom Betriebssystem bereitgestellte Ären-Informationen. Solange OS- und .NET-Updates eingespielt werden, folgt die Fähigkeit, „in der neuen Ära anzuzeigen“, also automatisch. Auch beim Übergang zu Reiwa bestand die eigentliche Reaktion größtenteils im Einspielen von Plattform-Updates. Das Problem liegt bei anwendungsseitigem Code und den Daten: hartkodierte Verzweigungen mit „Heisei“ oder „Reiwa“ als String, selbstgebaute Umrechnungsfunktionen mit eigener Ära-zu-Jahr-Zuordnungstabelle, direkt in Belegvorlagen geschriebene Ärannamen oder Felder wie „Reiwa __“ auf vorgedruckten Formularen folgen nicht automatisch. Vier praktische Maßnahmen sind sinnvoll: (1) intern und bei der Speicherung einheitlich auf den gregorianischen Kalender setzen, sodass die japanische Ära rein der Anzeige dient, (2) eigene Ären-Umrechnungslogik aufgeben und stattdessen JapaneseCalendar nutzen, (3) den gesamten Quellcode systematisch nach „Showa“, „Heisei“ und „Reiwa“ durchsuchen, um eine Bestandsaufnahme zu machen, und (4) Tests vorbereiten, die einen fiktiven künftigen Äraübergang simulieren.
Lassen sich Feiertage nicht per Formel bestimmen? Warum müssen sie als Daten vorgehalten werden?
Eine vollständige Formel lässt sich grundsätzlich nicht bauen, aus drei Gründen. Erstens werden der Frühlings- und der Herbst-Tagundnachtgleiche-Feiertag erst im Vorjahr durch Kabinettsbeschluss auf Basis astronomischer Berechnung festgelegt und sind nicht als feste Daten im Gesetz verankert. Zweitens werden Feiertage durch Gesetzesänderungen hinzugefügt, verschoben oder umbenannt: 2016 wurde der Tag des Berges neu eingeführt, 2019 verschob sich der Geburtstag des Kaisers infolge des Äraübergangs vom 23. Dezember auf den 23. Februar (**2019 gab es überhaupt keinen Geburtstag des Kaisers**), und der Tag des Sports und der Gesundheit wurde in Tag des Sports umbenannt. Drittens wurden 2020 und 2021 durch ein Sondergesetz im Zusammenhang mit den Olympischen Spielen der Tag des Meeres, der Tag des Sports und der Tag des Berges **nur für diese Jahre** verschoben – selbst eine Regel wie „immer der dritte Montag“ kann also punktuell außer Kraft gesetzt werden. Die richtige Lösung ist daher, die Feiertagsbestimmung als „Feiertagstabelle plus Aktualisierungsprozess“ zu gestalten und die Berechnungslogik auf Hilfsregeln wie Ersatzfeiertage zu beschränken.
Nach AddMonths(1) hat sich mein Zahlungsziel vom Monatsende verschoben. Warum?
Weil DateTime.AddMonths spezifiziert ist, auf den letzten Tag des resultierenden Monats abzurunden, wenn das naive Ergebnis auf einen Tag fiele, den es in diesem Monat gar nicht gibt. AddMonths(1) auf den 31. Januar ergibt den 28. Februar (in Schaltjahren den 29.) – dieses Verhalten selbst ist korrekt. Wird dieses gerundete Ergebnis jedoch in ein weiteres AddMonths übernommen, ergibt AddMonths(1) auf den 28. Februar den 28. März, und von da an driftet der Wert dauerhaft weiter: Aus dem beabsichtigten „Monatsende“ wird stillschweigend und dauerhaft „der 28.“. Die Abhilfe besteht aus zwei Teilen: Bei wiederholten Berechnungen stets vom festen Bezugspunkt ausgehen (dem Vertragsdatum oder der Definition von „jedes Monatsende“), statt auf das vorherige Ergebnis aufzuaddieren, und „Monatsende“ aus DateTime.DaysInMonth statt aus einem Datumsliteral ableiten.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Öffentliche Links

Zurück zum Blog