Implement Japanese Era information.
authorEric Erhardt <eerhardt@microsoft.com>
Wed, 2 Sep 2015 23:18:20 +0000 (18:18 -0500)
committerMatt Ellis <matell@microsoft.com>
Tue, 22 Sep 2015 18:50:35 +0000 (11:50 -0700)
src/corefx/System.Globalization.Native/calendarData.cpp
src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs
src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs
src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Unix.cs
src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Win32.cs
src/mscorlib/corefx/System/Globalization/JapaneseCalendar.cs

index a974c0f..38cbf99 100644 (file)
@@ -395,15 +395,41 @@ EnumEraNames
 Enumerates all the era names of the specified locale and calendar, invoking the callback function
 for each era name.
 */
-bool EnumEraNames(Locale& locale, CalendarId calendarId, EnumCalendarInfoCallback callback, const void* context)
+bool EnumEraNames(Locale& locale, CalendarId calendarId, CalendarDataType dataType, EnumCalendarInfoCallback callback, const void* context)
 {
        UErrorCode err = U_ZERO_ERROR;
-       DateFormatSymbols dateFormatSymbols(locale, GetCalendarName(calendarId), err);
+       const char* calendarName = GetCalendarName(calendarId);
+       DateFormatSymbols dateFormatSymbols(locale, calendarName, err);
        if (U_FAILURE(err))
                return false;
 
        int32_t eraNameCount;
-       const UnicodeString* eraNames = dateFormatSymbols.getEras(eraNameCount);
+       const UnicodeString* eraNames;
+
+       if (dataType == EraNames)
+       {
+               eraNames = dateFormatSymbols.getEras(eraNameCount);
+       }
+       else if (dataType == AbbrevEraNames)
+       {
+               if (strcmp(calendarName, "gregorian") == 0)
+               {
+                       // NOTE: On Windows, the EraName is "A.D." and AbbrevEraName is "AD".
+                       // But ICU/CLDR only supports "Anno Domini", "AD", and "A".
+                       // So returning getEras (i.e. "AD") for both EraNames and AbbrevEraNames.
+                       eraNames = dateFormatSymbols.getEras(eraNameCount);
+               }
+               else
+               {
+                       eraNames = dateFormatSymbols.getNarrowEras(eraNameCount);
+               }
+       }
+       else
+       {
+               assert(false);
+               return false;
+       }
+
        return EnumCalendarArray(eraNames, eraNameCount, callback, context);
 }
 
@@ -434,6 +460,7 @@ extern "C" int32_t EnumCalendarInfo(
                case LongDates:
                        // TODO: need to replace the "EEEE"s with "dddd"s for .net
                        // Also, "LLLL"s to "MMMM"s
+                       // Also, "G"s to "g"s
                        return InvokeCallbackForDateTimePattern(locale, "eeeeMMMMddyyyy", callback, context);
                case YearMonths:
                        return InvokeCallbackForDateTimePattern(locale, "yyyyMMMM", callback, context);
@@ -453,12 +480,117 @@ extern "C" int32_t EnumCalendarInfo(
                        return EnumMonths(locale, calendarId, DateFormatSymbols::FORMAT, DateFormatSymbols::ABBREVIATED, callback, context);
                case EraNames:
                case AbbrevEraNames:
-                       // NOTE: On Windows, the EraName is "A.D." and AbbrevEraName is "AD".
-                       // But ICU/CLDR only supports "Anno Domini", "AD", and "A".
-                       // So returning getEras (i.e. "AD") for both EraNames and AbbrevEraNames.
-                       return EnumEraNames(locale, calendarId, callback, context);
+                       return EnumEraNames(locale, calendarId, dataType, callback, context);
                default:
                        assert(false);
                        return false;
        }
 }
