業務應用程式的日本年號・國定假日・結算日處理 ── 抗改元設計與 JapaneseCalendar・營業日計算實務
· Go Komura · C#, .NET, 日本年號(和曆), 國定假日, 結算日, 日期時間處理, 業務應用程式, Windows 開發, 技術諮詢
「報表的日期要用年號」「付款期限是20日結算、次月底付款,若月底是假日則提前至前一個營業日」「彙總以會計年度為單位」──日本的業務應用程式標準配備了只在這個國家才通用的日期需求。而這個領域埋藏著與一般日期時間處理截然不同的陷阱。年號會隨改元增加,國定假日會隨法規修訂而變動,「月底」則因月份而長度不同。換句話說,這些全都是「日後會變動的規格」。
上一篇「業務應用程式的日期時間與時區」整理了以 UTC 儲存的原則以及時區的處理方式。這一次作為續篇,將處理日本在地的三大需求──年號・國定假日・結算日。從抗改元的設計、國定假日不可寫死在程式碼中的理由與資料化的方式,一直到考量 AddMonths 進位規則的結算日計算,將透過程式範例與判斷表逐一說明。
1. 先講結論
- 內部處理與儲存一律採用西曆(格里曆),年號只存在於顯示的那一瞬間。 以年號字串儲存會導致三重問題:無法排序、改元時表示法混雜、以及混入不正確日期的風險。
- 年號轉換交給
JapaneseCalendar,而非自製對照表。年號的定義由作業系統端供給,因此改元時只要套用平台更新即可跟上。1 唯有留在應用程式中「平成」「令和」的寫死字串會被拋下。 - 國定假日無法用公式判定。 春分與秋分是在前一年才確定,且國定假日本身會因一般法規修訂或特別措施法而變動。2 應以國定假日主檔+更新作業的方式設計,資料來源可使用內閣府公布的國定假日 CSV。
- 「月底」「營業日」「會計年度」應先在規格中定義,而非先寫進程式碼。尤其
AddMonths會自動對月底進行進位,因此結算日的計算原則上應每次都從基準日重新計算。3
以下先彙整各類資料的保存方式。
| 資料 | 保存方式 | 轉換・判定的時機 |
|---|---|---|
| 日期本身(受訂日・付款期限) | 西曆的 DateOnly / DateTime(資料庫為 date 型別) |
─ |
| 年號表示(報表・畫面) | 不保存 | 僅在顯示・列印瞬間以 JapaneseCalendar 轉換 |
| 國定假日 | 國定假日主檔(資料表或設定檔) | 於營業日計算時參照主檔 |
| 公司休業日(夏季休假・創立紀念日) | 與國定假日分開的休業日主檔 | 同上(不與國定假日混用) |
| 結算日・付款條件 | 客戶主檔的屬性(「20日結算次月底付款」) | 請款處理時從基準日計算 |
| 會計年度 | 不保存(導出值) | 顯示・彙總時由日期計算 |
2. 年號 ── 讓它只存在於顯示層
2.1 為何內部要統一為西曆
年號是給人看的表示法,並不是適合計算的表現形式。「平成9年」與「令和9年」在字串比較下無法決定順序,任何跨越年號的期間計算最終都需要轉換為西曆。而且年號體系會隨每次改元而增加,因此以年號原樣保存的資料,每次改元都會累積新舊表示法混雜的問題。像「昭和64年1月10日」(實際不存在,1月8日起已是平成)這類不正確資料的混入,也是把年號輸入原封不動保存的系統中常見的麻煩。
還有一個容易被忽略的限制:.NET 的 JapaneseCalendar 支援的範圍僅限明治元年(1868年)9月8日以後。4 在此之前的日期──例如源自戶籍的舊生日──若仍以年號形式保存將無法正確處理。只要內部統一為西曆格里曆,這些問題就都會被侷限在「顯示層的問題」而已。
2.2 以 JapaneseCalendar 顯示與輸入
.NET 中能處理多個年號(era)的行事曆只有 JapaneseCalendar(與 JapaneseLunisolarCalendar),格式指定字元 g 對應年號名稱。1 以年號顯示的最簡程式碼如下。
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
若要接受年號輸入,比起解析自由輸入的文字字串,設計成「以下拉選單選擇年號、年月日以數字輸入」的介面更為安全。若無論如何都必須接受文字輸入,請以 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
元年的表示法需要留意。在已套用改元支援更新的環境中,各年號的第一年預設會格式化為「令和元年」這類元年表示,並提供了可還原為數字「令和1年」的相容性開關(Switch.System.Globalization.FormatJapaneseFirstYearAsANumber)。5 報表格式要求的究竟是「令和1年」還是「令和元年」,應在實作前作為規格予以確認。另外,預設情況下年號的年份範圍檢查是寬鬆的,因此像「平成33年」這種實際不存在的年份指定也會被接受並換算為實際日期(令和3年)。若要作為輸入驗證嚴格擋下,可透過 Switch.System.Globalization.EnforceJapaneseEraYearRanges 強制執行。5 究竟要維持寬鬆還是改為嚴格,取決於既有資料的髒污程度(該如何處理改元前列印・輸入的「平成32年」)。
2.3 抗改元的程式碼,以及被拋下的部分
從令和改元(2019年)的經驗可以歸納出:平台會跟上,但應用程式自製的實作不會跟上。 年號的定義並未寫死在 .NET 中,而是參照作業系統端所持有的年號資訊,因此只要持續套用作業系統與 .NET 的更新,JapaneseCalendar 的轉換・格式化就會跟上新年號。1 會被拋下的是以下這類應用程式端的資產:
- 像
if (year >= 2019) era = "令和";這類自製的年號轉換表・分支 - 直接寫在報表範本、Excel 儲存格格式、印刷表單上的年號
- 以自訂規則處理「R」「H」等單一字母年號縮寫的程式碼(在與核心系統串接的格式中常見)
- 仍以年號原樣保存的資料本身
在估算改元因應改修時,應先對整個原始碼與範本以「昭和・平成・令和・元年」進行機械式 grep 盤點。從 VB6 或 Access 時代就在運作的業務應用程式,幾乎必定埋有自製的轉換表,因此這個議題常與「VB6 / Access 業務應用程式的延壽與遷移」中提到的存續判斷一併處理。
2.4 潛藏的伏兵:Windows 地區設定
還有一個容易被忽略的陷阱。在 Windows 的地區設定中,使用者可以將作業系統的行事曆類型變更為「年號」。由於 CultureInfo.CurrentCulture 預設會反映使用者的設定變更(使用者覆寫),6 在此設定下的裝置,文化特性預設的 DateTime.ToString() 會突然開始回傳如「令和08/07/11」這類年號字串。若程式碼是以文化特性預設的 ToString() 將日期寫入記錄檔、CSV 或資料庫,就會導致僅在該裝置上,儲存資料混入年號,破壞後續的剖析處理。
對策與前一篇文章的結論相同:任何跨越邊界(儲存・通訊・記錄)的日期字串,都必須以 CultureInfo.InvariantCulture 與明確格式輸出。 文化特性預設格式僅供畫面顯示使用。文化特性對顯示層整體的影響,也可參考「WinForms/WPF 應用程式的多語系化」。
3. 國定假日 ── 不可寫死在程式碼中的理由與主檔運作
3.1 國定假日「會變動」
國定假日的判定絕不能寫成 switch (月, 日)。國定假日是依據國民的節日相關法律所訂定,因法規修訂與實務運作而多次變動過。2 光是近年的實際案例就有:
| 年份 | 變動內容 |
|---|---|
| 2016 | 新設山之日(8月11日) |
| 2019 | 因應改元,天皇誕生日從12月23日移到2月23日。2019年當年並不存在天皇誕生日 |
| 2019 | 因皇位繼承的特例,5月1日(即位之日)等成為臨時國定假日・休日 |
| 2020 | 體育之日改名為運動之日 |
| 2020・2021 | 依奧運特別措施法,海之日・運動之日・山之日僅在當年移動 |
此外,春分之日與秋分之日原本就沒有寫進法律的固定日期。 這是根據天文計算,於前一年才確定並公布的機制,因此確實掌握未來春分・秋分日期的唯一方法就是等待官方公布。2 「國定假日每年都相同」這種前提的計算公式,原理上根本不成立。
3.2 資料來源與保存方式判斷表
國定假日的判定應以「資料+更新作業」來設計。以下比較主要的選項。
| 方式 | 內容 | 適合情境 | 注意事項 |
|---|---|---|---|
| 國定假日主檔(自行管理) | 資料表中保存年月日與名稱,透過管理畫面更新 | 想與公司休業日一併運作的一般業務系統 | 將更新納入業務運作(把每年一次的確認排入行事曆) |
| 匯入內閣府 CSV | 匯入內閣府公布的國定假日 CSV 以更新主檔 | 想要主檔更新的第一手資料來源時(建議做法) | 只涵蓋已公布的期間,須留意文字編碼 |
| 國定假日程式庫(NuGet 等) | 以計算邏輯+內建資料進行判定 | 內部工具等無法維持更新作業的小規模用途 | 未來的法規修訂需仰賴程式庫更新,須驗證內建規則 |
| 寫死程式碼 | 直接把日期寫在原始碼中 | 無 | 每次法規修訂都需要對所有用戶端重新發布 |
作為第一手資料來源,可使用內閣府在「國民的節日」頁面公布的自昭和30年(1955年)至隔年為止的國定假日月日・名稱 CSV。2 匯入時實務上須注意兩點:(1) 公布的僅為已確定的部分,因此應在規格中決定被查詢後年以後的國定假日時的行為(回傳錯誤,或以法律為基礎填入暫定值並附上「暫定」旗標);(2) 此 CSV 的文字編碼屬於 Shift_JIS 系列,讀取時須如「CSV並非單純的「文字檔」」中所述明確指定編碼。
3.3 補假・國民休日與營業日計算
除了日期清單之外,國定假日相關法律還定義了兩條衍生規則:補假(國定假日若恰逢星期日,則其後最接近的「非國定假日之日」訂為休日)與國民休日(前後兩日皆為國定假日所夾的平日訂為休日)。2 內閣府 CSV 中也將這些以資料列形式包含在內,因此若採用 CSV 匯入方式,只需參照主檔即可。即使自行實作補完這項邏輯,由於其源自法律,相對穩定。
營業日計算應以「非週末・不在國定假日主檔中・不在公司休業日主檔中」三項條件撰寫。
public sealed class BusinessDayCalendar
{
private readonly HashSet<DateOnly> _publicHolidays; // 國定假日主檔(含補假)
private readonly HashSet<DateOnly> _companyHolidays; // 公司休假日主檔
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);
// 若到期日為假日,則「提前至前一個營業日」。是否延後由合約規格決定
public DateOnly ToPreviousBusinessDay(DateOnly d)
{
while (!IsBusinessDay(d)) d = d.AddDays(-1);
return d;
}
}
將國定假日與公司休業日分成不同的主檔是刻意的設計。「銀行匯款期限的判定只看國定假日(自家公司的夏季休假無關)」「出貨日的判定則兩者都看」──依用途不同,需要參照的休日集合也不同。若混在同一個主檔中,日後要分離的改修工程就會變成對既有資料的考古挖掘。
4. 結算日・付款條件・會計年度 ── 以計算正確處理「月底」
4.1 AddMonths 的進位規則
結算日計算的第一個陷阱是 AddMonths。DateTime.AddMonths / DateOnly.AddMonths 在計算結果落在該月不存在的日期時,會進位到該月的最後一天。3
var jan31 = new DateOnly(2026, 1, 31);
var feb = jan31.AddMonths(1); // 2026-02-28 ── 進位到月底(正確)
var mar = feb.AddMonths(1); // 2026-03-28 ── 原本想要「月底」卻變成了 28 日
進位本身是正確的規格,但若對已經進位過的結果再次套用 AddMonths,「月底」這個意圖就會消失。 若每月請款排程以「上次執行日+1個月」建構,就會在通過2月的瞬間,讓執行日永久變成28日,此問題便會浮現。對策有兩個:
- 重複的日期不要以前一次的結果為基準,而是每次都從基準的定義(「每月底」「每月20日」)重新計算
- 「月底」不要使用日期字面值,改由
DateTime.DaysInMonth(y, m)推導
4.2 「20日結算次月底付款」的實作
結算日與付款條件是每個往來對象的屬性。將「20日結算次月底付款」寫成程式碼如下。
public static class PaymentTerms
{
// 交易日 → 結算日(每月 20 日結算範例)
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);
// 註:closingDay=31(月底結算)會因 AddMonths 的進位而正確變成下個月底
}
// 結算日 → 付款期限(次月月底付款範例)。假日調整請先確認規格(提前/延後)再套用
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); // 適用於「若為假日則提前至前一個營業日」的合約條件
}
}
比程式碼更重要的,是在此之前規格的確定。「月底結算」的月底,究竟是曆日還是營業日?期限若落在假日,該提前還是延後(付款方傾向提前,收款方預計入帳日則傾向延後──立場不同慣例也不同)?29~31日結算的合約在2月該如何解讀?──若這些沒有文件化,實作者的程式碼就會原封不動地變成「沒有任何人同意過的規格」。結算日相關的錯誤調查,多數最後追查到的不是程式碼的錯誤,而是規格的缺席。
4.3 會計年度與季度
日本業務應用程式的「會計年度」幾乎都是從4月開始。會計年度不應保存,而應作為從日期導出的值來處理。
public static int GetFiscalYear(DateOnly d) => d.Month >= 4 ? d.Year : d.Year - 1;
// 2026-03-31 → 2025 財年,2026-04-01 → 2026 財年
public static int GetFiscalQuarter(DateOnly d) => ((d.Month + 8) % 12) / 3 + 1;
// 4-6 月 → Q1,7-9 月 → Q2,10-12 月 → Q3,1-3 月 → Q4
須注意不要輕率地將「會計年度」冗餘保存到資料庫中。若把只要有日期就能隨時導出的值另外保存在一個欄位,一旦修正日期時忘記同步更新,就會產生日期與會計年度不一致的資料。若因彙總效能考量無論如何都必須保存,請將其做成計算欄(產生型欄位),並禁止應用程式端直接寫入。
4.4 測試 ── 先踩過「只在那一天」才會發作的錯誤
年號・國定假日・結算日的錯誤,只會在特定日期發作。以前一篇文章介紹的透過 TimeProvider 注入目前時間(「業務應用程式的日期時間與時區」第7章)為前提,這個領域至少應測試以下五個日期:
- 改元邊界:1989-01-07/08(昭和→平成)、2019-04-30/05-01(平成→令和)的顯示,以及年號輸入的反向轉換
- 元年:「令和元年」表示法的報表確認(2.2節)
- 閏年:包含2月29日的結算・付款條件計算(2024年、2028年)
- 月底的連鎖:以1月31日為起點產生12個月份的結算日,確認月底是否維持(4.1節)
- 會計年度邊界:3月31日與4月1日的會計年度・季度判定
此外,在程式碼審查時,以下模式也能透過 grep 機械式地找出。
| 發現時應懷疑的程式碼 | 可能發生什麼問題 | 修正方式 |
|---|---|---|
"平成" "令和" 等年號字面值 |
下次改元時被拋下 | 改用 JapaneseCalendar + g 格式(2.3節) |
國定假日的日期字面值(如 5, 3) |
因法規修訂・特別措施法而誤判 | 改為參照國定假日主檔(3.2節) |
無參數 ToString() 的儲存・輸出路徑 |
在年號設定裝置上,儲存資料混入年號 | InvariantCulture +明確格式(2.4節) |
重複使用 前一日期.AddMonths(1) |
通過2月後月底持續偏離 | 每次都從基準日重新計算(4.1節) |
new DateTime(y, m, 31) |
在30日以下的月份會拋出例外 | 由 DateTime.DaysInMonth 推導 |
5. 總結
年號・國定假日・結算日,處理的全都是「日後會變動的規格」。因此設計原則也是共通的:會變動的東西不要寫進程式碼,而是隔離到資料與轉換層中──內部與儲存統一為西曆,年號僅作為顯示用轉換;國定假日以主檔+更新作業的方式管理;「月底」「營業日」「期限的假日調整」則先以合約規格文件化,再以基準日為準進行計算。做到這個地步,下一次改元、下一次國定假日法規修訂,對應用程式而言就只會是「更新主檔」與「套用作業系統更新」即可解決的事件。
本公司承接報表・請款相關日期處理的設計與實作、既有應用程式中埋藏的年號・國定假日寫死問題的盤點與改修,以及包含結算日規格整理在內的設計審查。即使還處於「不知道改元因應散落在哪裡、有多少項」的階段,我們也能提供協助。
相關文章
- 業務應用程式的日期時間與時區 ── 從 DateTime 的陷阱到 UTC 儲存原則、測試設計
- CSV並非單純的「文字檔」 ── C# 業務應用程式的 CSV 實務(文字編碼・Excel 相容性・注入攻擊對策)
- WinForms/WPF 應用程式的多語系化 ── resx・附屬組件・文化特性切換實務
- VB6 / Access 業務應用程式的延壽與遷移 ── 保留・包裝・替換的判斷表
- Excel 報表輸出的實作方式 - COM/Open XML/範本
相關諮詢領域
合同會社小村軟體處理符合日本商業慣例的業務應用程式日期處理(年號報表・營業日計算・結算日處理)的設計與實作,以及既有應用程式的改元因應・國定假日因應的盤點與改修。
參考連結
-
Microsoft Learn, Work with eras - .NET. 關於 JapaneseCalendar 與 JapaneseLunisolarCalendar 是唯二能識別多個年號(era)的行事曆類別、透過格式指定字元「g」進行年號格式化,以及年號資訊由平台端供給、不依賴應用程式寫死的說明。 ↩ ↩2 ↩3
-
日本內閣府, 關於「國民的節日」. 關於依國民的節日相關法律訂定的國定假日清單、春分之日・秋分之日於前一年確定並公布、補假・國民休日的規定,以及提供自昭和30年至隔年為止的國定假日月日・名稱 CSV 的說明。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, DateTime.AddMonths(Int32) Method. 關於相加結果的日期超過結果月份的天數時,會調整為結果月份最後一天(例如1月31日+1個月會變成2月28日/29日)的說明。 ↩ ↩2
-
Microsoft Learn, JapaneseCalendar Class. 關於 JapaneseCalendar 支援的日期範圍為明治元年(西元1868年)9月8日以後,以及年號(era)處理方式的說明。 ↩
-
Microsoft Learn, Work with calendars - .NET. 關於將年號第一年格式化為「元年」的預設行為與還原為數字表示的 Switch.System.Globalization.FormatJapaneseFirstYearAsANumber 開關,以及年號的年份範圍檢查預設為寬鬆、可透過 Switch.System.Globalization.EnforceJapaneseEraYearRanges 強制嚴格化的說明。 ↩ ↩2
-
Microsoft Learn, CultureInfo.CurrentCulture Property. 關於 CurrentCulture 預設會反映 Windows 使用者的地區設定變更(使用者覆寫),以及格式化預設值依賴此文化特性的說明。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Windows 應用程式的工作列通知區常駐與 Toast 通知 —— NotifyIcon 的陷阱與 AppNotification 的選型
本文整理將業務用 Windows 應用程式常駐於工作列通知區,並透過 Toast 通知告知使用者的實作要點。內容涵蓋 NotifyIcon 的正確用法與「關閉後仍留在通知區」的設計、檔案總管重新啟動時的重新註冊、三種 Toast API(Windows App SDK Ap...
VB6 應用程式能用到什麼時候 ── 執行環境的支援現況與務實的 .NET 遷移做法
VB6 應用程式究竟能用到什麼時候?本文整理 VB6 執行環境的支援政策(Windows 11 也在支援範圍內)與 IDE 支援早已終止這種不對稱現況,並以實務指南的形式,說明全面重寫、自動轉換、階段性遷移的判斷表、遷移前的資產盤點、VB6 與 .NET 的不相容之處,以及...
防止 Windows 應用程式重複啟動 ── 具名 Mutex 與重複執行時的視窗前置
整理業務用 Windows 應用程式的常見需求「不讓同一個應用程式啟動兩次」,如何用具名 Mutex 實作。涵蓋 Global\ 與 Local\ 命名空間差異在 RDP 環境中的陷阱、擁有執行緒限制與 AbandonedMutexException、考量 SetForeg...
業務應用程式的日期時間與時區 ── 從 DateTime 的陷阱到 UTC 儲存原則、測試設計
伺服器搬遷後時間整整差了 9 個小時、只有海外據點的日期會變成前一天──本文從 DateTime 的 Kind 與隱式轉換整理日期時間事故的根本原因。內容涵蓋與 DateTimeOffset 的取捨、UTC 儲存與 ISO 8601 原則、TimeZoneInfo 與夏令時...
委外・委託開發 Windows 應用程式前該整理的事項
在委外・委託開發 Windows 應用程式之前,整理既有軟體改版、設備整合、COM/ActiveX、發布與更新、維護等應留意的重點。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
報表的年號顯示、營業日計算、結算日處理等日本特有的業務需求實作,正是 Windows 業務應用程式開發諮詢的核心範疇。
Windows 軟體維護 & 現代化
盤點既有應用程式中埋藏的年號・國定假日寫死問題,並改造成足以承受下一次改元或法規修訂的形式,屬於既有 Windows 軟體的改修・維護範疇。
技術諮詢 & 設計審查
釐清「月底」「營業日」「會計年度」的定義應規格化到何種程度、以資料方式管理,屬於伴隨設計審查的技術諮詢。
常見問題
整理諮詢這個主題時常見的問題。
- 資料庫不可以直接以年號原樣儲存日期嗎?
- 不建議這麼做,理由有四個。第一,「平成9年」與「令和9年」這類年號字串,一旦跨越改元的分界點,用字串比較就會失去大小順序,無法排序也無法做範圍查詢。第二,下一次改元發生的瞬間,既有資料與新資料的表示法就會混雜在一起。第三,容易混入「昭和64年1月10日」(實際不存在,1月8日起已是平成元年)這類不存在日期的髒資料,成為日後剖析失敗的未爆彈。第四,.NET 的 JapaneseCalendar 只支援明治元年(1868年)9月8日之後的日期,更早的生日等資料若仍以年號形式保存,將無法正確處理。原則是內部處理與儲存一律統一為西曆(格里曆),年號只在畫面與報表輸出的瞬間才轉換。
- 下一次改元來臨時,應用程式該如何因應?
- 年號的定義本身並未寫死在 .NET 中,而是參照作業系統端提供的年號資訊,因此只要持續套用作業系統與 .NET 的更新,「以新年號顯示」這件事本身會自動跟上。令和改元當時,因應的重點其實就是套用平台更新。真正的問題出在應用程式端的程式碼與資料:以字串寫死「平成」「令和」的分支、自行維護年號與西曆對照表的轉換函式、直接寫在報表範本裡的年號、印刷表單上的「令和 __年」欄位,這些都不會自動跟進。務實的因應方式有四點:(1) 內部與儲存一律統一為西曆,讓年號只用於顯示;(2) 放棄自製年號轉換邏輯,改用 JapaneseCalendar;(3) 對整個原始碼以「昭和・平成・令和」進行機械式 grep 盤點;(4) 準備假設未來改元情境的測試。
- 國定假日不能用公式計算嗎?為什麼一定要用資料的方式保存?
- 完整的計算公式原理上做不出來,理由有三個。第一,春分之日與秋分之日是根據天文計算,經內閣會議在前一年才確定,並未以固定日期寫入法律。第二,國定假日會因法規修訂而新增、移動或改名:實際上就有2016年新設山之日、2019年因改元使天皇誕生日從12月23日移到2月23日(**2019年當年並不存在天皇誕生日**)、體育之日改名為運動之日等案例。第三,2020年與2021年由於特別措施法,海之日・運動之日・山之日配合奧運僅在當年移動,顯示出「每年固定第三個星期一」這類規則本身也可能被臨時覆寫。因此國定假日的判定應設計成「國定假日主檔(資料)+更新作業」,計算邏輯只用於補充替代假日等輔助規則。
- 用了 AddMonths(1) 之後,付款期限偏離了月底。為什麼?
- 因為 DateTime.AddMonths 的規格是:若計算結果落在該月不存在的日期,就會進位到該月的最後一天。對1月31日套用 AddMonths(1) 會得到2月28日(閏年則是29日),這個行為本身是正確的。但如果把這個經過進位的結果再套用一次 AddMonths,對2月28日套用 AddMonths(1) 會得到3月28日,之後就會一直偏離下去——「原本想要月底」卻在不知不覺間變成了「28日」,並且持續錯下去。對策有兩個:重複計算時不要在前一次的結果上累加,而是每次都從基準日(合約日或「每月底」的定義)重新計算;「月底」不要用日期字面值,而是透過 DateTime.DaysInMonth 推導。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。