Japanese Era Dates, Public Holidays, and Closing-Date Processing in Business Apps — Era-Resilient Design, JapaneseCalendar, and Business-Day Calculations in Practice

· · C#, .NET, Japanese Era (Wareki), Public Holidays, Closing Date, Date/Time Handling, Business Applications, Windows Development, Technical Consulting

“Dates on the report should be in the Japanese era.” “Payment is due on a 20th-of-month closing, paid by the end of the following month — or the prior business day if that lands on a holiday.” “Totals should be aggregated by fiscal year.” Business applications in Japan come standard with date requirements that make sense nowhere else in the world. And this territory hides a different species of trap than ordinary date/time handling. Era names keep multiplying with each transition, public holidays move whenever the law changes, and “month-end” has a different length depending on the month. In other words, every one of these is a “specification that changes later.”

Last time, in “Date/Time and Time Zones in Business Applications,” we laid out the principle of storing in UTC and how to handle time zones. This time, as a follow-up, we cover Japan’s three big local requirements: the Japanese era, public holidays, and closing dates. We’ll walk through era-resilient design, why you must never hardcode public holidays and how to store them as data instead, and closing-date calculations that account for AddMonths’ rounding behavior — all with code samples and decision tables.

1. The Bottom Line First

  • Internal processing and storage stay in the Gregorian calendar; the Japanese era exists only at the moment of display. Storing era strings leads to a triple curse: unsortable data, mixed notations after an era change, and the risk of invalid dates creeping in.
  • Delegate era conversion to JapaneseCalendar rather than a homegrown mapping table. Era definitions are supplied by the OS, so a platform update is enough to follow an era change.1 Only hardcoded strings like “Heisei” or “Reiwa” left in the app get stranded.
  • Public holidays cannot be determined by formula. The Vernal and Autumnal Equinoxes are fixed the year before, and holidays themselves can be moved by ordinary legal revisions or special-measures laws.2 Design around a holiday master table plus an update process, and you can source the data from the CSV of public holidays published by Japan’s Cabinet Office.
  • “Month-end,” “business day,” and “fiscal year” should be defined in the spec before they’re defined in code. AddMonths in particular rounds month-end automatically, so closing-date calculations should always be computed from a fixed reference date.3

Here’s a summary of how to store each kind of data, up front.

Data How to store it When to convert/evaluate
The date itself (order date, payment due date) Gregorian DateOnly / DateTime (DB: a date column)
Japanese-era notation (reports, screens) Don’t store it Convert with JapaneseCalendar only at the moment of display/printing
Public holidays Holiday master table (DB table or config file) Referenced at business-day calculation time
Company closures (summer break, founding anniversary) A separate closure master table, distinct from public holidays Same as above (don’t mix with public holidays)
Closing date / payment terms An attribute on the counterparty master (“20th-of-month closing, paid end of following month”) Computed from the reference date at billing time
Fiscal year Don’t store it (a derived value) Computed from the date at display/aggregation time

2. The Japanese Era — Make It a Display-Only Concept

2.1 Why Standardize Internally on the Gregorian Calendar

The Japanese era is a human-facing notation, not a representation suited to computation. “Heisei 9” and “Reiwa 9” have no well-defined order under plain string comparison, and any period calculation that spans an era boundary ends up needing conversion to the Gregorian calendar anyway. Worse, the set of eras keeps growing with each transition, so data that stays stored in era format accumulates a mix of old and new notations every time the era changes. Invalid data like “Showa 64, January 10” (which never existed — that day was already Heisei 1, since the era changed on January 8) is a classic problem in systems that store raw era input as-is.

There’s another easily overlooked constraint: .NET’s JapaneseCalendar only supports dates from September 8, Meiji 1 (1868), onward.4 Anything earlier — an old birth date sourced from a family register, for instance — can’t be handled correctly while it stays in era format. Standardizing internally on the Gregorian calendar contains all of these problems within the display layer alone.

2.2 Displaying and Accepting Input with JapaneseCalendar

The only calendar classes in .NET that can handle multiple eras are JapaneseCalendar (and JapaneseLunisolarCalendar), and the format specifier g corresponds to the era name.1 Here’s the minimal code for displaying a date in the Japanese era.

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

For accepting era-based input, it’s safer to build a UI where the user picks the era from a combo box and enters the year/month/day as numbers, rather than parsing a free-text string. If you absolutely must accept free text, pin the format with ParseExact.

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