+
+/*
+Function:
+GetLatestJapaneseEra
+
+Gets the latest era in the Japanese calendar.
+*/
+extern "C" int32_t GetLatestJapaneseEra()
+{
+       UErrorCode err = U_ZERO_ERROR;
+       Locale japaneseLocale("ja_JP@calendar=japanese");
+       LocalPointer<Calendar> calendar(Calendar::createInstance(japaneseLocale, err));
+
+       if (U_FAILURE(err))
+               return 0;
+
+       return calendar->getMaximum(UCAL_ERA); 
+}
+
+/*
+Function:
+GetJapaneseEraInfo
+
+Gets the starting Gregorian date of the specified Japanese Era.
+*/
+extern "C" int32_t GetJapaneseEraStartDate(
+       int32_t era,
+       int32_t* startYear,
+       int32_t* startMonth,
+       int32_t* startDay)
+{
+       UErrorCode err = U_ZERO_ERROR;
+       Locale japaneseLocale("ja_JP@calendar=japanese");
+       LocalPointer<Calendar> calendar(Calendar::createInstance(japaneseLocale, err));
+       if (U_FAILURE(err))
+               return false;
+
+       calendar->set(UCAL_ERA, era);
+       calendar->set(UCAL_YEAR, 1);
+
+       // UCAL_EXTENDED_YEAR is the gregorian year for the JapaneseCalendar
+       *startYear = calendar->get(UCAL_EXTENDED_YEAR, err);
+       if (U_FAILURE(err))
+               return false;
+
+       // set the date to Jan 1
+       calendar->set(UCAL_MONTH, 0);
+       calendar->set(UCAL_DATE, 1);
+
+       int32_t currentEra = calendar->get(UCAL_ERA, err);
+       if (U_FAILURE(err))
+               return false;
+
+       if (currentEra == era)
+       {
+               // if Jan 1 is still in the specified Era, then the Era must have started on Jan 1.
+               *startMonth = 1;
+               *startDay = 1;
+               return true;
+       }
+
+       for (int i = 0; i < 12; i++)
+       {
+               // add 1 month at a time until we get into the specified Era
+               calendar->add(UCAL_MONTH, 1, err);
+               if (U_FAILURE(err))
+                       return false;
+
+               currentEra = calendar->get(UCAL_ERA, err);
+               if (U_FAILURE(err))
+                       return false;
+
+               if (currentEra == era)
+               {
+                       for (int i = 0; i < 32; i++)
+                       {
+                               // now subtract 1 day at a time until we get out of the specified Era
+                               calendar->add(Calendar::DATE, -1, err);
+                               if (U_FAILURE(err))
+                                       return false;
+
+                               currentEra = calendar->get(UCAL_ERA, err);
+                               if (U_FAILURE(err))
+                                       return false;
+
+                               if (currentEra != era)
+                               {
+                                       // add back 1 day to get back into the specified Era
+                                       calendar->add(UCAL_DATE, 1, err);
+                                       if (U_FAILURE(err))
+                                               return false;
+
+                                       *startMonth = calendar->get(UCAL_MONTH, err) + 1;  // ICU Calendar months are 0-based, but .NET is 1-based
+                                       if (U_FAILURE(err))
+                                               return false;
+
+                                       *startDay = calendar->get(UCAL_DATE, err);
+                                       if (U_FAILURE(err))
+                                               return false;
+
+                                       return true;
+                               }
+                       }
+               }
+       }
+
+       return false;
+}
index 7a918b8..f0b63b6 100644 (file)
@@ -23,5 +23,11 @@ internal static partial class Interop
         [DllImport(Libraries.GlobalizationInterop, CharSet = CharSet.Unicode)]
         [return: MarshalAs(UnmanagedType.Bool)]
         internal static extern bool EnumCalendarInfo(EnumCalendarInfoCallback callback, string localeName, CalendarId calendarId, CalendarDataType calendarDataType, IntPtr context);
+
+        [DllImport(Libraries.GlobalizationInterop)]
+        internal static extern int GetLatestJapaneseEra();
+
+        [DllImport(Libraries.GlobalizationInterop)]
+        internal static extern bool GetJapaneseEraStartDate(int era, out int startYear, out int startMonth, out int startDay);
     }
 }
index 16e652f..071ff4c 100644 (file)
@@ -119,7 +119,7 @@ namespace System.Globalization
             return false;
         }
 
-        private static bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] calendarData)
+        internal static bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] calendarData)
         {
             calendarData = null;
 
index e408b74..1e91dae 100644 (file)
@@ -1,24 +1,87 @@
 // Copyright (c) Microsoft. All rights reserved.
 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
+using System.Collections.Generic;
+
 namespace System.Globalization
 {
     public partial class JapaneseCalendar : Calendar
     {
-        private static int GetJapaneseEraCount()
+        private static EraInfo[] GetJapaneseEras()
         {
-            //UNIXTODO: Implement this fully.
-            return 0;
+            string[] eraNames;
+            if (!CalendarData.EnumCalendarInfo("ja-JP", CalendarId.JAPAN, CalendarDataType.EraNames, out eraNames))
+            {
+                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.GlobalizationInterop.GetLatestJapaneseEra();
+            for (int i = latestEra; i >= 0; i--)
+            {
+                DateTime dt;
+                if (!GetJapaneseEraStartDate(i, out dt))
+                {
+                    return null;
+                }
+
+                if (dt < JapaneseCalendar.calendarMinValue)
+                {
+                    // only populate the Eras that are valid JapaneseCalendar date times
+                    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]));
+
+                lastMaxYear = dt.Year;
+            }
+
+            // 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;
+            }
+
+            return eras.ToArray();
         }
 
-        private static bool GetJapaneseEraInfo(int era, out DateTimeOffset dateOffset, out string eraName, out string abbreviatedEraName)
+        // PAL Layer ends here
+
+        private static string GetAbbreviatedEraName(string[] eraNames, int eraIndex)
         {
-            //UNIXTODO: Implement this fully.
-            dateOffset = default(DateTimeOffset);
-            eraName = null;
-            abbreviatedEraName = null;
+            // This matches the behavior on Win32 - only returning the first character of the era name.
+            // See Calendar.EraAsString(Int32) - https://msdn.microsoft.com/en-us/library/windows/apps/br206751.aspx
+            return eraNames[eraIndex].Substring(0, 1);
+        }
+
+        private static bool GetJapaneseEraStartDate(int era, out DateTime dateTime)
+        {
+            dateTime = default(DateTime);
+
+            int startYear;
+            int startMonth;
+            int startDay;
+            bool result = Interop.GlobalizationInterop.GetJapaneseEraStartDate(
+                era,
+                out startYear,
+                out startMonth,
+                out startDay);
+
+            if (result)
+            {
+                dateTime = new DateTime(startYear, startMonth, startDay);
+            }
 
-            return false;
+            return result;
         }
     }
 }
