From 2b93f831736a44998b9b58a249dfd57d08ef319f Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 7 Aug 2020 09:18:40 -0700 Subject: [PATCH] Fix Japanese Abbreviated Era Names (#40300) --- .../DateTimeFormatInfo/DateTimeFormatInfoTests.cs | 22 +++++++++++++ .../src/System/Globalization/GregorianCalendar.cs | 2 +- .../System/Globalization/JapaneseCalendar.Icu.cs | 37 +++++++++++++++++----- .../src/System/Globalization/JapaneseCalendar.cs | 8 ++--- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs index 52cda26..90c94de 100644 --- a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs +++ b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs @@ -187,5 +187,27 @@ namespace System.Globalization.Tests $"Parsing '{formattedDateWithGannen}' result should match if '{formattedDate}' has Gan-nen symbol" ); } + + [Fact] + public void JapaneseAbbreviatedEnglishEraNamesTest() + { + string [] eraNames = { "M", "T", "S", "H", "R" }; + + var ci = new CultureInfo("ja-JP") { DateTimeFormat = { Calendar = new JapaneseCalendar() }}; + + int eraNumber = ci.DateTimeFormat.GetEra("Q"); + if (eraNumber == 4 || eraNumber == 5) + { + // Skip the test on Windows versions which have wrong Japanese Era information. + // Windows at some point used "Q" as fake era name before getting the official name. + return; + } + + int numberOfErasToTest = Math.Min(eraNames.Length, ci.DateTimeFormat.Calendar.Eras.Length); + for (int i = 0; i < numberOfErasToTest; i++) + { + Assert.Equal(i + 1, ci.DateTimeFormat.GetEra(eraNames[i])); + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs index 1aa2b02..4f7bf1c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs @@ -35,7 +35,7 @@ namespace System.Globalization public override CalendarAlgorithmType AlgorithmType => CalendarAlgorithmType.SolarCalendar; /// - /// Internal method to provide a default intance of GregorianCalendar. + /// Internal method to provide a default instance of GregorianCalendar. /// Used by NLS+ implementation /// internal static Calendar GetDefaultInstance() => s_defaultInstance ??= new GregorianCalendar(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.Icu.cs index cfc47d7..5ac43a9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.Icu.cs @@ -8,6 +8,8 @@ namespace System.Globalization { public partial class JapaneseCalendar : Calendar { + private static readonly string [] s_abbreviatedEnglishEraNames = { "M", "T", "S", "H", "R" }; + private static EraInfo[]? IcuGetJapaneseEras() { if (GlobalizationMode.Invariant) @@ -23,16 +25,11 @@ namespace System.Globalization return null; } - string[]? abbrevEnglishEraNames; - if (!CalendarData.EnumCalendarInfo("en", CalendarId.JAPAN, CalendarDataType.AbbrevEraNames, out abbrevEnglishEraNames)) - { - return null; - } - List eras = new List(); int lastMaxYear = GregorianCalendar.MaxYear; int latestEra = Interop.Globalization.GetLatestJapaneseEra(); + for (int i = latestEra; i >= 0; i--) { DateTime dt; @@ -47,16 +44,40 @@ namespace System.Globalization break; } - eras.Add(new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1, - eraNames![i], GetAbbreviatedEraName(eraNames, i), abbrevEnglishEraNames![i])); + eras.Add(new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1, eraNames![i], GetAbbreviatedEraName(eraNames, i), "")); lastMaxYear = dt.Year; } + string[] abbrevEnglishEraNames; + if (!CalendarData.EnumCalendarInfo("ja", CalendarId.JAPAN, CalendarDataType.AbbrevEraNames, out abbrevEnglishEraNames!)) + { + // Failed to get English names. fallback to hardcoded data. + abbrevEnglishEraNames = s_abbreviatedEnglishEraNames; + } + + // Check if we are getting the English Name at the end of the returned list. + // ICU usually return long list including all Era names written in Japanese characters except the recent eras which actually we support will be returned in English. + // We have the following check as older ICU versions doesn't carry the English names (e.g. ICU version 50). + if (abbrevEnglishEraNames[abbrevEnglishEraNames.Length - 1].Length == 0 || abbrevEnglishEraNames[abbrevEnglishEraNames.Length - 1][0] > '\u007F') + { + // Couldn't get English names. + abbrevEnglishEraNames = s_abbreviatedEnglishEraNames; + } + + int startIndex = abbrevEnglishEraNames == s_abbreviatedEnglishEraNames ? eras.Count - 1 : abbrevEnglishEraNames.Length - 1; + + Debug.Assert(abbrevEnglishEraNames == s_abbreviatedEnglishEraNames || eras.Count <= abbrevEnglishEraNames.Length); + // remap the Era numbers, now that we know how many there will be for (int i = 0; i < eras.Count; i++) { eras[i].era = eras.Count - i; + if (startIndex < abbrevEnglishEraNames.Length) + { + eras[i].englishEraName = abbrevEnglishEraNames[startIndex]; + } + startIndex--; } return eras.ToArray(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.cs index b147247..ba6b308 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.cs @@ -5,17 +5,17 @@ namespace System.Globalization { /// /// JapaneseCalendar is based on Gregorian calendar. The month and day values are the same as - /// Gregorian calendar. However, the year value is an offset to the Gregorian + /// Gregorian calendar. However, the year value is an offset to the Gregorian /// year based on the era. /// /// This system is adopted by Emperor Meiji in 1868. The year value is counted based on the reign of an emperor, /// and the era begins on the day an emperor ascends the throne and continues until his death. /// The era changes at 12:00AM. /// - /// For example, the current era is Reiwa. It started on 2019/5/1 A.D. Therefore, Gregorian year 2019 is also Reiwa 1st. + /// For example, the current era is Reiwa. It started on 2019/5/1 A.D. Therefore, Gregorian year 2019 is also Reiwa 1st. /// 2019/5/1 A.D. is also Reiwa 1st 5/1. /// - /// Any date in the year during which era is changed can be reckoned in either era. For example, + /// Any date in the year during which era is changed can be reckoned in either era. For example, /// 2019/1/1 can be 1/1 Reiwa 1st year or 1/1 Heisei 31st year. /// /// Note: @@ -50,7 +50,7 @@ namespace System.Globalization // // We know about 4 built-in eras, however users may add additional era(s) from the // registry, by adding values to HKLM\SYSTEM\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras - // we don't read the registry and instead we call WinRT to get the needed informatio + // we don't read the registry and instead we call WinRT to get the needed information // // Registry values look like: // yyyy.mm.dd=era_abbrev_english_englishabbrev -- 2.7.4