Notation for the first year of an era needs care. On environments where the era-support updates have been applied, year one of each era is formatted by default as “Reiwa Gan-nen” (元年, literally “the founding year”) rather than as a number, and there’s a compatibility switch — Switch.System.Globalization.FormatJapaneseFirstYearAsANumber — to restore the numeric “Reiwa 1” form.5 Confirm as part of the spec, before you implement anything, whether your report format expects “Reiwa 1” or “Reiwa Gan-nen.” One more thing: by default, the year-range check for each era is relaxed, so an out-of-range value like “Heisei 33” (which never existed) is accepted and converted to the actual corresponding date (Reiwa 3). If you need strict input validation that rejects such values, you can enforce it with Switch.System.Globalization.EnforceJapaneseEraYearRanges.5 Whether to leave it relaxed or make it strict comes down to how dirty your existing data is — specifically, how you want to handle a value like “Heisei 32” that was printed or entered before the era change.

2.3 Code That’s Resilient to Era Changes, and What Gets Left Behind

What we learned from the Reiwa transition (2019) is this: the platform keeps up, but an app’s homegrown implementation does not. Era definitions aren’t hardcoded into .NET — the mechanism references era information held by the OS — so as long as you keep applying OS and .NET updates, JapaneseCalendar’s conversion and formatting follow the new era automatically.1 What gets left behind is application-side assets like the following:

  • Homegrown era-conversion tables or branches, such as if (year >= 2019) era = "令和";
  • Era names written directly into report templates, Excel cell formats, or pre-printed paper forms
  • Code that handles a single-character era abbreviation like “R” or “H” with custom rules (common in interchange formats with core/legacy systems)
  • The stored data itself, still sitting in Japanese-era format

When estimating the cost of era-transition remediation, start by mechanically grepping the entire source tree and template set for “Showa,” “Heisei,” “Reiwa,” and “Gan-nen.” Business apps that have been running since the VB6 or Access era almost certainly have a homegrown conversion table buried somewhere, so this is often handled together with the survival-vs-migration decision described in “Extending the Life of, or Migrating, a VB6 / Access Business Application.”

2.4 A Sneaky Culprit: Windows Regional Settings

There’s one more easily missed trap. In Windows’ regional settings, a user can switch the OS’s calendar type to the Japanese era. Because CultureInfo.CurrentCulture reflects the user’s setting change (a “user override”) by default,6 on a machine with this setting, culture-default DateTime.ToString() calls will suddenly start returning Japanese-era strings like “令和08/07/11.” If your code writes dates to logs, CSV, or the database using culture-default ToString(), then only on that one machine, era-format text ends up mixed into stored data, breaking downstream parsing.

The countermeasure comes back to the same conclusion as last time: any date string crossing a boundary — storage, communication, logging — must always be written out with CultureInfo.InvariantCulture and an explicit format string. Culture-default formatting is for on-screen display only. For a broader look at how culture affects display, see also “Localizing WinForms/WPF Applications.”

3. Public Holidays — Why You Must Not Hardcode Them, and How to Run Master-Data Operations

3.1 Public Holidays “Change”

Never write holiday detection as a switch (month, day). Public holidays are defined by the Act on National Holidays, and they’ve repeatedly changed through legal revisions and operational rules.2 Just the recent examples:

Year Change
2016 Mountain Day (August 11) is newly established
2019 Following the era change, the Emperor’s Birthday moves from December 23 to February 23. There was no Emperor’s Birthday at all in 2019
2019 Under special provisions tied to the Imperial succession, May 1 (Enthronement Day) and others became temporary holidays
2020 Health-Sports Day is renamed Sports Day
2020–2021 Under Olympic special-measures legislation, Marine Day, Sports Day, and Mountain Day moved for those years only

On top of that, the Vernal and Autumnal Equinox Days simply have no date written into law. They’re fixed and announced the previous year based on astronomical calculation, so the only reliable way to know a future equinox date is to wait for the official announcement.2 A formula that assumes “holidays are the same every year” is, in principle, unworkable.

3.2 A Decision Table for Data Sources and Storage

Design holiday determination as “data plus an update process.” Here’s a comparison of the main options.

