Fix Japanese Abbreviated Era Names (#40300)
authorTarek Mahmoud Sayed <tarekms@microsoft.com>
Fri, 7 Aug 2020 16:18:40 +0000 (09:18 -0700)
committerGitHub <noreply@github.com>
Fri, 7 Aug 2020 16:18:40 +0000 (09:18 -0700)
src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.Icu.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.cs

index 52cda26..90c94de 100644 (file)
@@ -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]));
+            }
+        }
     }
 }
index 1aa2b02..4f7bf1c 100644 (file)
@@ -35,7 +35,7 @@ namespace System.Globalization
         public override CalendarAlgorithmType AlgorithmType => CalendarAlgorithmType.SolarCalendar;
 
         /// <summary>
-        /// Internal method to provide a default intance of GregorianCalendar.
+        /// Internal method to provide a default instance of GregorianCalendar.
         /// Used by NLS+ implementation
         /// </summary>
         internal static Calendar GetDefaultInstance() => s_defaultInstance ??= new GregorianCalendar();
index cfc47d7..5ac43a9 100644 (file)
@@ -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<EraInfo> eras = new List<EraInfo>();
             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();
index b147247..ba6b308 100644 (file)
@@ -5,17 +5,17 @@ namespace System.Globalization
 {
     /// <summary>
     /// 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