}
// This calendar recognizes two era values:
- // 0 CurrentEra (AD)
- // 1 BeforeCurrentEra (BC)
+ // 0 CurrentEra (AD)
+ // 1 BeforeCurrentEra (BC)
internal class GregorianCalendarHelper
{
// 1 tick = 100ns = 10E-7 second
//
// This is the max Gregorian year can be represented by DateTime class. The limitation
// is derived from DateTime class.
- //
+ //
internal int MaxYear
{
get
m_minYear = m_EraInfo[0].minEraYear; ;
}
- /*=================================GetGregorianYear==========================
- **Action: Get the Gregorian year value for the specified year in an era.
- **Returns: The Gregorian year value.
- **Arguments:
- ** year the year value in Japanese calendar
- ** era the Japanese emperor era value.
- **Exceptions:
- ** ArgumentOutOfRangeException if year value is invalid or era value is invalid.
- ============================================================================*/
-
- internal int GetGregorianYear(int year, int era)
+ // EraInfo.yearOffset: The offset to Gregorian year when the era starts. Gregorian Year = Era Year + yearOffset
+ // Era Year = Gregorian Year - yearOffset
+ // EraInfo.minEraYear: Min year value in this era. Generally, this value is 1, but this may be affected by the DateTime.MinValue;
+ // EraInfo.maxEraYear: Max year value in this era. (== the year length of the era + 1)
+ private int GetYearOffset(int year, int era, bool throwOnError)
{
if (year < 0)
{
- throw new ArgumentOutOfRangeException(nameof(year),
- SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (throwOnError)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ return -1;
}
if (era == Calendar.CurrentEra)
{
if (era == m_EraInfo[i].era)
{
- if (year < m_EraInfo[i].minEraYear || year > m_EraInfo[i].maxEraYear)
+ if (year >= m_EraInfo[i].minEraYear)
+ {
+ if (year <= m_EraInfo[i].maxEraYear)
+ {
+ return m_EraInfo[i].yearOffset;
+ }
+ else if (!AppContextSwitches.EnforceJapaneseEraYearRanges)
+ {
+ // If we got the year number exceeding the era max year number, this still possible be valid as the date can be created before
+ // introducing new eras after the era we are checking. we'll loop on the eras after the era we have and ensure the year
+ // can exist in one of these eras. otherwise, we'll throw.
+ // Note, we always return the offset associated with the requested era.
+ //
+ // Here is some example:
+ // if we are getting the era number 4 (Heisei) and getting the year number 32. if the era 4 has year range from 1 to 31
+ // then year 32 exceeded the range of era 4 and we'll try to find out if the years difference (32 - 31 = 1) would lay in
+ // the subsequent eras (e.g era 5 and up)
+
+ int remainingYears = year - m_EraInfo[i].maxEraYear;
+
+ for (int j = i - 1; j >= 0; j--)
+ {
+ if (remainingYears <= m_EraInfo[j].maxEraYear)
+ {
+ return m_EraInfo[i].yearOffset;
+ }
+ remainingYears -= m_EraInfo[j].maxEraYear;
+ }
+ }
+ }
+
+ if (throwOnError)
{
throw new ArgumentOutOfRangeException(
nameof(year),
m_EraInfo[i].minEraYear,
m_EraInfo[i].maxEraYear));
}
- return (m_EraInfo[i].yearOffset + year);
+
+ break; // no need to iterate more on eras.
}
}
- throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
- }
- internal bool IsValidYear(int year, int era)
- {
- if (year < 0)
+ if (throwOnError)
{
- return false;
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
}
+ return -1;
+ }
- if (era == Calendar.CurrentEra)
- {
- era = m_Cal.CurrentEraValue;
- }
+ /*=================================GetGregorianYear==========================
+ **Action: Get the Gregorian year value for the specified year in an era.
+ **Returns: The Gregorian year value.
+ **Arguments:
+ ** year the year value in Japanese calendar
+ ** era the Japanese emperor era value.
+ **Exceptions:
+ ** ArgumentOutOfRangeException if year value is invalid or era value is invalid.
+ ============================================================================*/
- for (int i = 0; i < m_EraInfo.Length; i++)
- {
- if (era == m_EraInfo[i].era)
- {
- if (year < m_EraInfo[i].minEraYear || year > m_EraInfo[i].maxEraYear)
- {
- return false;
- }
- return true;
- }
- }
- return false;
+ internal int GetGregorianYear(int year, int era)
+ {
+ return GetYearOffset(year, era, throwOnError: true) + year;
}
+ internal bool IsValidYear(int year, int era)
+ {
+ return GetYearOffset(year, era, throwOnError: false) >= 0;
+ }
// Returns a given date part of this DateTime. This method is used
// to compute the year, day-of-year, month, or day part.