Approach What it is Good fit Caveats
Holiday master table (self-managed) A table of dates and names, updated through an admin screen Business systems in general that want to manage this alongside company closures Build the update into an operational routine (turn the annual check into a calendar reminder)
Import from the Cabinet Office CSV Import the CSV of public holidays published by the Cabinet Office to update the master table When you want an authoritative primary source for master-data updates (recommended) Only contains the period that’s already been published. Watch the character encoding
Holiday library (NuGet, etc.) Determine holidays via calculation logic plus bundled data Small-scale uses like internal tools that can’t sustain an update process Future legal revisions depend on the library being updated. You need to verify the rules it bundles
Hardcoding Dates written directly into source None Every legal revision forces a redistribution to every client

As a primary source, you can use the CSV of holiday dates and names from Showa 30 (1955) through the following year, published by the Cabinet Office on its “National Holidays” page.2 There are two practical cautions when importing it: (1) since only confirmed data is published, decide as part of the spec how to behave when asked about a holiday two or more years out — return an error, or fill in a provisional value based on the law with a “provisional” flag — and (2) this CSV’s character encoding is Shift_JIS-family, so make sure to specify the encoding explicitly when reading it, as described in “CSV Isn’t Just ‘Plain Text’.”

3.3 Substitute Holidays, National Holidays, and Business-Day Calculation

Beyond the list of dates, the holiday law defines two derived rules: substitute holidays (when a holiday falls on a Sunday, the nearest following day that isn’t itself a holiday becomes a day off), and national holidays (a weekday sandwiched between two holidays becomes a day off).2 The Cabinet Office CSV includes these as rows too, so if you use the CSV-import approach, referencing the master table is all you need. Even if you compute this logic yourself, it’s relatively stable since it’s rooted in law.

Write business-day calculation with three conditions: not a weekend, not in the holiday master, and not in the company-closure master.

public sealed class BusinessDayCalendar
{
    private readonly HashSet<DateOnly> _publicHolidays;   // Public holiday master (includes substitute holidays)
    private readonly HashSet<DateOnly> _companyHolidays;  // Company holiday master

    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);

    // If the due date falls on a holiday, roll back to the previous business day. Whether to roll forward instead is decided by the contract terms
    public DateOnly ToPreviousBusinessDay(DateOnly d)
    {
        while (!IsBusinessDay(d)) d = d.AddDays(-1);
        return d;
    }
}

Keeping public holidays and company closures in separate master tables is deliberate. “Whether a bank-transfer due date counts a holiday only looks at public holidays (our summer closure is irrelevant),” “whether a shipment date counts holidays looks at both” — which set of days off applies depends on the use case. If you merge them into a single table, separating them out again later becomes an archaeology project through already-stored data.

4. Closing Dates, Payment Terms, and Fiscal Years — Handling “Month-End” Correctly in Calculations

4.1 The Rounding Behavior of AddMonths

The first trap in closing-date calculation is AddMonths. DateTime.AddMonths / DateOnly.AddMonths rounds down to the last day of the resulting month whenever the naive result would land on a day that doesn’t exist in that month.3

var jan31 = new DateOnly(2026, 1, 31);
var feb = jan31.AddMonths(1);   // 2026-02-28 ── rounded to month-end (correct)
var mar = feb.AddMonths(1);     // 2026-03-28 ── what was meant to be "month-end" silently turns into the 28th

The rounding itself is correctly specified, but chaining another AddMonths onto an already-rounded result loses the intent of “month-end.” This surfaces in practice when a monthly billing schedule is built as “previous run date plus one month” — the moment it passes through February, it permanently mutates into running on the 28th forever after. There are two countermeasures:

  • For repeating dates, always compute from the definition of the reference point (“every month-end,” “the 20th of every month”), never from the previous result
  • Derive “month-end” from DateTime.DaysInMonth(y, m) rather than a literal date

4.2 Implementing “20th-of-Month Closing, Paid at End of Following Month”

The closing date and payment terms are attributes per counterparty. Coding “20th-of-month closing, end-of-following-month payment” looks like this:

public static class PaymentTerms
{
    // Trade date → closing date (example: closing on the 20th of each month)
    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);
        // Note: closingDay=31 (month-end closing) correctly becomes the following month-end thanks to AddMonths' rounding
    }

    // Closing date → payment due date (example: paid at the end of the following month). Confirm the holiday-adjustment spec (roll back / roll forward) before applying it
    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);  // For contracts using the "if a holiday, roll back to the previous business day" rule
    }
}