index b6fd993..5f554b6 100644 (file)
@@ -7,12 +7,51 @@ namespace System.Globalization
 {
     public partial class JapaneseCalendar : Calendar
     {
-        public static int GetJapaneseEraCount()
+        private static EraInfo[] GetJapaneseEras()
         {
-            return WinRTInterop.Callbacks.GetJapaneseEraCount();
+            int erasCount = WinRTInterop.Callbacks.GetJapaneseEraCount();
+            if (erasCount < 4)
+            {
+                return null;
+            }
+
+            EraInfo[] eras = new EraInfo[erasCount];
+            int lastMaxYear = GregorianCalendar.MaxYear;
+
+            for (int i = erasCount; i > 0; i--)
+            {
+                DateTimeOffset dateOffset;
+
+                string eraName;
+                string abbreviatedEraName;
+
+                if (!GetJapaneseEraInfo(i, out dateOffset, out eraName, out abbreviatedEraName))
+                {
+                    return null;
+                }
+
+                DateTime dt = new DateTime(dateOffset.Ticks);
+
+                eras[erasCount - i] = new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1,
+                                                   eraName, abbreviatedEraName, GetJapaneseEnglishEraName(i));    // era #4 start year/month/day, yearOffset, minEraYear
+
+                lastMaxYear = dt.Year;
+            }
+
+            return eras;
+        }
+
+        // PAL Layer ends here
+
+        private static string[] JapaneseErasEnglishNames = new String[] { "M", "T", "S", "H" };
+
+        private static string GetJapaneseEnglishEraName(int era)
+        {
+            Debug.Assert(era > 0);
+            return era <= JapaneseErasEnglishNames.Length ? JapaneseErasEnglishNames[era - 1] : " ";
         }
 
-        public static bool GetJapaneseEraInfo(int era, out DateTimeOffset dateOffset, out string eraName, out string abbreviatedEraName)
+        private static bool GetJapaneseEraInfo(int era, out DateTimeOffset dateOffset, out string eraName, out string abbreviatedEraName)
         {
             return  WinRTInterop.Callbacks.GetJapaneseEraInfo(era, out dateOffset, out eraName, out abbreviatedEraName);
         }
index 03c974e..ff61896 100644 (file)
@@ -124,49 +124,6 @@ namespace System.Globalization
             return japaneseEraInfo;
         }
 
-        private static EraInfo[] GetJapaneseEras()
-        {
-            int erasCount = GetJapaneseEraCount();
-            if (erasCount < 4)
-            {
-                return null;
-            }
-
-            EraInfo[] eras = new EraInfo[erasCount];
-            int lastMaxYear = GregorianCalendar.MaxYear;
-
-            for (int i = erasCount; i > 0; i--)
-            {
-                DateTimeOffset dateOffset;
-
-                string eraName;
-                string abbreviatedEraName;
-
-                if (!GetJapaneseEraInfo(i, out dateOffset, out eraName, out abbreviatedEraName))
-                {
-                    return null;
-                }
-
-                DateTime dt = new DateTime(dateOffset.Ticks);
-
-                eras[erasCount - i] = new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1,
-                                                   eraName, abbreviatedEraName, GetJapaneseEnglishEraName(i));    // era #4 start year/month/day, yearOffset, minEraYear
-
-                lastMaxYear = dt.Year;
-            }
-
-            return eras;
-        }
-
-        private static string[] JapaneseErasEnglishNames = new String[] { "M", "T", "S", "H" };
-
-        private static string GetJapaneseEnglishEraName(int era)
-        {
-            Debug.Assert(era > 0);
-            return era <= JapaneseErasEnglishNames.Length ? JapaneseErasEnglishNames[era - 1] : " ";
-        }
-
-
         internal static volatile Calendar s_defaultInstance;
         internal GregorianCalendarHelper helper;