Describe the bug
Date.ParseStrategy, Date.VerbatimFormatStyle.parseStrategy, and Date.FormatStyle.parse(_:) can parse two-digit year-of-era values in Japanese and ROC calendars as dates 100 years too far in the future.
For example, 平成10年 is parsed as 2098 instead of 1998, and 民國01年 is parsed as 2012 instead of 1912.
To Reproduce
import Foundation
let timeZone = TimeZone(secondsFromGMT: 0)!
var gregorian = Calendar(identifier: .gregorian)
gregorian.timeZone = timeZone
func ymd(_ date: Date) -> String {
let c = gregorian.dateComponents([.year, .month, .day], from: date)
return String(format: "%04d-%02d-%02d", c.year!, c.month!, c.day!)
}
let japaneseLocale = Locale(identifier: "ja_JP@calendar=japanese")
var japaneseCalendar = Calendar(identifier: .japanese)
japaneseCalendar.locale = japaneseLocale
japaneseCalendar.timeZone = timeZone
let japaneseFormat: Date.FormatString =
"\(era: .wide)\(year: .defaultDigits)年\(month: .defaultDigits)月\(day: .defaultDigits)日"
let japaneseStrategy = Date.ParseStrategy(
format: japaneseFormat,
locale: japaneseLocale,
timeZone: timeZone,
calendar: japaneseCalendar,
isLenient: false
)
print("平成09:", ymd(try japaneseStrategy.parse("平成09年1月1日")))
print("平成10:", ymd(try japaneseStrategy.parse("平成10年1月1日")))
print("令和01:", ymd(try japaneseStrategy.parse("令和01年5月1日")))
let rocLocale = Locale(identifier: "zh_TW@calendar=roc")
var rocCalendar = Calendar(identifier: .republicOfChina)
rocCalendar.locale = rocLocale
rocCalendar.timeZone = timeZone
let rocFormat: Date.FormatString =
"\(era: .wide)\(year: .defaultDigits)年\(month: .defaultDigits)月\(day: .defaultDigits)日"
let rocStrategy = Date.ParseStrategy(
format: rocFormat,
locale: rocLocale,
timeZone: timeZone,
calendar: rocCalendar,
isLenient: false
)
print("民國01:", ymd(try rocStrategy.parse("民國01年1月1日")))
print("民國58:", ymd(try rocStrategy.parse("民國58年1月1日")))
print("民國59:", ymd(try rocStrategy.parse("民國59年1月1日")))
Actual output:
平成09: 2097-01-01
平成10: 2098-01-01
令和01: 2119-05-01
民國01: 2012-01-01
民國58: 2069-01-01
民國59: 1970-01-01
Expected behavior
The parsed year should remain scoped to the parsed era:
平成09: 1997-01-01
平成10: 1998-01-01
令和01: 2019-05-01
民國01: 1912-01-01
民國58: 1969-01-01
民國59: 1970-01-01
Configuration (please complete the following information):
- Swift Version: Apple Swift version 6.3 (swiftlang-6.3.0.123.5)
- OS: macOS
- OS Version: 26.4.1
Regression information:
Not confirmed.
Additional context
Also reproduced against swift-foundation release/6.4.x.
The affected APIs all use ICUDateFormatter. ICUDateFormatter currently calls udat_set2DigitYearStart unconditionally, using the parse strategy's twoDigitStartDate default of 1970-01-01.
ICU applies the two-digit year window when a y pattern with width less than 3 parses exactly two digits. That is appropriate for two-digit Gregorian years, but it also affects explicit year-of-era values such as 平成10 and 民國01.
JapaneseCalendar normally disables default-century pivoting in ICU, but the unconditional udat_set2DigitYearStart call re-enables it for this formatter.
One possible fix is to avoid applying ICU's two-digit year window to explicit era-year parsing, for example by using a parse-only wider year field for Japanese and ROC patterns that contain an era field.
This behavior was also independently observed and discussed on X here: URL
Describe the bug
Date.ParseStrategy,Date.VerbatimFormatStyle.parseStrategy, andDate.FormatStyle.parse(_:)can parse two-digit year-of-era values in Japanese and ROC calendars as dates 100 years too far in the future.For example,
平成10年is parsed as 2098 instead of 1998, and民國01年is parsed as 2012 instead of 1912.To Reproduce
Actual output:
Expected behavior
The parsed year should remain scoped to the parsed era:
Configuration (please complete the following information):
Regression information:
Not confirmed.
Additional context
Also reproduced against
swift-foundationrelease/6.4.x.The affected APIs all use
ICUDateFormatter.ICUDateFormattercurrently callsudat_set2DigitYearStartunconditionally, using the parse strategy'stwoDigitStartDatedefault of1970-01-01.ICU applies the two-digit year window when a
ypattern with width less than 3 parses exactly two digits. That is appropriate for two-digit Gregorian years, but it also affects explicit year-of-era values such as平成10and民國01.JapaneseCalendar normally disables default-century pivoting in ICU, but the unconditional
udat_set2DigitYearStartcall re-enables it for this formatter.One possible fix is to avoid applying ICU's two-digit year window to explicit era-year parsing, for example by using a parse-only wider year field for Japanese and ROC patterns that contain an era field.
This behavior was also independently observed and discussed on X here: URL