What matters more than the code is nailing down the spec ahead of it. Is “month-end” for a “month-end closing” a calendar day or a business day? If the due date falls on a holiday, does it move earlier or later (the payer tends to move it earlier; the recipient’s expected receipt date tends to move later — convention differs by which side you’re on)? How should a contract with a 29th–31st closing day be interpreted in February? When these things aren’t documented, whatever the implementer’s code happens to do becomes “a spec nobody actually agreed to.” Most closing-date bugs, when investigated, trace back not to a coding mistake but to a spec that was never written down.

4.3 Fiscal Year and Quarter

In Japan, a business application’s “fiscal year” almost always starts in April. Treat it as a value derived from the date, not something you store.

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

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

The thing to watch is: don’t casually redundantly store the fiscal year in the DB. If you keep something that’s always derivable from a date in a separate column, a missed update when the date is corrected produces data where the date and the fiscal year disagree. If you must keep it for aggregation performance, make it a computed (generated) column and forbid the application from writing to it directly.

4.4 Testing — Catch “Only-On-That-Date” Bugs Before They Catch You

Bugs in era, holiday, and closing-date handling only ever manifest on specific dates. Building on the current-time injection via TimeProvider introduced in the previous article (Chapter 7 of “Date/Time and Time Zones in Business Applications”), here are the five dates you should test in this area, at minimum:

  • Era boundaries: display and reverse-conversion of era input around 1989-01-07/08 (Showa → Heisei) and 2019-04-30/05-01 (Heisei → Reiwa)
  • First year of an era: confirming the “Reiwa Gan-nen” report notation (see Section 2.2)
  • Leap years: closing/payment-term calculations that span February 29 (2024, 2028)
  • A chain of month-ends: generating twelve months of closing dates starting from January 31, and checking that month-end is preserved throughout (Section 4.1)
  • Fiscal-year boundaries: fiscal-year and quarter determination on March 31 and April 1

For code review, the following patterns can also be caught mechanically with grep:

If you find this, be suspicious What can happen The fix
Era literals like "平成" or "令和" Gets left behind at the next era change Move to JapaneseCalendar plus the g format (Section 2.3)
Literal holiday dates (like 5, 3) Misjudged after a legal revision or special-measures law Reference the holiday master table instead (Section 3.2)
A save/output path using parameterless ToString() Era-format text leaks into stored data on a machine set to era display Use InvariantCulture plus an explicit format (Section 2.4)
Repeating previousDate.AddMonths(1) Month-end keeps drifting after passing through February Compute from a reference date each time (Section 4.1)
new DateTime(y, m, 31) Throws for any month with 30 or fewer days Derive it from DateTime.DaysInMonth

5. Summary

Era handling, public holidays, and closing dates are all, at bottom, dealing with “specifications that change later.” So the design principle is the same across all three: don’t write the things that change into code — isolate them in data and a conversion layer. Standardize internally and in storage on the Gregorian calendar, treating the Japanese era as a display-only conversion; keep public holidays in a master table with an update process; and document “month-end,” “business day,” and “how a due date adjusts around a holiday” as contractual specs before computing them from a reference date. Do all of that, and the next era transition, and the next holiday-law revision, become events your app handles with nothing more than “update the master table” and “apply the OS update.”

We handle the design and implementation of date processing around reports and billing, auditing and remediating era-name and holiday hardcoding buried in existing applications, and design reviews that include sorting out closing-date specifications. We can help even from the stage where you don’t yet know how many places in your codebase need era-transition remediation.

KomuraSoft LLC handles the design and implementation of date processing for business applications aligned with Japanese business customs (era-based reporting, business-day calculation, closing-date processing), as well as auditing and remediating era-transition and holiday handling in existing applications.

References

  1. Microsoft Learn, Work with eras - .NET. On JapaneseCalendar and JapaneseLunisolarCalendar being the only calendar classes that recognize multiple eras, era formatting via the “g” format specifier, and era information being supplied by the platform rather than depending on application hardcoding.  2 3

  2. Cabinet Office of Japan, “About National Holidays”. On the list of public holidays under the Act on National Holidays, the Vernal and Autumnal Equinox Days being fixed and announced the previous year, the substitute-holiday and national-holiday provisions, and the availability of a CSV of holiday dates and names from Showa 30 through the following year.  2 3 4 5

  3. Microsoft Learn, DateTime.AddMonths(Int32) Method. On how, when the resulting day exceeds the number of days in the resulting month, the result is adjusted to the last day of that month (for example, January 31 plus one month becomes February 28/29).  2

  4. Microsoft Learn, JapaneseCalendar Class. On the date range JapaneseCalendar supports — from September 8, Meiji 1 (1868 CE), onward — and how it handles eras. 

  5. Microsoft Learn, Work with calendars - .NET. On the default behavior of formatting an era’s first year as “Gan-nen,” the Switch.System.Globalization.FormatJapaneseFirstYearAsANumber switch that restores numeric notation, and how era year-range checking is relaxed by default but can be made strict with Switch.System.Globalization.EnforceJapaneseEraYearRanges.  2

  6. Microsoft Learn, CultureInfo.CurrentCulture Property. On CurrentCulture reflecting a Windows user’s regional-setting changes (a user override) by default, and default formatting depending on this culture. 

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Windows App Development

Implementing Japan-specific business requirements such as era-based report formatting, business-day calculation, and closing-date processing sits squarely within the core scope of Windows business-application development consulting.

Frequently Asked Questions

Common questions about the topic of this article.

Is it wrong to store dates in Japanese-era format in the database?
We don't recommend it, for four reasons. First, era strings like “Heisei 9” and “Reiwa 9” break ordering under plain string comparison once they cross an era boundary, making sorting and range queries impossible. Second, the moment the next era change happens, old and new notations end up mixed together in stored data. Third, dirty data containing dates that never existed — such as “Showa 64, January 10” (that day was already Heisei 1, since the era changed on January 8) — tends to creep in, becoming a parsing time bomb. Fourth, .NET's JapaneseCalendar only supports dates from September 8, Meiji 1 (1868) onward, so anything earlier — such as an old birth date sourced from a family register — can't be handled correctly while kept in era format. The rule of thumb is to standardize internal processing and storage on the Gregorian calendar, and convert to the Japanese era only at the moment of display or printing.
How should an app prepare for the next era change?
The definition of era names isn't hardcoded into .NET itself — it's designed to reference era information supplied by the OS — so as long as you keep applying OS and .NET updates, the ability to “display the new era” follows automatically. During the Reiwa transition, the bulk of the response really was just applying platform updates. The real problem is application-side code and data. Branches that hardcode “Heisei” or “Reiwa” as strings, homegrown conversion functions with their own era-to-Gregorian-year mapping tables, era names written directly into report templates, and “Reiwa __ year” fields on pre-printed forms — none of these follow automatically. Four practical preparations are worth making: (1) standardize internally and in storage on the Gregorian calendar so the Japanese era is display-only, (2) stop maintaining your own era-conversion logic and delegate it to JapaneseCalendar, (3) grep the entire source tree for “Showa,” “Heisei,” and “Reiwa” to take inventory, and (4) prepare tests that assume a hypothetical future era transition.
Can't public holidays be determined with a formula? Why do they need to be stored as data?
A complete formula is fundamentally impossible to build, for three reasons. First, the Vernal and Autumnal Equinox Days are fixed the previous year via a cabinet decision based on astronomical calculation — they aren't written into law as fixed dates. Second, holidays get added, moved, or renamed through legal revisions: Mountain Day was newly established in 2016, the Emperor's Birthday moved from December 23 to February 23 in 2019 (and there was no Emperor's Birthday at all in 2019), and Health-Sports Day was renamed Sports Day. Third, in 2020 and 2021, special-measures legislation temporarily moved Marine Day, Sports Day, and Mountain Day to align with the Olympics — meaning even a rule like “always the third Monday” can be overridden on a one-off basis. The correct design, therefore, is a “holiday master table plus an update process,” with calculation logic limited to auxiliary rules such as substitute holidays.
My payment due date shifted away from month-end after I used AddMonths(1). Why?
Because DateTime.AddMonths is specified to round down to the last day of the resulting month whenever the naive result would land on a day that doesn't exist in that month. Calling AddMonths(1) on January 31 gives you February 28 (or 29 in a leap year) — that behavior itself is correct. But if you then carry that rounded result forward into yet another AddMonths call, applying AddMonths(1) to February 28 gives you March 28, and from then on it keeps drifting: what was meant to be “month-end” silently and permanently turns into “the 28th.” The fix has two parts: for repeating calculations, always compute from a fixed reference point (the contract date, or the definition of “every month-end”) rather than adding to the previous result, and derive “month-end” from DateTime.DaysInMonth rather than a literal date.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog