summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Globalization
diff options
context:
space:
mode:
authorJiyoung Yun <jy910.yun@samsung.com>2016-11-23 19:09:09 +0900
committerJiyoung Yun <jy910.yun@samsung.com>2016-11-23 19:09:09 +0900
commit4b4aad7217d3292650e77eec2cf4c198ea9c3b4b (patch)
tree98110734c91668dfdbb126fcc0e15ddbd93738ca /src/mscorlib/src/System/Globalization
parentfa45f57ed55137c75ac870356a1b8f76c84b229c (diff)
downloadcoreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.gz
coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.bz2
coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.zip
Imported Upstream version 1.1.0upstream/1.1.0
Diffstat (limited to 'src/mscorlib/src/System/Globalization')
-rw-r--r--src/mscorlib/src/System/Globalization/BidiCategory.cs39
-rw-r--r--src/mscorlib/src/System/Globalization/Calendar.cs861
-rw-r--r--src/mscorlib/src/System/Globalization/CalendarAlgorithmType.cs20
-rw-r--r--src/mscorlib/src/System/Globalization/CalendarData.cs455
-rw-r--r--src/mscorlib/src/System/Globalization/CalendarWeekRule.cs20
-rw-r--r--src/mscorlib/src/System/Globalization/CalendricalCalculationsHelper.cs413
-rw-r--r--src/mscorlib/src/System/Globalization/CharUnicodeInfo.cs522
-rw-r--r--src/mscorlib/src/System/Globalization/ChineseLunisolarCalendar.cs401
-rw-r--r--src/mscorlib/src/System/Globalization/CompareInfo.cs1359
-rw-r--r--src/mscorlib/src/System/Globalization/CultureData.cs3354
-rw-r--r--src/mscorlib/src/System/Globalization/CultureInfo.cs2023
-rw-r--r--src/mscorlib/src/System/Globalization/CultureNotFoundException.cs131
-rw-r--r--src/mscorlib/src/System/Globalization/CultureTypes.cs31
-rw-r--r--src/mscorlib/src/System/Globalization/DateTimeFormat.cs1054
-rw-r--r--src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs2936
-rw-r--r--src/mscorlib/src/System/Globalization/DateTimeFormatInfoScanner.cs750
-rw-r--r--src/mscorlib/src/System/Globalization/DateTimeParse.cs5069
-rw-r--r--src/mscorlib/src/System/Globalization/DateTimeStyles.cs50
-rw-r--r--src/mscorlib/src/System/Globalization/DaylightTime.cs49
-rw-r--r--src/mscorlib/src/System/Globalization/DigitShapes.cs22
-rw-r--r--src/mscorlib/src/System/Globalization/EastAsianLunisolarCalendar.cs644
-rw-r--r--src/mscorlib/src/System/Globalization/EncodingDataItem.Unix.cs69
-rw-r--r--src/mscorlib/src/System/Globalization/EncodingDataItem.cs115
-rw-r--r--src/mscorlib/src/System/Globalization/EncodingTable.Unix.cs178
-rw-r--r--src/mscorlib/src/System/Globalization/EncodingTable.cs250
-rw-r--r--src/mscorlib/src/System/Globalization/GlobalizationAssembly.cs63
-rw-r--r--src/mscorlib/src/System/Globalization/GregorianCalendar.cs627
-rw-r--r--src/mscorlib/src/System/Globalization/GregorianCalendarHelper.cs633
-rw-r--r--src/mscorlib/src/System/Globalization/GregorianCalendarTypes.cs18
-rw-r--r--src/mscorlib/src/System/Globalization/HebrewCalendar.cs1084
-rw-r--r--src/mscorlib/src/System/Globalization/HebrewNumber.cs402
-rw-r--r--src/mscorlib/src/System/Globalization/HijriCalendar.cs723
-rw-r--r--src/mscorlib/src/System/Globalization/IdnMapping.cs1189
-rw-r--r--src/mscorlib/src/System/Globalization/JapaneseCalendar.cs594
-rw-r--r--src/mscorlib/src/System/Globalization/JapaneseLunisolarCalendar.cs293
-rw-r--r--src/mscorlib/src/System/Globalization/JulianCalendar.cs441
-rw-r--r--src/mscorlib/src/System/Globalization/KoreanCalendar.cs265
-rw-r--r--src/mscorlib/src/System/Globalization/KoreanLunisolarCalendar.cs1334
-rw-r--r--src/mscorlib/src/System/Globalization/NumberFormatInfo.cs842
-rw-r--r--src/mscorlib/src/System/Globalization/NumberStyles.cs67
-rw-r--r--src/mscorlib/src/System/Globalization/PersianCalendar.cs577
-rw-r--r--src/mscorlib/src/System/Globalization/RegionInfo.cs629
-rw-r--r--src/mscorlib/src/System/Globalization/SortKey.cs208
-rw-r--r--src/mscorlib/src/System/Globalization/SortVersion.cs102
-rw-r--r--src/mscorlib/src/System/Globalization/StringInfo.cs361
-rw-r--r--src/mscorlib/src/System/Globalization/Tables/charinfo.nlpbin0 -> 36992 bytes
-rw-r--r--src/mscorlib/src/System/Globalization/TaiwanCalendar.cs262
-rw-r--r--src/mscorlib/src/System/Globalization/TaiwanLunisolarCalendar.cs330
-rw-r--r--src/mscorlib/src/System/Globalization/TextElementEnumerator.cs155
-rw-r--r--src/mscorlib/src/System/Globalization/TextInfo.cs1004
-rw-r--r--src/mscorlib/src/System/Globalization/ThaiBuddhistCalendar.cs225
-rw-r--r--src/mscorlib/src/System/Globalization/TimeSpanFormat.cs474
-rw-r--r--src/mscorlib/src/System/Globalization/TimeSpanParse.cs1557
-rw-r--r--src/mscorlib/src/System/Globalization/TimeSpanStyles.cs12
-rw-r--r--src/mscorlib/src/System/Globalization/UmAlQuraCalendar.cs849
-rw-r--r--src/mscorlib/src/System/Globalization/UnicodeCategory.cs79
56 files changed, 36214 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Globalization/BidiCategory.cs b/src/mscorlib/src/System/Globalization/BidiCategory.cs
new file mode 100644
index 0000000000..1041776424
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/BidiCategory.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+** Purpose:
+**
+**
+============================================================*/
+namespace System.Globalization {
+ [Serializable]
+ internal enum BidiCategory {
+ LeftToRight = 0,
+ LeftToRightEmbedding = 1,
+ LeftToRightOverride = 2,
+ RightToLeft = 3,
+ RightToLeftArabic = 4,
+ RightToLeftEmbedding = 5,
+ RightToLeftOverride = 6,
+ PopDirectionalFormat = 7,
+ EuropeanNumber = 8,
+ EuropeanNumberSeparator = 9,
+ EuropeanNumberTerminator = 10,
+ ArabicNumber = 11,
+ CommonNumberSeparator = 12,
+ NonSpacingMark = 13,
+ BoundaryNeutral = 14,
+ ParagraphSeparator = 15,
+ SegmentSeparator = 16,
+ Whitespace = 17,
+ OtherNeutrals = 18,
+ LeftToRightIsolate = 19,
+ RightToLeftIsolate = 20,
+ FirstStrongIsolate = 21,
+ PopDirectionIsolate = 22,
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/Calendar.cs b/src/mscorlib/src/System/Globalization/Calendar.cs
new file mode 100644
index 0000000000..d6dfdc9f4b
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/Calendar.cs
@@ -0,0 +1,861 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+ using System.Runtime.CompilerServices;
+ using System.Globalization;
+ using System.Runtime.Versioning;
+ using System.Diagnostics.Contracts;
+
+ // This abstract class represents a calendar. A calendar reckons time in
+ // divisions such as weeks, months and years. The number, length and start of
+ // the divisions vary in each calendar.
+ //
+ // Any instant in time can be represented as an n-tuple of numeric values using
+ // a particular calendar. For example, the next vernal equinox occurs at (0.0, 0
+ // , 46, 8, 20, 3, 1999) in the Gregorian calendar. An implementation of
+ // Calendar can map any DateTime value to such an n-tuple and vice versa. The
+ // DateTimeFormat class can map between such n-tuples and a textual
+ // representation such as "8:46 AM March 20th 1999 AD".
+ //
+ // Most calendars identify a year which begins the current era. There may be any
+ // number of previous eras. The Calendar class identifies the eras as enumerated
+ // integers where the current era (CurrentEra) has the value zero.
+ //
+ // For consistency, the first unit in each interval, e.g. the first month, is
+ // assigned the value one.
+ // The calculation of hour/minute/second is moved to Calendar from GregorianCalendar,
+ // since most of the calendars (or all?) have the same way of calcuating hour/minute/second.
+
+ [Serializable]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public abstract class Calendar : ICloneable
+ {
+
+ // Number of 100ns (10E-7 second) ticks per time unit
+ internal const long TicksPerMillisecond = 10000;
+ internal const long TicksPerSecond = TicksPerMillisecond * 1000;
+ internal const long TicksPerMinute = TicksPerSecond * 60;
+ internal const long TicksPerHour = TicksPerMinute * 60;
+ internal const long TicksPerDay = TicksPerHour * 24;
+
+ // Number of milliseconds per time unit
+ internal const int MillisPerSecond = 1000;
+ internal const int MillisPerMinute = MillisPerSecond * 60;
+ internal const int MillisPerHour = MillisPerMinute * 60;
+ internal const int MillisPerDay = MillisPerHour * 24;
+
+ // Number of days in a non-leap year
+ internal const int DaysPerYear = 365;
+ // Number of days in 4 years
+ internal const int DaysPer4Years = DaysPerYear * 4 + 1;
+ // Number of days in 100 years
+ internal const int DaysPer100Years = DaysPer4Years * 25 - 1;
+ // Number of days in 400 years
+ internal const int DaysPer400Years = DaysPer100Years * 4 + 1;
+
+ // Number of days from 1/1/0001 to 1/1/10000
+ internal const int DaysTo10000 = DaysPer400Years * 25 - 366;
+
+ internal const long MaxMillis = (long)DaysTo10000 * MillisPerDay;
+
+ //
+ // Calendar ID Values. This is used to get data from calendar.nlp.
+ // The order of calendar ID means the order of data items in the table.
+ //
+
+ internal const int CAL_GREGORIAN = 1 ; // Gregorian (localized) calendar
+ internal const int CAL_GREGORIAN_US = 2 ; // Gregorian (U.S.) calendar
+ internal const int CAL_JAPAN = 3 ; // Japanese Emperor Era calendar
+ internal const int CAL_TAIWAN = 4 ; // Taiwan Era calendar
+ internal const int CAL_KOREA = 5 ; // Korean Tangun Era calendar
+ internal const int CAL_HIJRI = 6 ; // Hijri (Arabic Lunar) calendar
+ internal const int CAL_THAI = 7 ; // Thai calendar
+ internal const int CAL_HEBREW = 8 ; // Hebrew (Lunar) calendar
+ internal const int CAL_GREGORIAN_ME_FRENCH = 9 ; // Gregorian Middle East French calendar
+ internal const int CAL_GREGORIAN_ARABIC = 10; // Gregorian Arabic calendar
+ internal const int CAL_GREGORIAN_XLIT_ENGLISH = 11; // Gregorian Transliterated English calendar
+ internal const int CAL_GREGORIAN_XLIT_FRENCH = 12;
+ internal const int CAL_JULIAN = 13;
+ internal const int CAL_JAPANESELUNISOLAR = 14;
+ internal const int CAL_CHINESELUNISOLAR = 15;
+ internal const int CAL_SAKA = 16; // reserved to match Office but not implemented in our code
+ internal const int CAL_LUNAR_ETO_CHN = 17; // reserved to match Office but not implemented in our code
+ internal const int CAL_LUNAR_ETO_KOR = 18; // reserved to match Office but not implemented in our code
+ internal const int CAL_LUNAR_ETO_ROKUYOU = 19; // reserved to match Office but not implemented in our code
+ internal const int CAL_KOREANLUNISOLAR = 20;
+ internal const int CAL_TAIWANLUNISOLAR = 21;
+ internal const int CAL_PERSIAN = 22;
+ internal const int CAL_UMALQURA = 23;
+
+ internal int m_currentEraValue = -1;
+
+ [System.Runtime.Serialization.OptionalField(VersionAdded = 2)]
+ private bool m_isReadOnly = false;
+
+ // The minimum supported DateTime range for the calendar.
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ // The maximum supported DateTime range for the calendar.
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+
+
+
+ protected Calendar() {
+ //Do-nothing constructor.
+ }
+
+ ///
+ // This can not be abstract, otherwise no one can create a subclass of Calendar.
+ //
+ internal virtual int ID {
+ get {
+ return (-1);
+ }
+ }
+
+ ///
+ // Return the Base calendar ID for calendars that didn't have defined data in calendarData
+ //
+
+ internal virtual int BaseCalendarID
+ {
+ get { return ID; }
+ }
+
+ // Returns the type of the calendar.
+ //
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.Unknown;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsReadOnly
+ //
+ // Detect if the object is readonly.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public bool IsReadOnly
+ {
+ get { return (m_isReadOnly); }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Clone
+ //
+ // Is the implementation of ICloneable.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual Object Clone()
+ {
+ object o = MemberwiseClone();
+ ((Calendar) o).SetReadOnlyState(false);
+ return (o);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ReadOnly
+ //
+ // Create a cloned readonly instance or return the input one if it is
+ // readonly.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public static Calendar ReadOnly(Calendar calendar)
+ {
+ if (calendar == null) { throw new ArgumentNullException("calendar"); }
+ Contract.EndContractBlock();
+ if (calendar.IsReadOnly) { return (calendar); }
+
+ Calendar clonedCalendar = (Calendar)(calendar.MemberwiseClone());
+ clonedCalendar.SetReadOnlyState(true);
+
+ return (clonedCalendar);
+ }
+
+ internal void VerifyWritable()
+ {
+ if (m_isReadOnly)
+ {
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ }
+ }
+
+ internal void SetReadOnlyState(bool readOnly)
+ {
+ m_isReadOnly = readOnly;
+ }
+
+
+ /*=================================CurrentEraValue==========================
+ **Action: This is used to convert CurretEra(0) to an appropriate era value.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ **Notes:
+ ** The value is from calendar.nlp.
+ ============================================================================*/
+
+ internal virtual int CurrentEraValue {
+ get {
+ // The following code assumes that the current era value can not be -1.
+ if (m_currentEraValue == -1) {
+ Contract.Assert(BaseCalendarID > 0, "[Calendar.CurrentEraValue] Expected ID > 0");
+ m_currentEraValue = CalendarData.GetCalendarData(BaseCalendarID).iCurrentEra;
+ }
+ return (m_currentEraValue);
+ }
+ }
+
+ // The current era for a calendar.
+
+ public const int CurrentEra = 0;
+
+ internal int twoDigitYearMax = -1;
+
+ internal static void CheckAddResult(long ticks, DateTime minValue, DateTime maxValue) {
+ if (ticks < minValue.Ticks || ticks > maxValue.Ticks) {
+ throw new ArgumentException(
+ String.Format(CultureInfo.InvariantCulture, Environment.GetResourceString("Argument_ResultCalendarRange"),
+ minValue, maxValue));
+ }
+ Contract.EndContractBlock();
+ }
+
+ internal DateTime Add(DateTime time, double value, int scale) {
+ // From ECMA CLI spec, Partition III, section 3.27:
+ //
+ // If overflow occurs converting a floating-point type to an integer, or if the floating-point value
+ // being converted to an integer is a NaN, the value returned is unspecified.
+ //
+ // Based upon this, this method should be performing the comparison against the double
+ // before attempting a cast. Otherwise, the result is undefined.
+ double tempMillis = (value * scale + (value >= 0 ? 0.5 : -0.5));
+ if (!((tempMillis > -(double)MaxMillis) && (tempMillis < (double)MaxMillis)))
+ {
+ throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_AddValue"));
+ }
+
+ long millis = (long)tempMillis;
+ long ticks = time.Ticks + millis * TicksPerMillisecond;
+ CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // milliseconds to the specified DateTime. The result is computed by rounding
+ // the number of milliseconds given by value to the nearest integer,
+ // and adding that interval to the specified DateTime. The value
+ // argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddMilliseconds(DateTime time, double milliseconds) {
+ return (Add(time, milliseconds, 1));
+ }
+
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // days to the specified DateTime. The result is computed by rounding the
+ // fractional number of days given by value to the nearest
+ // millisecond, and adding that interval to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddDays(DateTime time, int days) {
+ return (Add(time, days, MillisPerDay));
+ }
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // hours to the specified DateTime. The result is computed by rounding the
+ // fractional number of hours given by value to the nearest
+ // millisecond, and adding that interval to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddHours(DateTime time, int hours) {
+ return (Add(time, hours, MillisPerHour));
+ }
+
+
+ // Returns the DateTime resulting from adding a fractional number of
+ // minutes to the specified DateTime. The result is computed by rounding the
+ // fractional number of minutes given by value to the nearest
+ // millisecond, and adding that interval to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddMinutes(DateTime time, int minutes) {
+ return (Add(time, minutes, MillisPerMinute));
+ }
+
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+ public abstract DateTime AddMonths(DateTime time, int months);
+
+ // Returns the DateTime resulting from adding a number of
+ // seconds to the specified DateTime. The result is computed by rounding the
+ // fractional number of seconds given by value to the nearest
+ // millisecond, and adding that interval to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddSeconds(DateTime time, int seconds) {
+ return Add(time, seconds, MillisPerSecond);
+ }
+
+ // Returns the DateTime resulting from adding a number of
+ // weeks to the specified DateTime. The
+ // value argument is permitted to be negative.
+ //
+
+ public virtual DateTime AddWeeks(DateTime time, int weeks) {
+ return (AddDays(time, weeks * 7));
+ }
+
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+ public abstract DateTime AddYears(DateTime time, int years);
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+ public abstract int GetDayOfMonth(DateTime time);
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public abstract DayOfWeek GetDayOfWeek(DateTime time);
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+ public abstract int GetDayOfYear(DateTime time);
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+ public virtual int GetDaysInMonth(int year, int month)
+ {
+ return (GetDaysInMonth(year, month, CurrentEra));
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments for the specified era.
+ //
+
+ public abstract int GetDaysInMonth(int year, int month, int era);
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public virtual int GetDaysInYear(int year)
+ {
+ return (GetDaysInYear(year, CurrentEra));
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public abstract int GetDaysInYear(int year, int era);
+
+ // Returns the era for the specified DateTime value.
+
+ public abstract int GetEra(DateTime time);
+
+ /*=================================Eras==========================
+ **Action: Get the list of era values.
+ **Returns: The int array of the era names supported in this calendar.
+ ** null if era is not used.
+ **Arguments: None.
+ **Exceptions: None.
+ ============================================================================*/
+
+
+ public abstract int[] Eras {
+ get;
+ }
+
+
+ // Returns the hour part of the specified DateTime. The returned value is an
+ // integer between 0 and 23.
+ //
+
+ public virtual int GetHour(DateTime time) {
+ return ((int)((time.Ticks / TicksPerHour) % 24));
+ }
+
+ // Returns the millisecond part of the specified DateTime. The returned value
+ // is an integer between 0 and 999.
+ //
+
+ public virtual double GetMilliseconds(DateTime time) {
+ return (double)((time.Ticks / TicksPerMillisecond) % 1000);
+ }
+
+ // Returns the minute part of the specified DateTime. The returned value is
+ // an integer between 0 and 59.
+ //
+
+ public virtual int GetMinute(DateTime time) {
+ return ((int)((time.Ticks / TicksPerMinute) % 60));
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+ public abstract int GetMonth(DateTime time);
+
+ // Returns the number of months in the specified year in the current era.
+
+ public virtual int GetMonthsInYear(int year)
+ {
+ return (GetMonthsInYear(year, CurrentEra));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public abstract int GetMonthsInYear(int year, int era);
+
+ // Returns the second part of the specified DateTime. The returned value is
+ // an integer between 0 and 59.
+ //
+
+ public virtual int GetSecond(DateTime time) {
+ return ((int)((time.Ticks / TicksPerSecond) % 60));
+ }
+
+ /*=================================GetFirstDayWeekOfYear==========================
+ **Action: Get the week of year using the FirstDay rule.
+ **Returns: the week of year.
+ **Arguments:
+ ** time
+ ** firstDayOfWeek the first day of week (0=Sunday, 1=Monday, ... 6=Saturday)
+ **Notes:
+ ** The CalendarWeekRule.FirstDay rule: Week 1 begins on the first day of the year.
+ ** Assume f is the specifed firstDayOfWeek,
+ ** and n is the day of week for January 1 of the specified year.
+ ** Assign offset = n - f;
+ ** Case 1: offset = 0
+ ** E.g.
+ ** f=1
+ ** weekday 0 1 2 3 4 5 6 0 1
+ ** date 1/1
+ ** week# 1 2
+ ** then week of year = (GetDayOfYear(time) - 1) / 7 + 1
+ **
+ ** Case 2: offset < 0
+ ** e.g.
+ ** n=1 f=3
+ ** weekday 0 1 2 3 4 5 6 0
+ ** date 1/1
+ ** week# 1 2
+ ** This means that the first week actually starts 5 days before 1/1.
+ ** So week of year = (GetDayOfYear(time) + (7 + offset) - 1) / 7 + 1
+ ** Case 3: offset > 0
+ ** e.g.
+ ** f=0 n=2
+ ** weekday 0 1 2 3 4 5 6 0 1 2
+ ** date 1/1
+ ** week# 1 2
+ ** This means that the first week actually starts 2 days before 1/1.
+ ** So Week of year = (GetDayOfYear(time) + offset - 1) / 7 + 1
+ ============================================================================*/
+
+ internal int GetFirstDayWeekOfYear(DateTime time, int firstDayOfWeek) {
+ int dayOfYear = GetDayOfYear(time) - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
+ // Calculate the day of week for the first day of the year.
+ // dayOfWeek - (dayOfYear % 7) is the day of week for the first day of this year. Note that
+ // this value can be less than 0. It's fine since we are making it positive again in calculating offset.
+ int dayForJan1 = (int)GetDayOfWeek(time) - (dayOfYear % 7);
+ int offset = (dayForJan1 - firstDayOfWeek + 14) % 7;
+ Contract.Assert(offset >= 0, "Calendar.GetFirstDayWeekOfYear(): offset >= 0");
+ return ((dayOfYear + offset) / 7 + 1);
+ }
+
+ private int GetWeekOfYearFullDays(DateTime time, int firstDayOfWeek, int fullDays) {
+ int dayForJan1;
+ int offset;
+ int day;
+
+ int dayOfYear = GetDayOfYear(time) - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
+ //
+ // Calculate the number of days between the first day of year (1/1) and the first day of the week.
+ // This value will be a positive value from 0 ~ 6. We call this value as "offset".
+ //
+ // If offset is 0, it means that the 1/1 is the start of the first week.
+ // Assume the first day of the week is Monday, it will look like this:
+ // Sun Mon Tue Wed Thu Fri Sat
+ // 12/31 1/1 1/2 1/3 1/4 1/5 1/6
+ // +--> First week starts here.
+ //
+ // If offset is 1, it means that the first day of the week is 1 day ahead of 1/1.
+ // Assume the first day of the week is Monday, it will look like this:
+ // Sun Mon Tue Wed Thu Fri Sat
+ // 1/1 1/2 1/3 1/4 1/5 1/6 1/7
+ // +--> First week starts here.
+ //
+ // If offset is 2, it means that the first day of the week is 2 days ahead of 1/1.
+ // Assume the first day of the week is Monday, it will look like this:
+ // Sat Sun Mon Tue Wed Thu Fri Sat
+ // 1/1 1/2 1/3 1/4 1/5 1/6 1/7 1/8
+ // +--> First week starts here.
+
+
+
+ // Day of week is 0-based.
+ // Get the day of week for 1/1. This can be derived from the day of week of the target day.
+ // Note that we can get a negative value. It's ok since we are going to make it a positive value when calculating the offset.
+ dayForJan1 = (int)GetDayOfWeek(time) - (dayOfYear % 7);
+
+ // Now, calculate the offset. Subtract the first day of week from the dayForJan1. And make it a positive value.
+ offset = (firstDayOfWeek - dayForJan1 + 14) % 7;
+ if (offset != 0 && offset >= fullDays)
+ {
+ //
+ // If the offset is greater than the value of fullDays, it means that
+ // the first week of the year starts on the week where Jan/1 falls on.
+ //
+ offset -= 7;
+ }
+ //
+ // Calculate the day of year for specified time by taking offset into account.
+ //
+ day = dayOfYear - offset;
+ if (day >= 0) {
+ //
+ // If the day of year value is greater than zero, get the week of year.
+ //
+ return (day/7 + 1);
+ }
+ //
+ // Otherwise, the specified time falls on the week of previous year.
+ // Call this method again by passing the last day of previous year.
+ //
+ // the last day of the previous year may "underflow" to no longer be a valid date time for
+ // this calendar if we just subtract so we need the subclass to provide us with
+ // that information
+ if (time <= MinSupportedDateTime.AddDays(dayOfYear))
+ {
+ return GetWeekOfYearOfMinSupportedDateTime(firstDayOfWeek, fullDays);
+ }
+ return (GetWeekOfYearFullDays(time.AddDays(-(dayOfYear + 1)), firstDayOfWeek, fullDays));
+ }
+
+ private int GetWeekOfYearOfMinSupportedDateTime(int firstDayOfWeek, int minimumDaysInFirstWeek)
+ {
+ int dayOfYear = GetDayOfYear(MinSupportedDateTime) - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
+ int dayOfWeekOfFirstOfYear = (int)GetDayOfWeek(MinSupportedDateTime) - dayOfYear % 7;
+
+ // Calculate the offset (how many days from the start of the year to the start of the week)
+ int offset = (firstDayOfWeek + 7 - dayOfWeekOfFirstOfYear) % 7;
+ if (offset == 0 || offset >= minimumDaysInFirstWeek)
+ {
+ // First of year falls in the first week of the year
+ return 1;
+ }
+
+ int daysInYearBeforeMinSupportedYear = DaysInYearBeforeMinSupportedYear - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
+ int dayOfWeekOfFirstOfPreviousYear = dayOfWeekOfFirstOfYear - 1 - (daysInYearBeforeMinSupportedYear % 7);
+
+ // starting from first day of the year, how many days do you have to go forward
+ // before getting to the first day of the week?
+ int daysInInitialPartialWeek = (firstDayOfWeek - dayOfWeekOfFirstOfPreviousYear + 14) % 7;
+ int day = daysInYearBeforeMinSupportedYear - daysInInitialPartialWeek;
+ if (daysInInitialPartialWeek >= minimumDaysInFirstWeek)
+ {
+ // If the offset is greater than the minimum Days in the first week, it means that
+ // First of year is part of the first week of the year even though it is only a partial week
+ // add another week
+ day += 7;
+ }
+
+ return (day / 7 + 1);
+ }
+
+ // it would be nice to make this abstract but we can't since that would break previous implementations
+ protected virtual int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ return 365;
+ }
+ }
+
+
+ // Returns the week of year for the specified DateTime. The returned value is an
+ // integer between 1 and 53.
+ //
+
+ public virtual int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ if ((int)firstDayOfWeek < 0 || (int)firstDayOfWeek > 6) {
+ throw new ArgumentOutOfRangeException(
+ "firstDayOfWeek", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ Contract.EndContractBlock();
+ switch (rule) {
+ case CalendarWeekRule.FirstDay:
+ return (GetFirstDayWeekOfYear(time, (int)firstDayOfWeek));
+ case CalendarWeekRule.FirstFullWeek:
+ return (GetWeekOfYearFullDays(time, (int)firstDayOfWeek, 7));
+ case CalendarWeekRule.FirstFourDayWeek:
+ return (GetWeekOfYearFullDays(time, (int)firstDayOfWeek, 4));
+ }
+ throw new ArgumentOutOfRangeException(
+ "rule", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
+
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and 9999.
+ //
+
+ public abstract int GetYear(DateTime time);
+
+ // Checks whether a given day in the current era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public virtual bool IsLeapDay(int year, int month, int day)
+ {
+ return (IsLeapDay(year, month, day, CurrentEra));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public abstract bool IsLeapDay(int year, int month, int day, int era);
+
+ // Checks whether a given month in the current era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public virtual bool IsLeapMonth(int year, int month) {
+ return (IsLeapMonth(year, month, CurrentEra));
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public abstract bool IsLeapMonth(int year, int month, int era);
+
+ // Returns the leap month in a calendar year of the current era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual int GetLeapMonth(int year)
+ {
+ return (GetLeapMonth(year, CurrentEra));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual int GetLeapMonth(int year, int era)
+ {
+ if (!IsLeapYear(year, era))
+ return 0;
+
+ int monthsCount = GetMonthsInYear(year, era);
+ for (int month=1; month<=monthsCount; month++)
+ {
+ if (IsLeapMonth(year, month, era))
+ return month;
+ }
+
+ return 0;
+ }
+
+ // Checks whether a given year in the current era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public virtual bool IsLeapYear(int year)
+ {
+ return (IsLeapYear(year, CurrentEra));
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public abstract bool IsLeapYear(int year, int era);
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public virtual DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond)
+ {
+ return (ToDateTime(year, month, day, hour, minute, second, millisecond, CurrentEra));
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public abstract DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era);
+
+ internal virtual Boolean TryToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era, out DateTime result) {
+ result = DateTime.MinValue;
+ try {
+ result = ToDateTime(year, month, day, hour, minute, second, millisecond, era);
+ return true;
+ }
+ catch (ArgumentException) {
+ return false;
+ }
+ }
+
+ internal virtual bool IsValidYear(int year, int era) {
+ return (year >= GetYear(MinSupportedDateTime) && year <= GetYear(MaxSupportedDateTime));
+ }
+
+ internal virtual bool IsValidMonth(int year, int month, int era) {
+ return (IsValidYear(year, era) && month >= 1 && month <= GetMonthsInYear(year, era));
+ }
+
+ internal virtual bool IsValidDay(int year, int month, int day, int era)
+ {
+ return (IsValidMonth(year, month, era) && day >= 1 && day <= GetDaysInMonth(year, month, era));
+ }
+
+
+ // Returns and assigns the maximum value to represent a two digit year. This
+ // value is the upper boundary of a 100 year range that allows a two digit year
+ // to be properly translated to a four digit year. For example, if 2029 is the
+ // upper boundary, then a two digit value of 30 should be interpreted as 1930
+ // while a two digit value of 29 should be interpreted as 2029. In this example
+ // , the 100 year range would be from 1930-2029. See ToFourDigitYear().
+
+ public virtual int TwoDigitYearMax
+ {
+ get
+ {
+ return (twoDigitYearMax);
+ }
+
+ set
+ {
+ VerifyWritable();
+ twoDigitYearMax = value;
+ }
+ }
+
+ // Converts the year value to the appropriate century by using the
+ // TwoDigitYearMax property. For example, if the TwoDigitYearMax value is 2029,
+ // then a two digit value of 30 will get converted to 1930 while a two digit
+ // value of 29 will get converted to 2029.
+
+ public virtual int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+ if (year < 100) {
+ return ((TwoDigitYearMax/100 - ( year > TwoDigitYearMax % 100 ? 1 : 0))*100 + year);
+ }
+ // If the year value is above 100, just return the year value. Don't have to do
+ // the TwoDigitYearMax comparison.
+ return (year);
+ }
+
+ // Return the tick count corresponding to the given hour, minute, second.
+ // Will check the if the parameters are valid.
+ internal static long TimeToTicks(int hour, int minute, int second, int millisecond)
+ {
+ if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >=0 && second < 60)
+ {
+ if (millisecond < 0 || millisecond >= MillisPerSecond) {
+ throw new ArgumentOutOfRangeException(
+ "millisecond",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), 0, MillisPerSecond - 1));
+ }
+ return TimeSpan.TimeToTicks(hour, minute, second) + millisecond * TicksPerMillisecond;
+ }
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadHourMinuteSecond"));
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal static int GetSystemTwoDigitYearSetting(int CalID, int defaultYearValue)
+ {
+ // Call nativeGetTwoDigitYearMax
+ int twoDigitYearMax = CalendarData.nativeGetTwoDigitYearMax(CalID);
+ if (twoDigitYearMax < 0)
+ {
+ twoDigitYearMax = defaultYearValue;
+ }
+ return (twoDigitYearMax);
+ }
+
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/CalendarAlgorithmType.cs b/src/mscorlib/src/System/Globalization/CalendarAlgorithmType.cs
new file mode 100644
index 0000000000..4bf636656f
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CalendarAlgorithmType.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+
+[System.Runtime.InteropServices.ComVisible(true)]
+ public enum CalendarAlgorithmType {
+ Unknown = 0, // This is the default value to return in the Calendar base class.
+ SolarCalendar = 1, // Solar-base calendar, such as GregorianCalendar, jaoaneseCalendar, JulianCalendar, etc.
+ // Solar calendars are based on the solar year and seasons.
+ LunarCalendar = 2, // Lunar-based calendar, such as Hijri and UmAlQuraCalendar.
+ // Lunar calendars are based on the path of the moon. The seasons are not accurately represented.
+ LunisolarCalendar = 3 // Lunisolar-based calendar which use leap month rule, such as HebrewCalendar and Asian Lunisolar calendars.
+ // Lunisolar calendars are based on the cycle of the moon, but consider the seasons as a secondary consideration,
+ // so they align with the seasons as well as lunar events.
+
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/CalendarData.cs b/src/mscorlib/src/System/Globalization/CalendarData.cs
new file mode 100644
index 0000000000..8c187f0033
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CalendarData.cs
@@ -0,0 +1,455 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+
+ using System;
+ using System.Runtime.InteropServices;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.Versioning;
+ using System.Diagnostics.Contracts;
+ //
+ // List of calendar data
+ // Note the we cache overrides.
+ // Note that localized names (resource names) aren't available from here.
+ //
+ // NOTE: Calendars depend on the locale name that creates it. Only a few
+ // properties are available without locales using CalendarData.GetCalendar(int)
+
+ // StructLayout is needed here otherwise compiler can re-arrange the fields.
+ // We have to keep this in-sync with the definition in calendardata.h
+ //
+ // WARNING WARNING WARNING
+ //
+ // WARNING: Anything changed here also needs to be updated on the native side (object.h see type CalendarDataBaseObject)
+ // WARNING: The type loader will rearrange class member offsets so the mscorwks!CalendarDataBaseObject
+ // WARNING: must be manually structured to match the true loaded class layout
+ //
+ internal class CalendarData
+ {
+ // Max calendars
+ internal const int MAX_CALENDARS = 23;
+
+ // Identity
+ internal String sNativeName ; // Calendar Name for the locale
+
+ // Formats
+ internal String[] saShortDates ; // Short Data format, default first
+ internal String[] saYearMonths ; // Year/Month Data format, default first
+ internal String[] saLongDates ; // Long Data format, default first
+ internal String sMonthDay ; // Month/Day format
+
+ // Calendar Parts Names
+ internal String[] saEraNames ; // Names of Eras
+ internal String[] saAbbrevEraNames ; // Abbreviated Era Names
+ internal String[] saAbbrevEnglishEraNames ; // Abbreviated Era Names in English
+ internal String[] saDayNames ; // Day Names, null to use locale data, starts on Sunday
+ internal String[] saAbbrevDayNames ; // Abbrev Day Names, null to use locale data, starts on Sunday
+ internal String[] saSuperShortDayNames ; // Super short Day of week names
+ internal String[] saMonthNames ; // Month Names (13)
+ internal String[] saAbbrevMonthNames ; // Abbrev Month Names (13)
+ internal String[] saMonthGenitiveNames ; // Genitive Month Names (13)
+ internal String[] saAbbrevMonthGenitiveNames; // Genitive Abbrev Month Names (13)
+ internal String[] saLeapYearMonthNames ; // Multiple strings for the month names in a leap year.
+
+ // Integers at end to make marshaller happier
+ internal int iTwoDigitYearMax=2029 ; // Max 2 digit year (for Y2K bug data entry)
+ internal int iCurrentEra=0 ; // current era # (usually 1)
+
+ // Use overrides?
+ internal bool bUseUserOverrides ; // True if we want user overrides.
+
+ // Static invariant for the invariant locale
+ internal static CalendarData Invariant;
+
+ // Private constructor
+ private CalendarData() {}
+
+ // Invariant constructor
+ static CalendarData()
+ {
+
+ // Set our default/gregorian US calendar data
+ // Calendar IDs are 1-based, arrays are 0 based.
+ CalendarData invariant = new CalendarData();
+
+ // Set default data for calendar
+ // Note that we don't load resources since this IS NOT supposed to change (by definition)
+ invariant.sNativeName = "Gregorian Calendar"; // Calendar Name
+
+ // Year
+ invariant.iTwoDigitYearMax = 2029; // Max 2 digit year (for Y2K bug data entry)
+ invariant.iCurrentEra = 1; // Current era #
+
+ // Formats
+ invariant.saShortDates = new String[] { "MM/dd/yyyy", "yyyy-MM-dd" }; // short date format
+ invariant.saLongDates = new String[] { "dddd, dd MMMM yyyy"}; // long date format
+ invariant.saYearMonths = new String[] { "yyyy MMMM" }; // year month format
+ invariant.sMonthDay = "MMMM dd"; // Month day pattern
+
+ // Calendar Parts Names
+ invariant.saEraNames = new String[] { "A.D." }; // Era names
+ invariant.saAbbrevEraNames = new String[] { "AD" }; // Abbreviated Era names
+ invariant.saAbbrevEnglishEraNames=new String[] { "AD" }; // Abbreviated era names in English
+ invariant.saDayNames = new String[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };// day names
+ invariant.saAbbrevDayNames = new String[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; // abbreviated day names
+ invariant.saSuperShortDayNames = new String[] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" }; // The super short day names
+ invariant.saMonthNames = new String[] { "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December", String.Empty}; // month names
+ invariant.saAbbrevMonthNames = new String[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", String.Empty}; // abbreviated month names
+ invariant.saMonthGenitiveNames = invariant.saMonthNames; // Genitive month names (same as month names for invariant)
+ invariant.saAbbrevMonthGenitiveNames=invariant.saAbbrevMonthNames; // Abbreviated genitive month names (same as abbrev month names for invariant)
+ invariant.saLeapYearMonthNames = invariant.saMonthNames; // leap year month names are unused in Gregorian English (invariant)
+
+ invariant.bUseUserOverrides = false;
+
+ // Calendar was built, go ahead and assign it...
+ Invariant = invariant;
+ }
+
+
+
+ //
+ // Get a bunch of data for a calendar
+ //
+ internal CalendarData(String localeName, int calendarId, bool bUseUserOverrides)
+ {
+ // Call nativeGetCalendarData to populate the data
+ this.bUseUserOverrides = bUseUserOverrides;
+ if (!nativeGetCalendarData(this, localeName, calendarId))
+ {
+ Contract.Assert(false, "[CalendarData] nativeGetCalendarData call isn't expected to fail for calendar " + calendarId + " locale " +localeName);
+
+ // Something failed, try invariant for missing parts
+ // This is really not good, but we don't want the callers to crash.
+ if (this.sNativeName == null) this.sNativeName = String.Empty; // Calendar Name for the locale.
+
+ // Formats
+ if (this.saShortDates == null) this.saShortDates = Invariant.saShortDates; // Short Data format, default first
+ if (this.saYearMonths == null) this.saYearMonths = Invariant.saYearMonths; // Year/Month Data format, default first
+ if (this.saLongDates == null) this.saLongDates = Invariant.saLongDates; // Long Data format, default first
+ if (this.sMonthDay == null) this.sMonthDay = Invariant.sMonthDay; // Month/Day format
+
+ // Calendar Parts Names
+ if (this.saEraNames == null) this.saEraNames = Invariant.saEraNames; // Names of Eras
+ if (this.saAbbrevEraNames == null) this.saAbbrevEraNames = Invariant.saAbbrevEraNames; // Abbreviated Era Names
+ if (this.saAbbrevEnglishEraNames == null) this.saAbbrevEnglishEraNames = Invariant.saAbbrevEnglishEraNames; // Abbreviated Era Names in English
+ if (this.saDayNames == null) this.saDayNames = Invariant.saDayNames; // Day Names, null to use locale data, starts on Sunday
+ if (this.saAbbrevDayNames == null) this.saAbbrevDayNames = Invariant.saAbbrevDayNames; // Abbrev Day Names, null to use locale data, starts on Sunday
+ if (this.saSuperShortDayNames == null) this.saSuperShortDayNames = Invariant.saSuperShortDayNames; // Super short Day of week names
+ if (this.saMonthNames == null) this.saMonthNames = Invariant.saMonthNames; // Month Names (13)
+ if (this.saAbbrevMonthNames == null) this.saAbbrevMonthNames = Invariant.saAbbrevMonthNames; // Abbrev Month Names (13)
+ // Genitive and Leap names can follow the fallback below
+ }
+
+ // Clean up the escaping of the formats
+ this.saShortDates = CultureData.ReescapeWin32Strings(this.saShortDates);
+ this.saLongDates = CultureData.ReescapeWin32Strings(this.saLongDates);
+ this.saYearMonths = CultureData.ReescapeWin32Strings(this.saYearMonths);
+ this.sMonthDay = CultureData.ReescapeWin32String(this.sMonthDay);
+
+ if ((CalendarId)calendarId == CalendarId.TAIWAN)
+ {
+ if (CultureInfo.IsTaiwanSku)
+ {
+ // We got the month/day names from the OS (same as gregorian), but the native name is wrong
+ this.sNativeName = "\x4e2d\x83ef\x6c11\x570b\x66c6";
+ }
+ else
+ {
+ this.sNativeName = String.Empty;
+ }
+ }
+
+ // Check for null genitive names (in case unmanaged side skips it for non-gregorian calendars, etc)
+ if (this.saMonthGenitiveNames == null || String.IsNullOrEmpty(this.saMonthGenitiveNames[0]))
+ this.saMonthGenitiveNames = this.saMonthNames; // Genitive month names (same as month names for invariant)
+ if (this.saAbbrevMonthGenitiveNames == null || String.IsNullOrEmpty(this.saAbbrevMonthGenitiveNames[0]))
+ this.saAbbrevMonthGenitiveNames = this.saAbbrevMonthNames; // Abbreviated genitive month names (same as abbrev month names for invariant)
+ if (this.saLeapYearMonthNames == null || String.IsNullOrEmpty(this.saLeapYearMonthNames[0]))
+ this.saLeapYearMonthNames = this.saMonthNames;
+
+ InitializeEraNames(localeName, calendarId);
+
+ InitializeAbbreviatedEraNames(localeName, calendarId);
+
+ // Abbreviated English Era Names are only used for the Japanese calendar.
+ if (calendarId == (int)CalendarId.JAPAN)
+ {
+ this.saAbbrevEnglishEraNames = JapaneseCalendar.EnglishEraNames();
+ }
+ else
+ {
+ // For all others just use the an empty string (doesn't matter we'll never ask for it for other calendars)
+ this.saAbbrevEnglishEraNames = new String[] { "" };
+ }
+
+ // Japanese is the only thing with > 1 era. Its current era # is how many ever
+ // eras are in the array. (And the others all have 1 string in the array)
+ this.iCurrentEra = this.saEraNames.Length;
+ }
+
+ private void InitializeEraNames(string localeName, int calendarId)
+ {
+ // Note that the saEraNames only include "A.D." We don't have localized names for other calendars available from windows
+ switch ((CalendarId)calendarId)
+ {
+ // For Localized Gregorian we really expect the data from the OS.
+ case CalendarId.GREGORIAN:
+ // Fallback for CoreCLR < Win7 or culture.dll missing
+ if (this.saEraNames == null || this.saEraNames.Length == 0 || String.IsNullOrEmpty(this.saEraNames[0]))
+ {
+ this.saEraNames = new String[] { "A.D." };
+ }
+ break;
+
+ // The rest of the calendars have constant data, so we'll just use that
+ case CalendarId.GREGORIAN_US:
+ case CalendarId.JULIAN:
+ this.saEraNames = new String[] { "A.D." };
+ break;
+ case CalendarId.HEBREW:
+ this.saEraNames = new String[] { "C.E." };
+ break;
+ case CalendarId.HIJRI:
+ case CalendarId.UMALQURA:
+ if (localeName == "dv-MV")
+ {
+ // Special case for Divehi
+ this.saEraNames = new String[] { "\x0780\x07a8\x0796\x07b0\x0783\x07a9" };
+ }
+ else
+ {
+ this.saEraNames = new String[] { "\x0628\x0639\x062F \x0627\x0644\x0647\x062C\x0631\x0629" };
+ }
+ break;
+ case CalendarId.GREGORIAN_ARABIC:
+ case CalendarId.GREGORIAN_XLIT_ENGLISH:
+ case CalendarId.GREGORIAN_XLIT_FRENCH:
+ // These are all the same:
+ this.saEraNames = new String[] { "\x0645" };
+ break;
+
+ case CalendarId.GREGORIAN_ME_FRENCH:
+ this.saEraNames = new String[] { "ap. J.-C." };
+ break;
+
+ case CalendarId.TAIWAN:
+ if (CultureInfo.IsTaiwanSku)
+ {
+ this.saEraNames = new String[] { "\x4e2d\x83ef\x6c11\x570b" };
+ }
+ else
+ {
+ this.saEraNames = new String[] { String.Empty };
+ }
+ break;
+
+ case CalendarId.KOREA:
+ this.saEraNames = new String[] { "\xb2e8\xae30" };
+ break;
+
+ case CalendarId.THAI:
+ this.saEraNames = new String[] { "\x0e1e\x002e\x0e28\x002e" };
+ break;
+
+ case CalendarId.JAPAN:
+ case CalendarId.JAPANESELUNISOLAR:
+ this.saEraNames = JapaneseCalendar.EraNames();
+ break;
+
+ case CalendarId.PERSIAN:
+ if (this.saEraNames == null || this.saEraNames.Length == 0 || String.IsNullOrEmpty(this.saEraNames[0]))
+ {
+ this.saEraNames = new String[] { "\x0647\x002e\x0634" };
+ }
+ break;
+
+ default:
+ // Most calendars are just "A.D."
+ this.saEraNames = Invariant.saEraNames;
+ break;
+ }
+ }
+
+ private void InitializeAbbreviatedEraNames(string localeName, int calendarId)
+ {
+ // Note that the saAbbrevEraNames only include "AD" We don't have localized names for other calendars available from windows
+ switch ((CalendarId)calendarId)
+ {
+ // For Localized Gregorian we really expect the data from the OS.
+ case CalendarId.GREGORIAN:
+ // Fallback for culture.dll missing
+ if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || String.IsNullOrEmpty(this.saAbbrevEraNames[0]))
+ {
+ this.saAbbrevEraNames = new String[] { "AD" };
+ }
+ break;
+
+ // The rest of the calendars have constant data, so we'll just use that
+ case CalendarId.GREGORIAN_US:
+ case CalendarId.JULIAN:
+ this.saAbbrevEraNames = new String[] { "AD" };
+ break;
+ case CalendarId.JAPAN:
+ case CalendarId.JAPANESELUNISOLAR:
+ this.saAbbrevEraNames = JapaneseCalendar.AbbrevEraNames();
+ break;
+ case CalendarId.HIJRI:
+ case CalendarId.UMALQURA:
+ if (localeName == "dv-MV")
+ {
+ // Special case for Divehi
+ this.saAbbrevEraNames = new String[] { "\x0780\x002e" };
+ }
+ else
+ {
+ this.saAbbrevEraNames = new String[] { "\x0647\x0640" };
+ }
+ break;
+ case CalendarId.TAIWAN:
+ // Get era name and abbreviate it
+ this.saAbbrevEraNames = new String[1];
+ if (this.saEraNames[0].Length == 4)
+ {
+ this.saAbbrevEraNames[0] = this.saEraNames[0].Substring(2,2);
+ }
+ else
+ {
+ this.saAbbrevEraNames[0] = this.saEraNames[0];
+ }
+ break;
+
+ case CalendarId.PERSIAN:
+ if (this.saAbbrevEraNames == null || this.saAbbrevEraNames.Length == 0 || String.IsNullOrEmpty(this.saAbbrevEraNames[0]))
+ {
+ this.saAbbrevEraNames = this.saEraNames;
+ }
+ break;
+
+ default:
+ // Most calendars just use the full name
+ this.saAbbrevEraNames = this.saEraNames;
+ break;
+ }
+ }
+
+ internal static CalendarData GetCalendarData(int calendarId)
+ {
+ //
+ // Get a calendar.
+ // Unfortunately we depend on the locale in the OS, so we need a locale
+ // no matter what. So just get the appropriate calendar from the
+ // appropriate locale here
+ //
+
+ // Get a culture name
+ String culture = CalendarIdToCultureName(calendarId);
+
+ // Return our calendar
+ return CultureInfo.GetCultureInfo(culture).m_cultureData.GetCalendar(calendarId);
+ }
+
+ //
+ // Helper methods
+ //
+ private static String CalendarIdToCultureName(int calendarId)
+ {
+ // note that this doesn't handle the new calendars (lunisolar, etc)
+ switch (calendarId)
+ {
+ case Calendar.CAL_GREGORIAN_US:
+ return "fa-IR"; // "fa-IR" Iran
+
+ case Calendar.CAL_JAPAN:
+ return "ja-JP"; // "ja-JP" Japan
+
+ case Calendar.CAL_TAIWAN: // zh-TW Taiwan
+ return "zh-TW";
+
+ case Calendar.CAL_KOREA:
+ return "ko-KR"; // "ko-KR" Korea
+
+ case Calendar.CAL_HIJRI:
+ case Calendar.CAL_GREGORIAN_ARABIC:
+ case Calendar.CAL_UMALQURA:
+ return "ar-SA"; // "ar-SA" Saudi Arabia
+
+ case Calendar.CAL_THAI:
+ return "th-TH"; // "th-TH" Thailand
+
+ case Calendar.CAL_HEBREW:
+ return "he-IL"; // "he-IL" Israel
+
+ case Calendar.CAL_GREGORIAN_ME_FRENCH:
+ return "ar-DZ"; // "ar-DZ" Algeria
+
+ case Calendar.CAL_GREGORIAN_XLIT_ENGLISH:
+ case Calendar.CAL_GREGORIAN_XLIT_FRENCH:
+ return "ar-IQ"; // "ar-IQ"; Iraq
+
+ default:
+ // Default to gregorian en-US
+ break;
+ }
+
+ return "en-US";
+ }
+
+ internal void FixupWin7MonthDaySemicolonBug()
+ {
+ int unescapedCharacterIndex = FindUnescapedCharacter(sMonthDay, ';');
+ if (unescapedCharacterIndex > 0)
+ {
+ sMonthDay = sMonthDay.Substring(0, unescapedCharacterIndex);
+ }
+ }
+ private static int FindUnescapedCharacter(string s, char charToFind)
+ {
+ bool inComment = false;
+ int length = s.Length;
+ for (int i = 0; i < length; i++)
+ {
+ char c = s[i];
+
+ switch (c)
+ {
+ case '\'':
+ inComment = !inComment;
+ break;
+ case '\\':
+ i++; // escape sequence -- skip next character
+ break;
+ default:
+ if (!inComment && charToFind == c)
+ {
+ return i;
+ }
+ break;
+ }
+ }
+ return -1;
+ }
+
+
+ // Get native two digit year max
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern int nativeGetTwoDigitYearMax(int calID);
+
+ // Call native side to load our calendar data
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private static extern bool nativeGetCalendarData(CalendarData data, String localeName, int calendar);
+
+ // Call native side to figure out which calendars are allowed
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern int nativeGetCalendars(String localeName, bool useUserOverride, [In, Out] int[] calendars);
+
+ }
+ }
+
diff --git a/src/mscorlib/src/System/Globalization/CalendarWeekRule.cs b/src/mscorlib/src/System/Globalization/CalendarWeekRule.cs
new file mode 100644
index 0000000000..578b5672fc
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CalendarWeekRule.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public enum CalendarWeekRule
+ {
+
+ FirstDay = 0, // Week 1 begins on the first day of the year
+
+ FirstFullWeek = 1, // Week 1 begins on first FirstDayOfWeek not before the first day of the year
+
+ FirstFourDayWeek = 2 // Week 1 begins on first FirstDayOfWeek such that FirstDayOfWeek+3 is not before the first day of the year
+ };
+}
diff --git a/src/mscorlib/src/System/Globalization/CalendricalCalculationsHelper.cs b/src/mscorlib/src/System/Globalization/CalendricalCalculationsHelper.cs
new file mode 100644
index 0000000000..7084511ce9
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CalendricalCalculationsHelper.cs
@@ -0,0 +1,413 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ using System;
+ using System.Diagnostics.Contracts;
+
+ internal class CalendricalCalculationsHelper
+ {
+ const double FullCircleOfArc = 360.0; // 360.0;
+ const int HalfCircleOfArc = 180;
+ const double TwelveHours = 0.5; // half a day
+ const double Noon2000Jan01 = 730120.5;
+ internal const double MeanTropicalYearInDays = 365.242189;
+ const double MeanSpeedOfSun = MeanTropicalYearInDays / FullCircleOfArc;
+ const double LongitudeSpring = 0.0;
+ const double TwoDegreesAfterSpring = 2.0;
+ const int SecondsPerDay = 24 * 60 * 60; // 24 hours * 60 minutes * 60 seconds
+
+ const int DaysInUniformLengthCentury = 36525;
+ const int SecondsPerMinute = 60;
+ const int MinutesPerDegree = 60;
+
+ static long StartOf1810 = GetNumberOfDays(new DateTime(1810, 1, 1));
+ static long StartOf1900Century = GetNumberOfDays(new DateTime(1900, 1, 1));
+
+ static double[] Coefficients1900to1987 = new double[] { -0.00002, 0.000297, 0.025184, -0.181133, 0.553040, -0.861938, 0.677066, -0.212591 };
+ static double[] Coefficients1800to1899 = new double[] { -0.000009, 0.003844, 0.083563, 0.865736, 4.867575, 15.845535, 31.332267, 38.291999, 28.316289, 11.636204, 2.043794 };
+ static double[] Coefficients1700to1799 = new double[] { 8.118780842, -0.005092142, 0.003336121, -0.0000266484 };
+ static double[] Coefficients1620to1699 = new double[] { 196.58333, -4.0675, 0.0219167 };
+ static double[] LambdaCoefficients = new double[] { 280.46645, 36000.76983, 0.0003032 };
+ static double[] AnomalyCoefficients = new double[] { 357.52910, 35999.05030, -0.0001559, -0.00000048 };
+ static double[] EccentricityCoefficients = new double[] { 0.016708617, -0.000042037, -0.0000001236 };
+ static double[] Coefficients = new double[] { Angle(23, 26, 21.448), Angle(0, 0, -46.8150), Angle(0, 0, -0.00059), Angle(0, 0, 0.001813) };
+ static double[] CoefficientsA = new double[] { 124.90, -1934.134, 0.002063 };
+ static double[] CoefficientsB = new double[] { 201.11, 72001.5377, 0.00057 };
+
+ static double RadiansFromDegrees(double degree)
+ {
+ return degree * Math.PI / 180;
+ }
+
+ static double SinOfDegree(double degree)
+ {
+ return Math.Sin(RadiansFromDegrees(degree));
+ }
+
+ static double CosOfDegree(double degree)
+ {
+ return Math.Cos(RadiansFromDegrees(degree));
+ }
+ static double TanOfDegree(double degree)
+ {
+ return Math.Tan(RadiansFromDegrees(degree));
+ }
+
+ public static double Angle(int degrees, int minutes, double seconds)
+ {
+ return ((seconds / SecondsPerMinute + minutes) / MinutesPerDegree) + degrees;
+ }
+
+ static double Obliquity(double julianCenturies)
+ {
+ return PolynomialSum(Coefficients, julianCenturies);
+ }
+
+ internal static long GetNumberOfDays(DateTime date)
+ {
+ return date.Ticks / GregorianCalendar.TicksPerDay;
+ }
+
+ static int GetGregorianYear(double numberOfDays)
+ {
+ return new DateTime(Math.Min((long)(Math.Floor(numberOfDays) * GregorianCalendar.TicksPerDay), DateTime.MaxValue.Ticks)).Year;
+ }
+
+ enum CorrectionAlgorithm
+ {
+ Default,
+ Year1988to2019,
+ Year1900to1987,
+ Year1800to1899,
+ Year1700to1799,
+ Year1620to1699
+ }
+
+ struct EphemerisCorrectionAlgorithmMap
+ {
+ public EphemerisCorrectionAlgorithmMap(int year, CorrectionAlgorithm algorithm)
+ {
+ _lowestYear = year;
+ _algorithm = algorithm;
+ }
+
+ internal int _lowestYear;
+ internal CorrectionAlgorithm _algorithm;
+ };
+
+ static EphemerisCorrectionAlgorithmMap[] EphemerisCorrectionTable = new EphemerisCorrectionAlgorithmMap[]
+ {
+ // lowest year that starts algorithm, algorithm to use
+ new EphemerisCorrectionAlgorithmMap(2020, CorrectionAlgorithm.Default),
+ new EphemerisCorrectionAlgorithmMap(1988, CorrectionAlgorithm.Year1988to2019),
+ new EphemerisCorrectionAlgorithmMap(1900, CorrectionAlgorithm.Year1900to1987),
+ new EphemerisCorrectionAlgorithmMap(1800, CorrectionAlgorithm.Year1800to1899),
+ new EphemerisCorrectionAlgorithmMap(1700, CorrectionAlgorithm.Year1700to1799),
+ new EphemerisCorrectionAlgorithmMap(1620, CorrectionAlgorithm.Year1620to1699),
+ new EphemerisCorrectionAlgorithmMap(int.MinValue, CorrectionAlgorithm.Default) // default must be last
+ };
+
+ static double Reminder(double divisor, double dividend)
+ {
+ double whole = Math.Floor(divisor / dividend);
+ return divisor - (dividend * whole);
+ }
+
+ static double NormalizeLongitude(double longitude)
+ {
+ longitude = Reminder(longitude, FullCircleOfArc);
+ if (longitude < 0)
+ {
+ longitude += FullCircleOfArc;
+ }
+ return longitude;
+ }
+
+ static public double AsDayFraction(double longitude)
+ {
+ return longitude / FullCircleOfArc;
+ }
+
+ static double PolynomialSum(double[] coefficients, double indeterminate)
+ {
+ double sum = coefficients[0];
+ double indeterminateRaised = 1;
+ for (int i=1; i<coefficients.Length; i++)
+ {
+ indeterminateRaised *= indeterminate;
+ sum += (coefficients[i] * indeterminateRaised);
+ }
+
+ return sum;
+ }
+
+ static double CenturiesFrom1900(int gregorianYear)
+ {
+ long july1stOfYear = GetNumberOfDays(new DateTime(gregorianYear, 7, 1));
+ return (double) (july1stOfYear - StartOf1900Century) / DaysInUniformLengthCentury;
+ }
+
+ // the following formulas defines a polynomial function which gives us the amount that the earth is slowing down for specific year ranges
+ static double DefaultEphemerisCorrection(int gregorianYear)
+ {
+ Contract.Assert(gregorianYear < 1620 || 2020 <= gregorianYear);
+ long january1stOfYear = GetNumberOfDays(new DateTime(gregorianYear, 1, 1));
+ double daysSinceStartOf1810 = january1stOfYear - StartOf1810;
+ double x = TwelveHours + daysSinceStartOf1810;
+ return ((Math.Pow(x, 2) / 41048480) - 15) / SecondsPerDay;
+ }
+
+ static double EphemerisCorrection1988to2019(int gregorianYear)
+ {
+ Contract.Assert(1988 <= gregorianYear && gregorianYear <= 2019);
+ return (double)(gregorianYear - 1933) / SecondsPerDay;
+ }
+
+ static double EphemerisCorrection1900to1987(int gregorianYear)
+ {
+ Contract.Assert(1900 <= gregorianYear && gregorianYear <= 1987);
+ double centuriesFrom1900 = CenturiesFrom1900(gregorianYear);
+ return PolynomialSum(Coefficients1900to1987, centuriesFrom1900);
+ }
+
+ static double EphemerisCorrection1800to1899(int gregorianYear)
+ {
+ Contract.Assert(1800 <= gregorianYear && gregorianYear <= 1899);
+ double centuriesFrom1900 = CenturiesFrom1900(gregorianYear);
+ return PolynomialSum(Coefficients1800to1899, centuriesFrom1900);
+ }
+
+ static double EphemerisCorrection1700to1799(int gregorianYear)
+ {
+ Contract.Assert(1700 <= gregorianYear && gregorianYear <= 1799);
+ double yearsSince1700 = gregorianYear - 1700;
+ return PolynomialSum(Coefficients1700to1799, yearsSince1700) / SecondsPerDay;
+ }
+
+ static double EphemerisCorrection1620to1699(int gregorianYear)
+ {
+ Contract.Assert(1620 <= gregorianYear && gregorianYear <= 1699);
+ double yearsSince1600 = gregorianYear - 1600;
+ return PolynomialSum(Coefficients1620to1699, yearsSince1600) / SecondsPerDay;
+ }
+
+ // ephemeris-correction: correction to account for the slowing down of the rotation of the earth
+ static double EphemerisCorrection(double time)
+ {
+ int year = GetGregorianYear(time);
+ foreach (EphemerisCorrectionAlgorithmMap map in EphemerisCorrectionTable)
+ {
+ if (map._lowestYear <= year)
+ {
+ switch (map._algorithm)
+ {
+ case CorrectionAlgorithm.Default: return DefaultEphemerisCorrection(year);
+ case CorrectionAlgorithm.Year1988to2019: return EphemerisCorrection1988to2019(year);
+ case CorrectionAlgorithm.Year1900to1987: return EphemerisCorrection1900to1987(year);
+ case CorrectionAlgorithm.Year1800to1899: return EphemerisCorrection1800to1899(year);
+ case CorrectionAlgorithm.Year1700to1799: return EphemerisCorrection1700to1799(year);
+ case CorrectionAlgorithm.Year1620to1699: return EphemerisCorrection1620to1699(year);
+ }
+
+ break; // break the loop and assert eventually
+ }
+ }
+
+ Contract.Assert(false, "Not expected to come here");
+ return DefaultEphemerisCorrection(year);
+ }
+
+ static public double JulianCenturies(double moment)
+ {
+ double dynamicalMoment = moment + EphemerisCorrection(moment);
+ return (dynamicalMoment - Noon2000Jan01) / DaysInUniformLengthCentury;
+ }
+
+ static bool IsNegative(double value)
+ {
+ return Math.Sign(value) == -1;
+ }
+
+ static double CopySign(double value, double sign)
+ {
+ return (IsNegative(value) == IsNegative(sign)) ? value : -value;
+ }
+
+ // equation-of-time; approximate the difference between apparent solar time and mean solar time
+ // formal definition is EOT = GHA - GMHA
+ // GHA is the Greenwich Hour Angle of the apparent (actual) Sun
+ // GMHA is the Greenwich Mean Hour Angle of the mean (fictitious) Sun
+ // http://www.esrl.noaa.gov/gmd/grad/solcalc/
+ // http://en.wikipedia.org/wiki/Equation_of_time
+ static double EquationOfTime(double time)
+ {
+ double julianCenturies = JulianCenturies(time);
+ double lambda = PolynomialSum(LambdaCoefficients, julianCenturies);
+ double anomaly = PolynomialSum(AnomalyCoefficients, julianCenturies);
+ double eccentricity = PolynomialSum(EccentricityCoefficients, julianCenturies);
+
+ double epsilon = Obliquity(julianCenturies);
+ double tanHalfEpsilon = TanOfDegree(epsilon / 2);
+ double y = tanHalfEpsilon * tanHalfEpsilon;
+
+ double dividend = ((y * SinOfDegree(2 * lambda))
+ - (2 * eccentricity * SinOfDegree(anomaly))
+ + (4 * eccentricity * y * SinOfDegree(anomaly) * CosOfDegree(2 * lambda))
+ - (0.5 * Math.Pow(y, 2) * SinOfDegree(4 * lambda))
+ - (1.25 * Math.Pow(eccentricity, 2) * SinOfDegree(2 * anomaly)));
+ double divisor = 2 * Math.PI;
+ double equation = dividend / divisor;
+
+ // approximation of equation of time is not valid for dates that are many millennia in the past or future
+ // thus limited to a half day
+ return CopySign(Math.Min(Math.Abs(equation), TwelveHours), equation);
+ }
+
+ static double AsLocalTime(double apparentMidday, double longitude)
+ {
+ // slightly inaccurate since equation of time takes mean time not apparent time as its argument, but the difference is negligible
+ double universalTime = apparentMidday - AsDayFraction(longitude);
+ return apparentMidday - EquationOfTime(universalTime);
+ }
+
+ // midday
+ static public double Midday(double date, double longitude)
+ {
+ return AsLocalTime(date+TwelveHours, longitude) - AsDayFraction(longitude);
+ }
+
+ static double InitLongitude(double longitude)
+ {
+ return NormalizeLongitude(longitude + HalfCircleOfArc) - HalfCircleOfArc;
+ }
+
+ // midday-in-tehran
+ static public double MiddayAtPersianObservationSite(double date)
+ {
+ return Midday(date, InitLongitude(52.5)); // 52.5 degrees east - longitude of UTC+3:30 which defines Iranian Standard Time
+ }
+
+ static double PeriodicTerm(double julianCenturies, int x, double y, double z)
+ {
+ return x * SinOfDegree(y + z * julianCenturies);
+ }
+
+ static double SumLongSequenceOfPeriodicTerms(double julianCenturies)
+ {
+ double sum = 0.0;
+ sum += PeriodicTerm(julianCenturies, 403406, 270.54861, 0.9287892);
+ sum += PeriodicTerm(julianCenturies, 195207, 340.19128, 35999.1376958);
+ sum += PeriodicTerm(julianCenturies, 119433, 63.91854, 35999.4089666);
+ sum += PeriodicTerm(julianCenturies, 112392, 331.2622, 35998.7287385);
+ sum += PeriodicTerm(julianCenturies, 3891, 317.843, 71998.20261);
+ sum += PeriodicTerm(julianCenturies, 2819, 86.631, 71998.4403);
+ sum += PeriodicTerm(julianCenturies, 1721, 240.052, 36000.35726);
+ sum += PeriodicTerm(julianCenturies, 660, 310.26, 71997.4812);
+ sum += PeriodicTerm(julianCenturies, 350, 247.23, 32964.4678);
+ sum += PeriodicTerm(julianCenturies, 334, 260.87, -19.441);
+ sum += PeriodicTerm(julianCenturies, 314, 297.82, 445267.1117);
+ sum += PeriodicTerm(julianCenturies, 268, 343.14, 45036.884);
+ sum += PeriodicTerm(julianCenturies, 242, 166.79, 3.1008);
+ sum += PeriodicTerm(julianCenturies, 234, 81.53, 22518.4434);
+ sum += PeriodicTerm(julianCenturies, 158, 3.5, -19.9739);
+ sum += PeriodicTerm(julianCenturies, 132, 132.75, 65928.9345);
+ sum += PeriodicTerm(julianCenturies, 129, 182.95, 9038.0293);
+ sum += PeriodicTerm(julianCenturies, 114, 162.03, 3034.7684);
+ sum += PeriodicTerm(julianCenturies, 99, 29.8, 33718.148);
+ sum += PeriodicTerm(julianCenturies, 93, 266.4, 3034.448);
+ sum += PeriodicTerm(julianCenturies, 86, 249.2, -2280.773);
+ sum += PeriodicTerm(julianCenturies, 78, 157.6, 29929.992);
+ sum += PeriodicTerm(julianCenturies, 72, 257.8, 31556.493);
+ sum += PeriodicTerm(julianCenturies, 68, 185.1, 149.588);
+ sum += PeriodicTerm(julianCenturies, 64, 69.9, 9037.75);
+ sum += PeriodicTerm(julianCenturies, 46, 8.0, 107997.405);
+ sum += PeriodicTerm(julianCenturies, 38, 197.1, -4444.176);
+ sum += PeriodicTerm(julianCenturies, 37, 250.4, 151.771);
+ sum += PeriodicTerm(julianCenturies, 32, 65.3, 67555.316);
+ sum += PeriodicTerm(julianCenturies, 29, 162.7, 31556.08);
+ sum += PeriodicTerm(julianCenturies, 28, 341.5, -4561.54);
+ sum += PeriodicTerm(julianCenturies, 27, 291.6, 107996.706);
+ sum += PeriodicTerm(julianCenturies, 27, 98.5, 1221.655);
+ sum += PeriodicTerm(julianCenturies, 25, 146.7, 62894.167);
+ sum += PeriodicTerm(julianCenturies, 24, 110.0, 31437.369);
+ sum += PeriodicTerm(julianCenturies, 21, 5.2, 14578.298);
+ sum += PeriodicTerm(julianCenturies, 21, 342.6, -31931.757);
+ sum += PeriodicTerm(julianCenturies, 20, 230.9, 34777.243);
+ sum += PeriodicTerm(julianCenturies, 18, 256.1, 1221.999);
+ sum += PeriodicTerm(julianCenturies, 17, 45.3, 62894.511);
+ sum += PeriodicTerm(julianCenturies, 14, 242.9, -4442.039);
+ sum += PeriodicTerm(julianCenturies, 13, 115.2, 107997.909);
+ sum += PeriodicTerm(julianCenturies, 13, 151.8, 119.066);
+ sum += PeriodicTerm(julianCenturies, 13, 285.3, 16859.071);
+ sum += PeriodicTerm(julianCenturies, 12, 53.3, -4.578);
+ sum += PeriodicTerm(julianCenturies, 10, 126.6, 26895.292);
+ sum += PeriodicTerm(julianCenturies, 10, 205.7, -39.127);
+ sum += PeriodicTerm(julianCenturies, 10, 85.9, 12297.536);
+ sum += PeriodicTerm(julianCenturies, 10, 146.1, 90073.778);
+ return sum;
+ }
+
+ static double Aberration(double julianCenturies)
+ {
+ return (0.0000974 * CosOfDegree(177.63 + (35999.01848 * julianCenturies))) - 0.005575;
+ }
+
+ static double Nutation(double julianCenturies)
+ {
+ double a = PolynomialSum(CoefficientsA, julianCenturies);
+ double b = PolynomialSum(CoefficientsB, julianCenturies);
+ return (-0.004778 * SinOfDegree(a)) - (0.0003667 * SinOfDegree(b));
+ }
+
+ static public double Compute(double time)
+ {
+ double julianCenturies = JulianCenturies(time);
+ double lambda = 282.7771834
+ + (36000.76953744 * julianCenturies)
+ + (0.000005729577951308232 * SumLongSequenceOfPeriodicTerms(julianCenturies));
+
+ double longitude = lambda + Aberration(julianCenturies) + Nutation(julianCenturies);
+ return InitLongitude(longitude);
+ }
+
+ static public double AsSeason(double longitude)
+ {
+ return (longitude < 0) ? (longitude + FullCircleOfArc) : longitude;
+ }
+
+ static double EstimatePrior(double longitude, double time)
+ {
+ double timeSunLastAtLongitude = time - (MeanSpeedOfSun * AsSeason(InitLongitude(Compute(time) - longitude)));
+ double longitudeErrorDelta = InitLongitude(Compute(timeSunLastAtLongitude) - longitude);
+ return Math.Min(time, timeSunLastAtLongitude - (MeanSpeedOfSun * longitudeErrorDelta));
+ }
+
+ // persian-new-year-on-or-before
+ // number of days is the absolute date. The absolute date is the number of days from January 1st, 1 A.D.
+ // 1/1/0001 is absolute date 1.
+ internal static long PersianNewYearOnOrBefore(long numberOfDays)
+ {
+ double date = (double) numberOfDays;
+
+ double approx = EstimatePrior(LongitudeSpring, MiddayAtPersianObservationSite(date));
+ long lowerBoundNewYearDay = (long) Math.Floor(approx) - 1;
+ long upperBoundNewYearDay = lowerBoundNewYearDay + 3; // estimate is generally within a day of the actual occurrance (at the limits, the error expands, since the calculations rely on the mean tropical year which changes...)
+ long day = lowerBoundNewYearDay;
+ for (; day != upperBoundNewYearDay; ++day)
+ {
+ double midday = MiddayAtPersianObservationSite((double) day);
+ double l = Compute(midday);
+ if ((LongitudeSpring <= l) && (l <= TwoDegreesAfterSpring))
+ {
+ break;
+ }
+ }
+ Contract.Assert(day != upperBoundNewYearDay);
+
+ return day - 1;
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/CharUnicodeInfo.cs b/src/mscorlib/src/System/Globalization/CharUnicodeInfo.cs
new file mode 100644
index 0000000000..63151951f9
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CharUnicodeInfo.cs
@@ -0,0 +1,522 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class implements a set of methods for retrieving
+// character type information. Character type information is
+// independent of culture and region.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+namespace System.Globalization {
+
+ //This class has only static members and therefore doesn't need to be serialized.
+
+ using System;
+ using System.Threading;
+ using System.Runtime.InteropServices;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.Versioning;
+ using System.Reflection;
+ using System.Security;
+ using System.Diagnostics.Contracts;
+
+
+ public static class CharUnicodeInfo
+ {
+ //--------------------------------------------------------------------//
+ // Internal Information //
+ //--------------------------------------------------------------------//
+
+ //
+ // Native methods to access the Unicode category data tables in charinfo.nlp.
+ //
+ internal const char HIGH_SURROGATE_START = '\ud800';
+ internal const char HIGH_SURROGATE_END = '\udbff';
+ internal const char LOW_SURROGATE_START = '\udc00';
+ internal const char LOW_SURROGATE_END = '\udfff';
+
+ internal const int UNICODE_CATEGORY_OFFSET = 0;
+ internal const int BIDI_CATEGORY_OFFSET = 1;
+
+ static bool s_initialized = InitTable();
+
+ // The native pointer to the 12:4:4 index table of the Unicode cateogry data.
+ [SecurityCritical]
+ unsafe static ushort* s_pCategoryLevel1Index;
+ [SecurityCritical]
+ unsafe static byte* s_pCategoriesValue;
+
+ // The native pointer to the 12:4:4 index table of the Unicode numeric data.
+ // The value of this index table is an index into the real value table stored in s_pNumericValues.
+ [SecurityCritical]
+ unsafe static ushort* s_pNumericLevel1Index;
+
+ // The numeric value table, which is indexed by s_pNumericLevel1Index.
+ // Every item contains the value for numeric value.
+ // unsafe static double* s_pNumericValues;
+ // To get around the IA64 alignment issue. Our double data is aligned in 8-byte boundary, but loader loads the embeded table starting
+ // at 4-byte boundary. This cause a alignment issue since double is 8-byte.
+ [SecurityCritical]
+ unsafe static byte* s_pNumericValues;
+
+ // The digit value table, which is indexed by s_pNumericLevel1Index. It shares the same indice as s_pNumericValues.
+ // Every item contains the value for decimal digit/digit value.
+ [SecurityCritical]
+ unsafe static DigitValues* s_pDigitValues;
+
+ internal const String UNICODE_INFO_FILE_NAME = "charinfo.nlp";
+ // The starting codepoint for Unicode plane 1. Plane 1 contains 0x010000 ~ 0x01ffff.
+ internal const int UNICODE_PLANE01_START = 0x10000;
+
+
+ //
+ // This is the header for the native data table that we load from UNICODE_INFO_FILE_NAME.
+ //
+ // Excplicit layout is used here since a syntax like char[16] can not be used in sequential layout.
+ [StructLayout(LayoutKind.Explicit)]
+ internal unsafe struct UnicodeDataHeader {
+ [FieldOffset(0)]
+ internal char TableName; // WCHAR[16]
+ [FieldOffset(0x20)]
+ internal ushort version; // WORD[4]
+ [FieldOffset(0x28)]
+ internal uint OffsetToCategoriesIndex; // DWORD
+ [FieldOffset(0x2c)]
+ internal uint OffsetToCategoriesValue; // DWORD
+ [FieldOffset(0x30)]
+ internal uint OffsetToNumbericIndex; // DWORD
+ [FieldOffset(0x34)]
+ internal uint OffsetToDigitValue; // DWORD
+ [FieldOffset(0x38)]
+ internal uint OffsetToNumbericValue; // DWORD
+
+ }
+
+ // NOTE: It's important to specify pack size here, since the size of the structure is 2 bytes. Otherwise,
+ // the default pack size will be 4.
+
+ [StructLayout(LayoutKind.Sequential, Pack=2)]
+ internal struct DigitValues {
+ internal sbyte decimalDigit;
+ internal sbyte digit;
+ }
+
+
+ //We need to allocate the underlying table that provides us with the information that we
+ //use. We allocate this once in the class initializer and then we don't need to worry
+ //about it again.
+ //
+ [System.Security.SecuritySafeCritical] // auto-generated
+ unsafe static bool InitTable() {
+
+ // Go to native side and get pointer to the native table
+ byte * pDataTable = GlobalizationAssembly.GetGlobalizationResourceBytePtr(typeof(CharUnicodeInfo).Assembly, UNICODE_INFO_FILE_NAME);
+
+ UnicodeDataHeader* mainHeader = (UnicodeDataHeader*)pDataTable;
+
+ // Set up the native pointer to different part of the tables.
+ s_pCategoryLevel1Index = (ushort*) (pDataTable + mainHeader->OffsetToCategoriesIndex);
+ s_pCategoriesValue = (byte*) (pDataTable + mainHeader->OffsetToCategoriesValue);
+ s_pNumericLevel1Index = (ushort*) (pDataTable + mainHeader->OffsetToNumbericIndex);
+ s_pNumericValues = (byte*) (pDataTable + mainHeader->OffsetToNumbericValue);
+ s_pDigitValues = (DigitValues*) (pDataTable + mainHeader->OffsetToDigitValue);
+
+ return true;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Convert the BMP character or surrogate pointed by index to a UTF32 value.
+ // This is similar to Char.ConvertToUTF32, but the difference is that
+ // it does not throw exceptions when invalid surrogate characters are passed in.
+ //
+ // WARNING: since it doesn't throw an exception it CAN return a value
+ // in the surrogate range D800-DFFF, which are not legal unicode values.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static int InternalConvertToUtf32(String s, int index) {
+ Contract.Assert(s != null, "s != null");
+ Contract.Assert(index >= 0 && index < s.Length, "index < s.Length");
+ if (index < s.Length - 1) {
+ int temp1 = (int)s[index] - HIGH_SURROGATE_START;
+ if (temp1 >= 0 && temp1 <= 0x3ff) {
+ int temp2 = (int)s[index+1] - LOW_SURROGATE_START;
+ if (temp2 >= 0 && temp2 <= 0x3ff) {
+ // Convert the surrogate to UTF32 and get the result.
+ return ((temp1 * 0x400) + temp2 + UNICODE_PLANE01_START);
+ }
+ }
+ }
+ return ((int)s[index]);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Convert a character or a surrogate pair starting at index of string s
+ // to UTF32 value.
+ //
+ // Parameters:
+ // s The string
+ // index The starting index. It can point to a BMP character or
+ // a surrogate pair.
+ // len The length of the string.
+ // charLength [out] If the index points to a BMP char, charLength
+ // will be 1. If the index points to a surrogate pair,
+ // charLength will be 2.
+ //
+ // WARNING: since it doesn't throw an exception it CAN return a value
+ // in the surrogate range D800-DFFF, which are not legal unicode values.
+ //
+ // Returns:
+ // The UTF32 value
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static int InternalConvertToUtf32(String s, int index, out int charLength) {
+ Contract.Assert(s != null, "s != null");
+ Contract.Assert(s.Length > 0, "s.Length > 0");
+ Contract.Assert(index >= 0 && index < s.Length, "index >= 0 && index < s.Length");
+ charLength = 1;
+ if (index < s.Length - 1) {
+ int temp1 = (int)s[index] - HIGH_SURROGATE_START;
+ if (temp1 >= 0 && temp1 <= 0x3ff) {
+ int temp2 = (int)s[index+1] - LOW_SURROGATE_START;
+ if (temp2 >= 0 && temp2 <= 0x3ff) {
+ // Convert the surrogate to UTF32 and get the result.
+ charLength++;
+ return ((temp1 * 0x400) + temp2 + UNICODE_PLANE01_START);
+ }
+ }
+ }
+ return ((int)s[index]);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsWhiteSpace
+ //
+ // Determines if the given character is a white space character.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static bool IsWhiteSpace(String s, int index)
+ {
+ Contract.Assert(s != null, "s!=null");
+ Contract.Assert(index >= 0 && index < s.Length, "index >= 0 && index < s.Length");
+
+ UnicodeCategory uc = GetUnicodeCategory(s, index);
+ // In Unicode 3.0, U+2028 is the only character which is under the category "LineSeparator".
+ // And U+2029 is th eonly character which is under the category "ParagraphSeparator".
+ switch (uc) {
+ case (UnicodeCategory.SpaceSeparator):
+ case (UnicodeCategory.LineSeparator):
+ case (UnicodeCategory.ParagraphSeparator):
+ return (true);
+ }
+ return (false);
+ }
+
+
+ internal static bool IsWhiteSpace(char c)
+ {
+ UnicodeCategory uc = GetUnicodeCategory(c);
+ // In Unicode 3.0, U+2028 is the only character which is under the category "LineSeparator".
+ // And U+2029 is th eonly character which is under the category "ParagraphSeparator".
+ switch (uc) {
+ case (UnicodeCategory.SpaceSeparator):
+ case (UnicodeCategory.LineSeparator):
+ case (UnicodeCategory.ParagraphSeparator):
+ return (true);
+ }
+
+ return (false);
+ }
+
+ //
+ // This is called by the public char and string, index versions
+ //
+ // Note that for ch in the range D800-DFFF we just treat it as any other non-numeric character
+ //
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe static double InternalGetNumericValue(int ch) {
+ Contract.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range.");
+ // Get the level 2 item from the highest 12 bit (8 - 19) of ch.
+ ushort index = s_pNumericLevel1Index[ch >> 8];
+ // Get the level 2 WORD offset from the 4 - 7 bit of ch. This provides the base offset of the level 3 table.
+ // The offset is referred to an float item in m_pNumericFloatData.
+ // Note that & has the lower precedence than addition, so don't forget the parathesis.
+ index = s_pNumericLevel1Index[index + ((ch >> 4) & 0x000f)];
+ byte* pBytePtr = (byte*)&(s_pNumericLevel1Index[index]);
+ // Get the result from the 0 -3 bit of ch.
+#if BIT64
+ // To get around the IA64 alignment issue. Our double data is aligned in 8-byte boundary, but loader loads the embeded table starting
+ // at 4-byte boundary. This cause a alignment issue since double is 8-byte.
+ byte* pSourcePtr = &(s_pNumericValues[pBytePtr[(ch & 0x000f)] * sizeof(double)]);
+ if (((long)pSourcePtr % 8) != 0) {
+ // We are not aligned in 8-byte boundary. Do a copy.
+ double ret;
+ byte* retPtr = (byte*)&ret;
+ Buffer.Memcpy(retPtr, pSourcePtr, sizeof(double));
+ return (ret);
+ }
+ return (((double*)s_pNumericValues)[pBytePtr[(ch & 0x000f)]]);
+#else
+ return (((double*)s_pNumericValues)[pBytePtr[(ch & 0x000f)]]);
+#endif
+ }
+
+ //
+ // This is called by the public char and string, index versions
+ //
+ // Note that for ch in the range D800-DFFF we just treat it as any other non-numeric character
+ //
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe static DigitValues* InternalGetDigitValues(int ch) {
+ Contract.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range.");
+ // Get the level 2 item from the highest 12 bit (8 - 19) of ch.
+ ushort index = s_pNumericLevel1Index[ch >> 8];
+ // Get the level 2 WORD offset from the 4 - 7 bit of ch. This provides the base offset of the level 3 table.
+ // The offset is referred to an float item in m_pNumericFloatData.
+ // Note that & has the lower precedence than addition, so don't forget the parathesis.
+ index = s_pNumericLevel1Index[index + ((ch >> 4) & 0x000f)];
+ byte* pBytePtr = (byte*)&(s_pNumericLevel1Index[index]);
+ // Get the result from the 0 -3 bit of ch.
+ return &(s_pDigitValues[pBytePtr[(ch & 0x000f)]]);
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe static sbyte InternalGetDecimalDigitValue(int ch) {
+ return (InternalGetDigitValues(ch)->decimalDigit);
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe static sbyte InternalGetDigitValue(int ch) {
+ return (InternalGetDigitValues(ch)->digit);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //Returns the numeric value associated with the character c. If the character is a fraction,
+ // the return value will not be an integer. If the character does not have a numeric value, the return value is -1.
+ //
+ //Returns:
+ // the numeric value for the specified Unicode character. If the character does not have a numeric value, the return value is -1.
+ //Arguments:
+ // ch a Unicode character
+ //Exceptions:
+ // ArgumentNullException
+ // ArgumentOutOfRangeException
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public static double GetNumericValue(char ch) {
+ return (InternalGetNumericValue(ch));
+ }
+
+
+ public static double GetNumericValue(String s, int index) {
+ if (s == null) {
+ throw new ArgumentNullException("s");
+ }
+ if (index < 0 || index >= s.Length) {
+ throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+ Contract.EndContractBlock();
+ return (InternalGetNumericValue(InternalConvertToUtf32(s, index)));
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //Returns the decimal digit value associated with the character c.
+ //
+ // The value should be from 0 ~ 9.
+ // If the character does not have a numeric value, the return value is -1.
+ // From Unicode.org: Decimal Digits. Digits that can be used to form decimal-radix numbers.
+ //Returns:
+ // the decimal digit value for the specified Unicode character. If the character does not have a decimal digit value, the return value is -1.
+ //Arguments:
+ // ch a Unicode character
+ //Exceptions:
+ // ArgumentNullException
+ // ArgumentOutOfRangeException
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public static int GetDecimalDigitValue(char ch) {
+ return (InternalGetDecimalDigitValue(ch));
+ }
+
+
+ public static int GetDecimalDigitValue(String s, int index) {
+ if (s == null) {
+ throw new ArgumentNullException("s");
+ }
+ if (index < 0 || index >= s.Length) {
+ throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+ Contract.EndContractBlock();
+
+ return (InternalGetDecimalDigitValue(InternalConvertToUtf32(s, index)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //Action: Returns the digit value associated with the character c.
+ // If the character does not have a numeric value, the return value is -1.
+ // From Unicode.org: If the character represents a digit, not necessarily a decimal digit,
+ // the value is here. This covers digits which do not form decimal radix forms, such as the compatibility superscript digits.
+ //
+ // An example is: U+2460 IRCLED DIGIT ONE. This character has digit value 1, but does not have associcated decimal digit value.
+ //
+ //Returns:
+ // the digit value for the specified Unicode character. If the character does not have a digit value, the return value is -1.
+ //Arguments:
+ // ch a Unicode character
+ //Exceptions:
+ // ArgumentNullException
+ // ArgumentOutOfRangeException
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public static int GetDigitValue(char ch) {
+ return (InternalGetDigitValue(ch));
+ }
+
+
+ public static int GetDigitValue(String s, int index) {
+ if (s == null) {
+ throw new ArgumentNullException("s");
+ }
+ if (index < 0 || index >= s.Length) {
+ throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+ Contract.EndContractBlock();
+ return (InternalGetDigitValue(InternalConvertToUtf32(s, index)));
+ }
+
+ public static UnicodeCategory GetUnicodeCategory(char ch)
+ {
+ return (InternalGetUnicodeCategory(ch)) ;
+ }
+
+ public static UnicodeCategory GetUnicodeCategory(String s, int index)
+ {
+ if (s==null)
+ throw new ArgumentNullException("s");
+ if (((uint)index)>=((uint)s.Length)) {
+ throw new ArgumentOutOfRangeException("index");
+ }
+ Contract.EndContractBlock();
+ return InternalGetUnicodeCategory(s, index);
+ }
+
+ internal unsafe static UnicodeCategory InternalGetUnicodeCategory(int ch) {
+ return ((UnicodeCategory)InternalGetCategoryValue(ch, UNICODE_CATEGORY_OFFSET));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //Action: Returns the Unicode Category property for the character c.
+ //Returns:
+ // an value in UnicodeCategory enum
+ //Arguments:
+ // ch a Unicode character
+ //Exceptions:
+ // None
+ //
+ //Note that this API will return values for D800-DF00 surrogate halves.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe static byte InternalGetCategoryValue(int ch, int offset) {
+ Contract.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range.");
+ // Get the level 2 item from the highest 12 bit (8 - 19) of ch.
+ ushort index = s_pCategoryLevel1Index[ch >> 8];
+ // Get the level 2 WORD offset from the 4 - 7 bit of ch. This provides the base offset of the level 3 table.
+ // Note that & has the lower precedence than addition, so don't forget the parathesis.
+ index = s_pCategoryLevel1Index[index + ((ch >> 4) & 0x000f)];
+ byte* pBytePtr = (byte*)&(s_pCategoryLevel1Index[index]);
+ // Get the result from the 0 -3 bit of ch.
+ byte valueIndex = pBytePtr[(ch & 0x000f)];
+ byte uc = s_pCategoriesValue[valueIndex * 2 + offset];
+ //
+ // Make sure that OtherNotAssigned is the last category in UnicodeCategory.
+ // If that changes, change the following assertion as well.
+ //
+ //Contract.Assert(uc >= 0 && uc <= UnicodeCategory.OtherNotAssigned, "Table returns incorrect Unicode category");
+ return (uc);
+ }
+
+// internal static BidiCategory GetBidiCategory(char ch) {
+// return ((BidiCategory)InternalGetCategoryValue(c, BIDI_CATEGORY_OFFSET));
+// }
+
+ internal static BidiCategory GetBidiCategory(String s, int index) {
+ if (s==null)
+ throw new ArgumentNullException("s");
+ if (((uint)index)>=((uint)s.Length)) {
+ throw new ArgumentOutOfRangeException("index");
+ }
+ Contract.EndContractBlock();
+ return ((BidiCategory)InternalGetCategoryValue(InternalConvertToUtf32(s, index), BIDI_CATEGORY_OFFSET));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //Action: Returns the Unicode Category property for the character c.
+ //Returns:
+ // an value in UnicodeCategory enum
+ //Arguments:
+ // value a Unicode String
+ // index Index for the specified string.
+ //Exceptions:
+ // None
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static UnicodeCategory InternalGetUnicodeCategory(String value, int index) {
+ Contract.Assert(value != null, "value can not be null");
+ Contract.Assert(index < value.Length, "index < value.Length");
+
+ return (InternalGetUnicodeCategory(InternalConvertToUtf32(value, index)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Get the Unicode category of the character starting at index. If the character is in BMP, charLength will return 1.
+ // If the character is a valid surrogate pair, charLength will return 2.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static UnicodeCategory InternalGetUnicodeCategory(String str, int index, out int charLength) {
+ Contract.Assert(str != null, "str can not be null");
+ Contract.Assert(str.Length > 0, "str.Length > 0");;
+ Contract.Assert(index >= 0 && index < str.Length, "index >= 0 && index < str.Length");
+
+ return (InternalGetUnicodeCategory(InternalConvertToUtf32(str, index, out charLength)));
+ }
+
+ internal static bool IsCombiningCategory(UnicodeCategory uc) {
+ Contract.Assert(uc >= 0, "uc >= 0");
+ return (
+ uc == UnicodeCategory.NonSpacingMark ||
+ uc == UnicodeCategory.SpacingCombiningMark ||
+ uc == UnicodeCategory.EnclosingMark
+ );
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/ChineseLunisolarCalendar.cs b/src/mscorlib/src/System/Globalization/ChineseLunisolarCalendar.cs
new file mode 100644
index 0000000000..a5cf37f712
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/ChineseLunisolarCalendar.cs
@@ -0,0 +1,401 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+ using System.Diagnostics.Contracts;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Notes about ChineseLunisolarCalendar
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1901/02/19 2101/01/28
+ ** ChineseLunisolar 1901/01/01 2100/12/29
+ */
+
+ [Serializable]
+ public class ChineseLunisolarCalendar : EastAsianLunisolarCalendar {
+
+
+ //
+ // The era value for the current era.
+ //
+
+ public const int ChineseEra = 1;
+ //internal static Calendar m_defaultInstance;
+
+ internal const int MIN_LUNISOLAR_YEAR = 1901;
+ internal const int MAX_LUNISOLAR_YEAR = 2100;
+
+ internal const int MIN_GREGORIAN_YEAR = 1901;
+ internal const int MIN_GREGORIAN_MONTH = 2;
+ internal const int MIN_GREGORIAN_DAY = 19;
+
+ internal const int MAX_GREGORIAN_YEAR = 2101;
+ internal const int MAX_GREGORIAN_MONTH = 1;
+ internal const int MAX_GREGORIAN_DAY = 28;
+
+ internal static DateTime minDate = new DateTime(MIN_GREGORIAN_YEAR, MIN_GREGORIAN_MONTH, MIN_GREGORIAN_DAY);
+ internal static DateTime maxDate = new DateTime((new DateTime(MAX_GREGORIAN_YEAR, MAX_GREGORIAN_MONTH, MAX_GREGORIAN_DAY, 23, 59, 59, 999)).Ticks + 9999);
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MinSupportedDateTime {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MaxSupportedDateTime {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // 1900: 1-29 2-30 3-29 4-29 5-30 6-29 7-30 8-30 Leap8-29 9-30 10-30 11-29 12-30 from Calendrical Tabulations
+ return 384;
+ }
+ }
+
+
+ static readonly int [,] yinfo =
+ {
+ /*Y LM Lmon Lday DaysPerMonth D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 #Days
+1901 */{ 0 , 2 , 19 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1902 */{ 0 , 2 , 8 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1903 */{ 5 , 1 , 29 , 21096 },/* 29 30 29 30 29 29 30 29 29 30 30 29 30 383
+1904 */{ 0 , 2 , 16 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+1905 */{ 0 , 2 , 4 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1906 */{ 4 , 1 , 25 , 27304 },/* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1907 */{ 0 , 2 , 13 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1908 */{ 0 , 2 , 2 , 39632 },/* 30 29 29 30 30 29 30 29 30 30 29 30 0 355
+1909 */{ 2 , 1 , 22 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1910 */{ 0 , 2 , 10 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1911 */{ 6 , 1 , 30 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1912 */{ 0 , 2 , 18 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1913 */{ 0 , 2 , 6 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1914 */{ 5 , 1 , 26 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1915 */{ 0 , 2 , 14 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1916 */{ 0 , 2 , 3 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 0 355
+1917 */{ 2 , 1 , 23 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1918 */{ 0 , 2 , 11 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1919 */{ 7 , 2 , 1 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1920 */{ 0 , 2 , 20 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1921 */{ 0 , 2 , 8 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1922 */{ 5 , 1 , 28 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1923 */{ 0 , 2 , 16 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1924 */{ 0 , 2 , 5 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1925 */{ 4 , 1 , 24 , 44456 },/* 30 29 30 29 30 30 29 30 30 29 30 29 30 385
+1926 */{ 0 , 2 , 13 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1927 */{ 0 , 2 , 2 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 0 355
+1928 */{ 2 , 1 , 23 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1929 */{ 0 , 2 , 10 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1930 */{ 6 , 1 , 30 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1931 */{ 0 , 2 , 17 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1932 */{ 0 , 2 , 6 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1933 */{ 5 , 1 , 26 , 27976 },/* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1934 */{ 0 , 2 , 14 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1935 */{ 0 , 2 , 4 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1936 */{ 3 , 1 , 24 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1937 */{ 0 , 2 , 11 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1938 */{ 7 , 1 , 31 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1939 */{ 0 , 2 , 19 , 51536 },/* 30 30 29 29 30 29 29 30 29 30 29 30 0 354
+1940 */{ 0 , 2 , 8 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1941 */{ 6 , 1 , 27 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1942 */{ 0 , 2 , 15 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1943 */{ 0 , 2 , 5 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1944 */{ 4 , 1 , 25 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+1945 */{ 0 , 2 , 13 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1946 */{ 0 , 2 , 2 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+1947 */{ 2 , 1 , 22 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+1948 */{ 0 , 2 , 10 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1949 */{ 7 , 1 , 29 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+1950 */{ 0 , 2 , 17 , 27808 },/* 29 30 30 29 30 30 29 29 30 29 30 29 0 354
+1951 */{ 0 , 2 , 6 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1952 */{ 5 , 1 , 27 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+1953 */{ 0 , 2 , 14 , 19872 },/* 29 30 29 29 30 30 29 30 30 29 30 29 0 354
+1954 */{ 0 , 2 , 3 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+1955 */{ 3 , 1 , 24 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1956 */{ 0 , 2 , 12 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1957 */{ 8 , 1 , 31 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1958 */{ 0 , 2 , 18 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 0 355
+1959 */{ 0 , 2 , 8 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1960 */{ 6 , 1 , 28 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1961 */{ 0 , 2 , 15 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1962 */{ 0 , 2 , 5 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1963 */{ 4 , 1 , 25 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1964 */{ 0 , 2 , 13 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1965 */{ 0 , 2 , 2 , 21088 },/* 29 30 29 30 29 29 30 29 29 30 30 29 0 353
+1966 */{ 3 , 1 , 21 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1967 */{ 0 , 2 , 9 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1968 */{ 7 , 1 , 30 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1969 */{ 0 , 2 , 17 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1970 */{ 0 , 2 , 6 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+1971 */{ 5 , 1 , 27 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1972 */{ 0 , 2 , 15 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+1973 */{ 0 , 2 , 3 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1974 */{ 4 , 1 , 23 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1975 */{ 0 , 2 , 11 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1976 */{ 8 , 1 , 31 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1977 */{ 0 , 2 , 18 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1978 */{ 0 , 2 , 7 , 46752 },/* 30 29 30 30 29 30 30 29 30 29 30 29 0 355
+1979 */{ 6 , 1 , 28 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1980 */{ 0 , 2 , 16 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1981 */{ 0 , 2 , 5 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1982 */{ 4 , 1 , 25 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1983 */{ 0 , 2 , 13 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1984 */{ 10 , 2 , 2 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1985 */{ 0 , 2 , 20 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1986 */{ 0 , 2 , 9 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1987 */{ 6 , 1 , 29 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 29 384
+1988 */{ 0 , 2 , 17 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1989 */{ 0 , 2 , 6 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 0 355
+1990 */{ 5 , 1 , 27 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1991 */{ 0 , 2 , 15 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1992 */{ 0 , 2 , 4 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1993 */{ 3 , 1 , 23 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1994 */{ 0 , 2 , 10 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1995 */{ 8 , 1 , 31 , 27432 },/* 29 30 30 29 30 29 30 30 29 29 30 29 30 384
+1996 */{ 0 , 2 , 19 , 23232 },/* 29 30 29 30 30 29 30 29 30 30 29 29 0 354
+1997 */{ 0 , 2 , 7 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1998 */{ 5 , 1 , 28 , 37736 },/* 30 29 29 30 29 29 30 30 29 30 30 29 30 384
+1999 */{ 0 , 2 , 16 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+2000 */{ 0 , 2 , 5 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+2001 */{ 4 , 1 , 24 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+2002 */{ 0 , 2 , 12 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+2003 */{ 0 , 2 , 1 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+2004 */{ 2 , 1 , 22 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2005 */{ 0 , 2 , 9 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+2006 */{ 7 , 1 , 29 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+2007 */{ 0 , 2 , 18 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+2008 */{ 0 , 2 , 7 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+2009 */{ 5 , 1 , 26 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+2010 */{ 0 , 2 , 14 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2011 */{ 0 , 2 , 3 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+2012 */{ 4 , 1 , 23 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+2013 */{ 0 , 2 , 10 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2014 */{ 9 , 1 , 31 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2015 */{ 0 , 2 , 19 , 19360 },/* 29 30 29 29 30 29 30 30 30 29 30 29 0 354
+2016 */{ 0 , 2 , 8 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+2017 */{ 6 , 1 , 28 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+2018 */{ 0 , 2 , 16 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+2019 */{ 0 , 2 , 5 , 43312 },/* 30 29 30 29 30 29 29 30 29 29 30 30 0 354
+2020 */{ 4 , 1 , 25 , 29864 },/* 29 30 30 30 29 30 29 29 30 29 30 29 30 384
+2021 */{ 0 , 2 , 12 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+2022 */{ 0 , 2 , 1 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2023 */{ 2 , 1 , 22 , 19880 },/* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+2024 */{ 0 , 2 , 10 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+2025 */{ 6 , 1 , 29 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+2026 */{ 0 , 2 , 17 , 42208 },/* 30 29 30 29 29 30 29 29 30 30 30 29 0 354
+2027 */{ 0 , 2 , 6 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+2028 */{ 5 , 1 , 26 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+2029 */{ 0 , 2 , 13 , 54576 },/* 30 30 29 30 29 30 29 30 29 29 30 30 0 355
+2030 */{ 0 , 2 , 3 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+2031 */{ 3 , 1 , 23 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+2032 */{ 0 , 2 , 11 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2033 */{ 11 , 1 , 31 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+2034 */{ 0 , 2 , 19 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+2035 */{ 0 , 2 , 8 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+2036 */{ 6 , 1 , 28 , 53848 },/* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+2037 */{ 0 , 2 , 15 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+2038 */{ 0 , 2 , 4 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+2039 */{ 5 , 1 , 24 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 29 384
+2040 */{ 0 , 2 , 12 , 46496 },/* 30 29 30 30 29 30 29 30 30 29 30 29 0 355
+2041 */{ 0 , 2 , 1 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+2042 */{ 2 , 1 , 22 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+2043 */{ 0 , 2 , 10 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+2044 */{ 7 , 1 , 30 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+2045 */{ 0 , 2 , 17 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+2046 */{ 0 , 2 , 6 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+2047 */{ 5 , 1 , 26 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+2048 */{ 0 , 2 , 14 , 27936 },/* 29 30 30 29 30 30 29 30 29 29 30 29 0 354
+2049 */{ 0 , 2 , 2 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+2050 */{ 3 , 1 , 23 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+2051 */{ 0 , 2 , 11 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+2052 */{ 8 , 2 , 1 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+2053 */{ 0 , 2 , 19 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+2054 */{ 0 , 2 , 8 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+2055 */{ 6 , 1 , 28 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+2056 */{ 0 , 2 , 15 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+2057 */{ 0 , 2 , 4 , 27424 },/* 29 30 30 29 30 29 30 30 29 29 30 29 0 354
+2058 */{ 4 , 1 , 24 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 29 384
+2059 */{ 0 , 2 , 12 , 43744 },/* 30 29 30 29 30 29 30 29 30 30 30 29 0 355
+2060 */{ 0 , 2 , 2 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+2061 */{ 3 , 1 , 21 , 51568 },/* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+2062 */{ 0 , 2 , 9 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+2063 */{ 7 , 1 , 29 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+2064 */{ 0 , 2 , 17 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+2065 */{ 0 , 2 , 5 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+2066 */{ 5 , 1 , 26 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2067 */{ 0 , 2 , 14 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+2068 */{ 0 , 2 , 3 , 42704 },/* 30 29 30 29 29 30 30 29 30 30 29 30 0 355
+2069 */{ 4 , 1 , 23 , 21224 },/* 29 30 29 30 29 29 30 29 30 30 30 29 30 384
+2070 */{ 0 , 2 , 11 , 21200 },/* 29 30 29 30 29 29 30 29 30 30 29 30 0 354
+2071 */{ 8 , 1 , 31 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+2072 */{ 0 , 2 , 19 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2073 */{ 0 , 2 , 7 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+2074 */{ 6 , 1 , 27 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+2075 */{ 0 , 2 , 15 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2076 */{ 0 , 2 , 5 , 21920 },/* 29 30 29 30 29 30 29 30 30 29 30 29 0 354
+2077 */{ 4 , 1 , 24 , 42448 },/* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+2078 */{ 0 , 2 , 12 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+2079 */{ 0 , 2 , 2 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+2080 */{ 3 , 1 , 22 , 43320 },/* 30 29 30 29 30 29 29 30 29 29 30 30 30 384
+2081 */{ 0 , 2 , 9 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 0 354
+2082 */{ 7 , 1 , 29 , 29336 },/* 29 30 30 30 29 29 30 29 30 29 29 30 30 384
+2083 */{ 0 , 2 , 17 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+2084 */{ 0 , 2 , 6 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2085 */{ 5 , 1 , 26 , 19880 },/* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+2086 */{ 0 , 2 , 14 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+2087 */{ 0 , 2 , 3 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+2088 */{ 4 , 1 , 24 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 29 383
+2089 */{ 0 , 2 , 10 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+2090 */{ 8 , 1 , 30 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+2091 */{ 0 , 2 , 18 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+2092 */{ 0 , 2 , 7 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 0 355
+2093 */{ 6 , 1 , 27 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+2094 */{ 0 , 2 , 15 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+2095 */{ 0 , 2 , 5 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+2096 */{ 4 , 1 , 25 , 42216 },/* 30 29 30 29 29 30 29 29 30 30 30 29 30 384
+2097 */{ 0 , 2 , 12 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+2098 */{ 0 , 2 , 1 , 53584 },/* 30 30 29 30 29 29 29 30 29 30 29 30 0 354
+2099 */{ 2 , 1 , 21 , 55592 },/* 30 30 29 30 30 29 29 30 29 29 30 29 30 384
+2100 */{ 0 , 2 , 9 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+ */};
+
+
+ internal override int MinCalendarYear {
+ get
+ {
+ return (MIN_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override int MaxCalendarYear {
+ get
+ {
+ return (MAX_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override DateTime MinDate {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ internal override DateTime MaxDate {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ internal override EraInfo[] CalEraInfo {
+ get
+ {
+ return (null);
+ }
+ }
+
+ internal override int GetYearInfo(int LunarYear, int Index) {
+ if ((LunarYear < MIN_LUNISOLAR_YEAR) || (LunarYear > MAX_LUNISOLAR_YEAR)) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR ));
+ }
+ Contract.EndContractBlock();
+
+ return yinfo[LunarYear - MIN_LUNISOLAR_YEAR, Index];
+ }
+
+ internal override int GetYear(int year, DateTime time) {
+ return year;
+ }
+
+ internal override int GetGregorianYear(int year, int era) {
+ if (era != CurrentEra && era != ChineseEra) {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ if (year < MIN_LUNISOLAR_YEAR || year > MAX_LUNISOLAR_YEAR) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR));
+ }
+ Contract.EndContractBlock();
+
+ return year;
+ }
+
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of ChineseLunisolarCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ /*
+ internal static Calendar GetDefaultInstance()
+ {
+ if (m_defaultInstance == null) {
+ m_defaultInstance = new ChineseLunisolarCalendar();
+ }
+ return (m_defaultInstance);
+ }
+ */
+
+ // Construct an instance of ChineseLunisolar calendar.
+
+ public ChineseLunisolarCalendar() {
+ }
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetEra(DateTime time) {
+ CheckTicksRange(time.Ticks);
+ return (ChineseEra);
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_CHINESELUNISOLAR);
+ }
+ }
+
+ internal override int BaseCalendarID {
+ get {
+ //Use CAL_GREGORIAN just to get CurrentEraValue as 1 since we do not have data under the ID CAL_ChineseLunisolar yet
+ return (CAL_GREGORIAN);
+ }
+ }
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int[] Eras {
+ get {
+ return (new int[] {ChineseEra});
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/CompareInfo.cs b/src/mscorlib/src/System/Globalization/CompareInfo.cs
new file mode 100644
index 0000000000..0b14f05264
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CompareInfo.cs
@@ -0,0 +1,1359 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+//
+// Purpose: This class implements a set of methods for comparing
+// strings.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+namespace System.Globalization {
+
+ //
+ // We pass all of the sorting calls to the native side, preferrably to the OS to do
+ // the actual work.
+ //
+
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Reflection;
+ using System.Runtime.Serialization;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.ConstrainedExecution;
+ using System.Runtime.InteropServices;
+ using System.Runtime.Versioning;
+ using System.Threading;
+ using System.Security.Permissions;
+ using Microsoft.Win32;
+ using System.Security;
+ using System.Diagnostics.Contracts;
+
+ //
+ // Options can be used during string comparison.
+ //
+ // Native implementation (COMNlsInfo.cpp & SortingTable.cpp) relies on the values of these,
+ // If you change the values below, be sure to change the values in native part as well.
+ //
+
+
+[Serializable]
+ [Flags]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public enum CompareOptions
+ {
+ None = 0x00000000,
+ IgnoreCase = 0x00000001,
+ IgnoreNonSpace = 0x00000002,
+ IgnoreSymbols = 0x00000004,
+ IgnoreKanaType = 0x00000008, // ignore kanatype
+ IgnoreWidth = 0x00000010, // ignore width
+ OrdinalIgnoreCase = 0x10000000, // This flag can not be used with other flags.
+ StringSort = 0x20000000, // use string sort method
+ Ordinal = 0x40000000, // This flag can not be used with other flags.
+
+ // StopOnNull = 0x10000000,
+
+ // StopOnNull is defined in SortingTable.h, but we didn't enable this option here.
+ // Do not use this value for other flags accidentally.
+ }
+
+
+ [Serializable]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public partial class CompareInfo : IDeserializationCallback
+ {
+ // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags.
+ private const CompareOptions ValidIndexMaskOffFlags =
+ ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
+
+ // Mask used to check if Compare() has the right flags.
+ private const CompareOptions ValidCompareMaskOffFlags =
+ ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
+
+ // Mask used to check if GetHashCodeOfString() has the right flags.
+ private const CompareOptions ValidHashCodeOfStringMaskOffFlags =
+ ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
+
+ //
+ // CompareInfos have an interesting identity. They are attached to the locale that created them,
+ // ie: en-US would have an en-US sort. For haw-US (custom), then we serialize it as haw-US.
+ // The interesting part is that since haw-US doesn't have its own sort, it has to point at another
+ // locale, which is what SCOMPAREINFO does.
+
+ [OptionalField(VersionAdded = 2)]
+ private String m_name; // The name used to construct this CompareInfo
+
+ [NonSerialized]
+ private String m_sortName; // The name that defines our behavior
+
+ [NonSerialized]
+ private IntPtr m_dataHandle;
+
+ [NonSerialized]
+ private IntPtr m_handleOrigin;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CompareInfo Constructor
+ //
+ //
+ ////////////////////////////////////////////////////////////////////////
+ // Constructs an instance that most closely corresponds to the NLS locale
+ // identifier.
+ internal CompareInfo(CultureInfo culture)
+ {
+ this.m_name = culture.m_name;
+ this.m_sortName = culture.SortName;
+
+ IntPtr handleOrigin;
+ this.m_dataHandle = InternalInitSortHandle(m_sortName, out handleOrigin);
+ this.m_handleOrigin = handleOrigin;
+ }
+
+ /*=================================GetCompareInfo==========================
+ **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
+ ** Warning: The assembly versioning mechanism is dead!
+ **Returns: The CompareInfo for the specified culture.
+ **Arguments:
+ ** culture the ID of the culture
+ ** assembly the assembly which contains the sorting table.
+ **Exceptions:
+ ** ArugmentNullException when the assembly is null
+ ** ArgumentException if culture is invalid.
+ ============================================================================*/
+#if FEATURE_USE_LCID
+ // Assembly constructor should be deprecated, we don't act on the assembly information any more
+ public static CompareInfo GetCompareInfo(int culture, Assembly assembly){
+ // Parameter checking.
+ if (assembly == null) {
+ throw new ArgumentNullException("assembly");
+ }
+ if (assembly!=typeof(Object).Module.Assembly) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_OnlyMscorlib"));
+ }
+ Contract.EndContractBlock();
+
+ return GetCompareInfo(culture);
+ }
+#endif
+
+
+ /*=================================GetCompareInfo==========================
+ **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
+ ** The purpose of this method is to provide version for CompareInfo tables.
+ **Returns: The CompareInfo for the specified culture.
+ **Arguments:
+ ** name the name of the culture
+ ** assembly the assembly which contains the sorting table.
+ **Exceptions:
+ ** ArugmentNullException when the assembly is null
+ ** ArgumentException if name is invalid.
+ ============================================================================*/
+ // Assembly constructor should be deprecated, we don't act on the assembly information any more
+ public static CompareInfo GetCompareInfo(String name, Assembly assembly){
+ if (name == null || assembly == null) {
+ throw new ArgumentNullException(name == null ? "name" : "assembly");
+ }
+ Contract.EndContractBlock();
+
+ if (assembly!=typeof(Object).Module.Assembly) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_OnlyMscorlib"));
+ }
+
+ return GetCompareInfo(name);
+ }
+
+ /*=================================GetCompareInfo==========================
+ **Action: Get the CompareInfo for the specified culture.
+ ** This method is provided for ease of integration with NLS-based software.
+ **Returns: The CompareInfo for the specified culture.
+ **Arguments:
+ ** culture the ID of the culture.
+ **Exceptions:
+ ** ArgumentException if culture is invalid.
+ ============================================================================*/
+
+#if FEATURE_USE_LCID
+ // People really shouldn't be calling LCID versions, no custom support
+ public static CompareInfo GetCompareInfo(int culture)
+ {
+ if (CultureData.IsCustomCultureId(culture))
+ {
+ // Customized culture cannot be created by the LCID.
+ throw new ArgumentException(Environment.GetResourceString("Argument_CustomCultureCannotBePassedByNumber", "culture"));
+ }
+
+ return CultureInfo.GetCultureInfo(culture).CompareInfo;
+ }
+#endif
+
+ /*=================================GetCompareInfo==========================
+ **Action: Get the CompareInfo for the specified culture.
+ **Returns: The CompareInfo for the specified culture.
+ **Arguments:
+ ** name the name of the culture.
+ **Exceptions:
+ ** ArgumentException if name is invalid.
+ ============================================================================*/
+
+ public static CompareInfo GetCompareInfo(String name)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException("name");
+ }
+ Contract.EndContractBlock();
+
+ return CultureInfo.GetCultureInfo(name).CompareInfo;
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public static bool IsSortable(char ch) {
+ return(IsSortable(ch.ToString()));
+ }
+
+ [System.Security.SecuritySafeCritical]
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public static bool IsSortable(String text) {
+ if (text == null) {
+ // A null param is invalid here.
+ throw new ArgumentNullException("text");
+ }
+
+ if (0 == text.Length) {
+ // A zero length string is not invalid, but it is also not sortable.
+ return(false);
+ }
+
+ CompareInfo c = CultureInfo.InvariantCulture.CompareInfo;
+
+ return (InternalIsSortable(c.m_dataHandle, c.m_handleOrigin, c.m_sortName, text, text.Length));
+ }
+
+
+#region Serialization
+ // the following fields are defined to keep the compatibility with Whidbey.
+ // don't change/remove the names/types of these fields.
+#if FEATURE_USE_LCID
+ [OptionalField(VersionAdded = 1)]
+ private int win32LCID; // mapped sort culture id of this instance
+ private int culture; // the culture ID used to create this instance.
+#endif
+ [OnDeserializing]
+ private void OnDeserializing(StreamingContext ctx)
+ {
+ this.m_name = null;
+ }
+
+ private void OnDeserialized()
+ {
+ CultureInfo ci;
+ // If we didn't have a name, use the LCID
+ if (this.m_name == null)
+ {
+#if FEATURE_USE_LCID
+ // From whidbey, didn't have a name
+ ci = CultureInfo.GetCultureInfo(this.culture);
+ this.m_name = ci.m_name;
+ this.m_sortName = ci.SortName;
+#endif
+ }
+ else
+ {
+ ci = CultureInfo.GetCultureInfo(m_name);
+ this.m_sortName = ci.SortName;
+ }
+
+ IntPtr handleOrigin;
+ this.m_dataHandle = InternalInitSortHandle(m_sortName, out handleOrigin);
+ this.m_handleOrigin = handleOrigin;
+
+ }
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ OnDeserialized();
+ }
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+#if FEATURE_USE_LCID
+ // This is merely for serialization compatibility with Whidbey/Orcas, it can go away when we don't want that compat any more.
+ culture = CultureInfo.GetCultureInfo(this.Name).LCID; // This is the lcid of the constructing culture (still have to dereference to get target sort)
+ Contract.Assert(m_name != null, "CompareInfo.OnSerializing - expected m_name to be set already");
+#endif
+ }
+
+ void IDeserializationCallback.OnDeserialization(Object sender)
+ {
+ OnDeserialized();
+ }
+
+#endregion Serialization
+
+
+ ///////////////////////////----- Name -----/////////////////////////////////
+ //
+ // Returns the name of the culture (well actually, of the sort).
+ // Very important for providing a non-LCID way of identifying
+ // what the sort is.
+ //
+ // Note that this name isn't dereferenced in case the CompareInfo is a different locale
+ // which is consistent with the behaviors of earlier versions. (so if you ask for a sort
+ // and the locale's changed behavior, then you'll get changed behavior, which is like
+ // what happens for a version update)
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual String Name
+ {
+ get
+ {
+ Contract.Assert(m_name != null, "CompareInfo.Name Expected m_name to be set");
+ if (m_name == "zh-CHT" || m_name == "zh-CHS")
+ {
+ return m_name;
+ }
+
+ return (m_sortName);
+ }
+ }
+
+ // These flags are used in the native Win32. so we need to map the managed options to those flags
+ private const int LINGUISTIC_IGNORECASE = 0x00000010; // linguistically appropriate 'ignore case'
+ private const int NORM_IGNORECASE = 0x00000001; // Ignores case. (use LINGUISTIC_IGNORECASE instead)
+ private const int NORM_IGNOREKANATYPE = 0x00010000; // Does not differentiate between Hiragana and Katakana characters. Corresponding Hiragana and Katakana will compare as equal.
+ private const int LINGUISTIC_IGNOREDIACRITIC = 0x00000020; // linguistically appropriate 'ignore nonspace'
+ private const int NORM_IGNORENONSPACE = 0x00000002; // Ignores nonspacing. This flag also removes Japanese accent characters. (use LINGUISTIC_IGNOREDIACRITIC instead)
+ private const int NORM_IGNORESYMBOLS = 0x00000004; // Ignores symbols.
+ private const int NORM_IGNOREWIDTH = 0x00020000; // Does not differentiate between a single-byte character and the same character as a double-byte character.
+ private const int SORT_STRINGSORT = 0x00001000; // Treats punctuation the same as symbols.
+ private const int COMPARE_OPTIONS_ORDINAL = 0x40000000; // Ordinal (handled by Comnlsinfo)
+ internal const int NORM_LINGUISTIC_CASING = 0x08000000; // use linguistic rules for casing
+
+
+ private const int RESERVED_FIND_ASCII_STRING = 0x20000000; // This flag used only to tell the sorting DLL can assume the string characters are in ASCII.
+
+ [Pure]
+ internal static int GetNativeCompareFlags(CompareOptions options)
+ {
+ // some NLS VM functions can handle COMPARE_OPTIONS_ORDINAL
+ // in which case options should be simply cast to int instead of using this function
+ // Does not look like the best approach to me but for now I am going to leave it as it is
+ Contract.Assert(options != CompareOptions.OrdinalIgnoreCase, "[CompareInfo.GetNativeCompareFlags]CompareOptions.OrdinalIgnoreCase should be handled separately");
+
+ // Use "linguistic casing" by default (load the culture's casing exception tables)
+ int nativeCompareFlags = NORM_LINGUISTIC_CASING;
+
+ if ((options & CompareOptions.IgnoreCase) != 0) { nativeCompareFlags |= NORM_IGNORECASE; }
+ if ((options & CompareOptions.IgnoreKanaType) != 0) { nativeCompareFlags |= NORM_IGNOREKANATYPE; }
+ if ((options & CompareOptions.IgnoreNonSpace) != 0) { nativeCompareFlags |= NORM_IGNORENONSPACE; }
+ if ((options & CompareOptions.IgnoreSymbols) != 0) { nativeCompareFlags |= NORM_IGNORESYMBOLS; }
+ if ((options & CompareOptions.IgnoreWidth) != 0) { nativeCompareFlags |= NORM_IGNOREWIDTH; }
+ if ((options & CompareOptions.StringSort) != 0) { nativeCompareFlags |= SORT_STRINGSORT; }
+
+ // Suffix & Prefix shouldn't use this, make sure to turn off the NORM_LINGUISTIC_CASING flag
+ if (options == CompareOptions.Ordinal) { nativeCompareFlags = COMPARE_OPTIONS_ORDINAL; }
+
+ Contract.Assert(((options & ~(CompareOptions.IgnoreCase |
+ CompareOptions.IgnoreKanaType |
+ CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreSymbols |
+ CompareOptions.IgnoreWidth |
+ CompareOptions.StringSort)) == 0) ||
+ (options == CompareOptions.Ordinal), "[CompareInfo.GetNativeCompareFlags]Expected all flags to be handled");
+
+ Contract.Assert((nativeCompareFlags & RESERVED_FIND_ASCII_STRING) == 0, "[CompareInfo.GetNativeCompareFlags] RESERVED_FIND_ASCII_STRING shouldn't be set here");
+
+ return nativeCompareFlags;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Compare
+ //
+ // Compares the two strings with the given options. Returns 0 if the
+ // two strings are equal, a number less than 0 if string1 is less
+ // than string2, and a number greater than 0 if string1 is greater
+ // than string2.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public virtual int Compare(String string1, String string2)
+ {
+ return (Compare(string1, string2, CompareOptions.None));
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual int Compare(String string1, String string2, CompareOptions options){
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return String.Compare(string1, string2, StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Verify the options before we do any real comparison.
+ if ((options & CompareOptions.Ordinal) != 0)
+ {
+ if (options != CompareOptions.Ordinal)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_CompareOptionOrdinal"), "options");
+ }
+ return String.CompareOrdinal(string1, string2);
+ }
+
+ if ((options & ValidCompareMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+ }
+
+ //Our paradigm is that null sorts less than any other string and
+ //that two nulls sort as equal.
+ if (string1 == null) {
+ if (string2 == null) {
+ return (0); // Equal
+ }
+ return (-1); // null < non-null
+ }
+ if (string2 == null) {
+ return (1); // non-null > null
+ }
+
+ return InternalCompareString(m_dataHandle, m_handleOrigin, m_sortName, string1, 0, string1.Length, string2, 0, string2.Length, GetNativeCompareFlags(options));
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Compare
+ //
+ // Compares the specified regions of the two strings with the given
+ // options.
+ // Returns 0 if the two strings are equal, a number less than 0 if
+ // string1 is less than string2, and a number greater than 0 if
+ // string1 is greater than string2.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public unsafe virtual int Compare(String string1, int offset1, int length1, String string2, int offset2, int length2)
+ {
+ return Compare(string1, offset1, length1, string2, offset2, length2, 0);
+ }
+
+
+ public unsafe virtual int Compare(String string1, int offset1, String string2, int offset2, CompareOptions options)
+ {
+ return Compare(string1, offset1, string1 == null ? 0 : string1.Length-offset1,
+ string2, offset2, string2 == null ? 0 : string2.Length-offset2, options);
+ }
+
+
+ public unsafe virtual int Compare(String string1, int offset1, String string2, int offset2)
+ {
+ return Compare(string1, offset1, string2, offset2, 0);
+ }
+
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual int Compare(String string1, int offset1, int length1, String string2, int offset2, int length2, CompareOptions options)
+ {
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ int result = String.Compare(string1, offset1, string2, offset2, length1<length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase);
+ if ((length1 != length2) && result == 0)
+ return (length1 > length2? 1: -1);
+ return (result);
+ }
+
+ // Verify inputs
+ if (length1 < 0 || length2 < 0)
+ {
+ throw new ArgumentOutOfRangeException((length1 < 0) ? "length1" : "length2", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+ if (offset1 < 0 || offset2 < 0)
+ {
+ throw new ArgumentOutOfRangeException((offset1 < 0) ? "offset1" : "offset2", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+ if (offset1 > (string1 == null ? 0 : string1.Length) - length1)
+ {
+ throw new ArgumentOutOfRangeException("string1", Environment.GetResourceString("ArgumentOutOfRange_OffsetLength"));
+ }
+ if (offset2 > (string2 == null ? 0 : string2.Length) - length2)
+ {
+ throw new ArgumentOutOfRangeException("string2", Environment.GetResourceString("ArgumentOutOfRange_OffsetLength"));
+ }
+ if ((options & CompareOptions.Ordinal) != 0)
+ {
+ if (options != CompareOptions.Ordinal)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_CompareOptionOrdinal"),
+ "options");
+ }
+ }
+ else if ((options & ValidCompareMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+ }
+
+ //
+ // Check for the null case.
+ //
+ if (string1 == null)
+ {
+ if (string2 == null)
+ {
+ return (0);
+ }
+ return (-1);
+ }
+ if (string2 == null)
+ {
+ return (1);
+ }
+
+ if (options == CompareOptions.Ordinal)
+ {
+ return string.CompareOrdinalHelper(string1, offset1, length1, string2, offset2, length2);
+ }
+ return InternalCompareString(this.m_dataHandle, this.m_handleOrigin, this.m_sortName,
+ string1, offset1, length1,
+ string2, offset2, length2,
+ GetNativeCompareFlags(options));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsPrefix
+ //
+ // Determines whether prefix is a prefix of string. If prefix equals
+ // String.Empty, true is returned.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual bool IsPrefix(String source, String prefix, CompareOptions options)
+ {
+ if (source == null || prefix == null) {
+ throw new ArgumentNullException((source == null ? "source" : "prefix"),
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ int prefixLen = prefix.Length;
+
+ if (prefixLen == 0)
+ {
+ return (true);
+ }
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return source.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ if (options == CompareOptions.Ordinal)
+ {
+ return source.StartsWith(prefix, StringComparison.Ordinal);
+ }
+
+ if ((options & ValidIndexMaskOffFlags) != 0) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+ }
+
+
+ // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
+ // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
+
+ return (InternalFindNLSStringEx(
+ m_dataHandle, m_handleOrigin, m_sortName,
+ GetNativeCompareFlags(options) | Win32Native.FIND_STARTSWITH | ((source.IsAscii() && prefix.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0),
+ source, source.Length, 0, prefix, prefix.Length) > -1);
+ }
+
+ public virtual bool IsPrefix(String source, String prefix)
+ {
+ return (IsPrefix(source, prefix, 0));
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsSuffix
+ //
+ // Determines whether suffix is a suffix of string. If suffix equals
+ // String.Empty, true is returned.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual bool IsSuffix(String source, String suffix, CompareOptions options)
+ {
+ if (source == null || suffix == null) {
+ throw new ArgumentNullException((source == null ? "source" : "suffix"),
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ int suffixLen = suffix.Length;
+
+ if (suffixLen == 0)
+ {
+ return (true);
+ }
+
+ if (options == CompareOptions.OrdinalIgnoreCase) {
+ return source.EndsWith(suffix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ if (options == CompareOptions.Ordinal) {
+ return source.EndsWith(suffix, StringComparison.Ordinal);
+ }
+
+ if ((options & ValidIndexMaskOffFlags) != 0) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+ }
+
+ // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
+ // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
+ return InternalFindNLSStringEx(
+ m_dataHandle, m_handleOrigin, m_sortName,
+ GetNativeCompareFlags(options) | Win32Native.FIND_ENDSWITH | ((source.IsAscii() && suffix.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0),
+ source, source.Length, source.Length - 1, suffix, suffix.Length) >= 0;
+ }
+
+
+ public virtual bool IsSuffix(String source, String suffix)
+ {
+ return (IsSuffix(source, suffix, 0));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IndexOf
+ //
+ // Returns the first index where value is found in string. The
+ // search starts from startIndex and ends at endIndex. Returns -1 if
+ // the specified value is not found. If value equals String.Empty,
+ // startIndex is returned. Throws IndexOutOfRange if startIndex or
+ // endIndex is less than zero or greater than the length of string.
+ // Throws ArgumentException if value is null.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public unsafe virtual int IndexOf(String source, char value)
+ {
+ if (source==null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ return IndexOf(source, value, 0, source.Length, CompareOptions.None);
+ }
+
+
+ public unsafe virtual int IndexOf(String source, String value)
+ {
+ if (source==null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ return IndexOf(source, value, 0, source.Length, CompareOptions.None);
+ }
+
+
+ public unsafe virtual int IndexOf(String source, char value, CompareOptions options)
+ {
+ if (source==null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ return IndexOf(source, value, 0, source.Length, options);
+ }
+
+
+ public unsafe virtual int IndexOf(String source, String value, CompareOptions options)
+ {
+ if (source==null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ return IndexOf(source, value, 0, source.Length, options);
+ }
+
+
+ public unsafe virtual int IndexOf(String source, char value, int startIndex)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
+ }
+
+
+ public unsafe virtual int IndexOf(String source, String value, int startIndex)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
+ }
+
+
+ public unsafe virtual int IndexOf(String source, char value, int startIndex, CompareOptions options)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ return IndexOf(source, value, startIndex, source.Length - startIndex, options);
+ }
+
+
+ public unsafe virtual int IndexOf(String source, String value, int startIndex, CompareOptions options)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ return IndexOf(source, value, startIndex, source.Length - startIndex, options);
+ }
+
+
+ public unsafe virtual int IndexOf(String source, char value, int startIndex, int count)
+ {
+ return IndexOf(source, value, startIndex, count, CompareOptions.None);
+ }
+
+
+ public unsafe virtual int IndexOf(String source, String value, int startIndex, int count)
+ {
+ return IndexOf(source, value, startIndex, count, CompareOptions.None);
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual int IndexOf(String source, char value, int startIndex, int count, CompareOptions options)
+ {
+ // Validate inputs
+ if (source == null)
+ throw new ArgumentNullException("source");
+
+ if (startIndex < 0 || startIndex > source.Length)
+ throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+
+ if (count < 0 || startIndex > source.Length - count)
+ throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count"));
+ Contract.EndContractBlock();
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return source.IndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Validate CompareOptions
+ // Ordinal can't be selected with other flags
+ if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+
+ // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
+ // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
+ return InternalFindNLSStringEx(
+ m_dataHandle, m_handleOrigin, m_sortName,
+ GetNativeCompareFlags(options) | Win32Native.FIND_FROMSTART | ((source.IsAscii() && (value <= '\x007f')) ? RESERVED_FIND_ASCII_STRING : 0),
+ source, count, startIndex, new String(value, 1), 1);
+ }
+
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual int IndexOf(String source, String value, int startIndex, int count, CompareOptions options)
+ {
+ // Validate inputs
+ if (source == null)
+ throw new ArgumentNullException("source");
+ if (value == null)
+ throw new ArgumentNullException("value");
+
+ if (startIndex > source.Length)
+ {
+ throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+ Contract.EndContractBlock();
+
+ // In Everett we used to return -1 for empty string even if startIndex is negative number so we keeping same behavior here.
+ // We return 0 if both source and value are empty strings for Everett compatibility too.
+ if (source.Length == 0)
+ {
+ if (value.Length == 0)
+ {
+ return 0;
+ }
+ return -1;
+ }
+
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+
+ if (count < 0 || startIndex > source.Length - count)
+ throw new ArgumentOutOfRangeException("count",Environment.GetResourceString("ArgumentOutOfRange_Count"));
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return source.IndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Validate CompareOptions
+ // Ordinal can't be selected with other flags
+ if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+
+ // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
+ // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
+ return InternalFindNLSStringEx(
+ m_dataHandle, m_handleOrigin, m_sortName,
+ GetNativeCompareFlags(options) | Win32Native.FIND_FROMSTART | ((source.IsAscii() && value.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0),
+ source, count, startIndex, value, value.Length);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // LastIndexOf
+ //
+ // Returns the last index where value is found in string. The
+ // search starts from startIndex and ends at endIndex. Returns -1 if
+ // the specified value is not found. If value equals String.Empty,
+ // endIndex is returned. Throws IndexOutOfRange if startIndex or
+ // endIndex is less than zero or greater than the length of string.
+ // Throws ArgumentException if value is null.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public unsafe virtual int LastIndexOf(String source, char value)
+ {
+ if (source==null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ // Can't start at negative index, so make sure we check for the length == 0 case.
+ return LastIndexOf(source, value, source.Length - 1,
+ source.Length, CompareOptions.None);
+ }
+
+
+ public virtual int LastIndexOf(String source, String value)
+ {
+ if (source==null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ // Can't start at negative index, so make sure we check for the length == 0 case.
+ return LastIndexOf(source, value, source.Length - 1,
+ source.Length, CompareOptions.None);
+ }
+
+
+ public virtual int LastIndexOf(String source, char value, CompareOptions options)
+ {
+ if (source==null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ // Can't start at negative index, so make sure we check for the length == 0 case.
+ return LastIndexOf(source, value, source.Length - 1,
+ source.Length, options);
+ }
+
+ public unsafe virtual int LastIndexOf(String source, String value, CompareOptions options)
+ {
+ if (source==null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ // Can't start at negative index, so make sure we check for the length == 0 case.
+ return LastIndexOf(source, value, source.Length - 1,
+ source.Length, options);
+ }
+
+
+ public unsafe virtual int LastIndexOf(String source, char value, int startIndex)
+ {
+ return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
+ }
+
+
+ public unsafe virtual int LastIndexOf(String source, String value, int startIndex)
+ {
+ return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
+ }
+
+
+ public unsafe virtual int LastIndexOf(String source, char value, int startIndex, CompareOptions options)
+ {
+ return LastIndexOf(source, value, startIndex, startIndex + 1, options);
+ }
+
+
+ public unsafe virtual int LastIndexOf(String source, String value, int startIndex, CompareOptions options)
+ {
+ return LastIndexOf(source, value, startIndex, startIndex + 1, options);
+ }
+
+
+ public unsafe virtual int LastIndexOf(String source, char value, int startIndex, int count)
+ {
+ return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
+ }
+
+
+ public unsafe virtual int LastIndexOf(String source, String value, int startIndex, int count)
+ {
+ return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
+ }
+
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual int LastIndexOf(String source, char value, int startIndex, int count, CompareOptions options)
+ {
+ // Verify Arguments
+ if (source==null)
+ throw new ArgumentNullException("source");
+ Contract.EndContractBlock();
+
+ // Validate CompareOptions
+ // Ordinal can't be selected with other flags
+ if ((options & ValidIndexMaskOffFlags) != 0 &&
+ (options != CompareOptions.Ordinal) &&
+ (options != CompareOptions.OrdinalIgnoreCase))
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+
+ // Special case for 0 length input strings
+ if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
+ return -1;
+
+ // Make sure we're not out of range
+ if (startIndex < 0 || startIndex > source.Length)
+ throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+
+ // Make sure that we allow startIndex == source.Length
+ if (startIndex == source.Length)
+ {
+ startIndex--;
+ if (count > 0)
+ count--;
+ }
+
+ // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
+ if (count < 0 || startIndex - count + 1 < 0)
+ throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count"));
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return source.LastIndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
+ }
+
+ // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
+ // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
+ return InternalFindNLSStringEx(
+ m_dataHandle, m_handleOrigin, m_sortName,
+ GetNativeCompareFlags(options) | Win32Native.FIND_FROMEND | ((source.IsAscii() && (value <= '\x007f')) ? RESERVED_FIND_ASCII_STRING : 0),
+ source, count, startIndex, new String(value, 1), 1);
+ }
+
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual int LastIndexOf(String source, String value, int startIndex, int count, CompareOptions options)
+ {
+ // Verify Arguments
+ if (source == null)
+ throw new ArgumentNullException("source");
+ if (value == null)
+ throw new ArgumentNullException("value");
+ Contract.EndContractBlock();
+
+ // Validate CompareOptions
+ // Ordinal can't be selected with other flags
+ if ((options & ValidIndexMaskOffFlags) != 0 &&
+ (options != CompareOptions.Ordinal) &&
+ (options != CompareOptions.OrdinalIgnoreCase))
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+
+ // Special case for 0 length input strings
+ if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
+ return (value.Length == 0) ? 0 : -1;
+
+ // Make sure we're not out of range
+ if (startIndex < 0 || startIndex > source.Length)
+ throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+
+ // Make sure that we allow startIndex == source.Length
+ if (startIndex == source.Length)
+ {
+ startIndex--;
+ if (count > 0)
+ count--;
+
+ // If we are looking for nothing, just return 0
+ if (value.Length == 0 && count >= 0 && startIndex - count + 1 >= 0)
+ return startIndex;
+ }
+
+ // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
+ if (count < 0 || startIndex - count + 1 < 0)
+ throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count"));
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return source.LastIndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase);
+ }
+
+ // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
+ // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
+ return InternalFindNLSStringEx(
+ m_dataHandle, m_handleOrigin, m_sortName,
+ GetNativeCompareFlags(options) | Win32Native.FIND_FROMEND | ((source.IsAscii() && value.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0),
+ source, count, startIndex, value, value.Length);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetSortKey
+ //
+ // Gets the SortKey for the given string with the given options.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public unsafe virtual SortKey GetSortKey(String source, CompareOptions options)
+ {
+ return CreateSortKey(source, options);
+ }
+
+
+ public unsafe virtual SortKey GetSortKey(String source)
+ {
+ return CreateSortKey(source, CompareOptions.None);
+ }
+
+ [System.Security.SecuritySafeCritical]
+ private SortKey CreateSortKey(String source, CompareOptions options)
+ {
+ if (source==null) { throw new ArgumentNullException("source"); }
+ Contract.EndContractBlock();
+
+ // Mask used to check if we have the right flags.
+ const CompareOptions ValidSortkeyCtorMaskOffFlags = ~(CompareOptions.IgnoreCase |
+ CompareOptions.IgnoreSymbols |
+ CompareOptions.IgnoreNonSpace |
+ CompareOptions.IgnoreWidth |
+ CompareOptions.IgnoreKanaType |
+ CompareOptions.StringSort);
+
+ if ((options & ValidSortkeyCtorMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+ }
+ byte[] keyData = null;
+ // The OS doesn't have quite the same behavior so we have to test for empty inputs
+ if (String.IsNullOrEmpty(source))
+ {
+ // Empty strings get an empty sort key
+ keyData = EmptyArray<Byte>.Value;
+ // Fake value to test though so we can verify our flags
+ source = "\x0000";
+ }
+
+ int flags = GetNativeCompareFlags(options);
+
+ // Go ahead and call the OS
+ // First get the count
+ int length = InternalGetSortKey(m_dataHandle, m_handleOrigin, m_sortName, flags, source, source.Length, null, 0);
+
+ // If there was an error, return an error
+ if (length == 0)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "source");
+ }
+
+ // If input was empty, return the empty byte[] we made earlier and skip this
+ if (keyData == null)
+ {
+ // Make an appropriate byte array
+ keyData = new byte[length];
+
+ // Fill up the array
+ length = InternalGetSortKey(m_dataHandle, m_handleOrigin, m_sortName, flags, source, source.Length, keyData, keyData.Length);
+ }
+ else
+ {
+ source = String.Empty; // back to original
+ }
+
+ return new SortKey(Name, source, options, keyData);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Equals
+ //
+ // Implements Object.Equals(). Returns a boolean indicating whether
+ // or not object refers to the same CompareInfo as the current
+ // instance.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override bool Equals(Object value)
+ {
+ CompareInfo that = value as CompareInfo;
+
+ if (that != null)
+ {
+ return this.Name == that.Name;
+ }
+
+ return (false);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCode
+ //
+ // Implements Object.GetHashCode(). Returns the hash code for the
+ // CompareInfo. The hash code is guaranteed to be the same for
+ // CompareInfo A and B where A.Equals(B) is true.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override int GetHashCode()
+ {
+ return (this.Name.GetHashCode());
+ }
+
+ //
+ // return hash value for the string according to the input CompareOptions
+ //
+
+ public virtual int GetHashCode(string source, CompareOptions options)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException("source");
+ }
+
+ if (options == CompareOptions.Ordinal)
+ {
+ return source.GetHashCode();
+ }
+
+ if (options == CompareOptions.OrdinalIgnoreCase)
+ {
+ return TextInfo.GetHashCodeOrdinalIgnoreCase(source);
+ }
+
+ //
+ // GetHashCodeOfString does more parameters validation. basically will throw when
+ // having Ordinal, OrdinalIgnoreCase and StringSort
+ //
+
+ return GetHashCodeOfString(source, options, false, 0);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCodeOfString
+ //
+ // This internal method allows a method that allows the equivalent of creating a Sortkey for a
+ // string from CompareInfo, and generate a hashcode value from it. It is not very convenient
+ // to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed.
+ //
+ // The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
+ // the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
+ // treat the string the same way, this implementation will treat them differently (the same way that
+ // Sortkey does at the moment).
+ //
+ // This method will never be made public itself, but public consumers of it could be created, e.g.:
+ //
+ // string.GetHashCode(CultureInfo)
+ // string.GetHashCode(CompareInfo)
+ // string.GetHashCode(CultureInfo, CompareOptions)
+ // string.GetHashCode(CompareInfo, CompareOptions)
+ // etc.
+ //
+ // (the methods above that take a CultureInfo would use CultureInfo.CompareInfo)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ internal int GetHashCodeOfString(string source, CompareOptions options)
+ {
+ return GetHashCodeOfString(source, options, false, 0);
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal int GetHashCodeOfString(string source, CompareOptions options, bool forceRandomizedHashing, long additionalEntropy)
+ {
+ //
+ // Parameter validation
+ //
+ if(null == source)
+ {
+ throw new ArgumentNullException("source");
+ }
+
+ if ((options & ValidHashCodeOfStringMaskOffFlags) != 0)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
+ }
+ Contract.EndContractBlock();
+
+ if(0 == source.Length)
+ {
+ return(0);
+ }
+
+ //
+ ////////////////////////////////////////////////////////////////////////
+ return (InternalGetGlobalizedHashCode(m_dataHandle, m_handleOrigin, this.m_sortName, source, source.Length, GetNativeCompareFlags(options), forceRandomizedHashing, additionalEntropy));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Implements Object.ToString(). Returns a string describing the
+ // CompareInfo.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override String ToString()
+ {
+ return ("CompareInfo - " + this.Name);
+ }
+
+#if FEATURE_USE_LCID
+ public int LCID
+ {
+ get
+ {
+ return CultureInfo.GetCultureInfo(this.Name).LCID;
+ }
+ }
+#endif
+
+ [System.Security.SecuritySafeCritical]
+ internal static IntPtr InternalInitSortHandle(String localeName, out IntPtr handleOrigin)
+ {
+ return NativeInternalInitSortHandle(localeName, out handleOrigin);
+ }
+
+#if !FEATURE_CORECLR
+ private const int SORT_VERSION_WHIDBEY = 0x00001000;
+ private const int SORT_VERSION_V4 = 0x00060101;
+
+ internal static bool IsLegacy20SortingBehaviorRequested
+ {
+ get
+ {
+ return InternalSortVersion == SORT_VERSION_WHIDBEY;
+ }
+ }
+
+ private static uint InternalSortVersion
+ {
+ [System.Security.SecuritySafeCritical]
+ get
+ {
+ return InternalGetSortVersion();
+ }
+ }
+
+ [OptionalField(VersionAdded = 3)]
+ private SortVersion m_SortVersion;
+
+ public SortVersion Version
+ {
+ [SecuritySafeCritical]
+ get
+ {
+ if(m_SortVersion == null)
+ {
+ Win32Native.NlsVersionInfoEx v = new Win32Native.NlsVersionInfoEx();
+ v.dwNLSVersionInfoSize = Marshal.SizeOf(typeof(Win32Native.NlsVersionInfoEx));
+ InternalGetNlsVersionEx(m_dataHandle, m_handleOrigin, m_sortName, ref v);
+ m_SortVersion = new SortVersion(v.dwNLSVersion, (v.dwEffectiveId != 0) ? v.dwEffectiveId : LCID, v.guidCustomVersion);
+ }
+
+ return m_SortVersion;
+ }
+ }
+
+ [System.Security.SecurityCritical]
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool InternalGetNlsVersionEx(IntPtr handle, IntPtr handleOrigin, String localeName, ref Win32Native.NlsVersionInfoEx lpNlsVersionInformation);
+
+ [System.Security.SecurityCritical]
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ private static extern uint InternalGetSortVersion();
+
+#endif
+ [System.Security.SecurityCritical]
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ private static extern IntPtr NativeInternalInitSortHandle(String localeName, out IntPtr handleOrigin);
+
+ // Get a locale sensitive sort hash code from native code -- COMNlsInfo::InternalGetGlobalizedHashCode
+ [System.Security.SecurityCritical] // auto-generated
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ private static extern int InternalGetGlobalizedHashCode(IntPtr handle, IntPtr handleOrigin, string localeName, string source, int length, int dwFlags, bool forceRandomizedHashing, long additionalEntropy);
+
+ // Use native API calls to see if this string is entirely defined -- COMNlsInfo::InternalIsSortable
+ [System.Security.SecurityCritical] // auto-generated
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool InternalIsSortable(IntPtr handle, IntPtr handleOrigin, String localeName, String source, int length);
+
+ // Compare a string using the native API calls -- COMNlsInfo::InternalCompareString
+ [System.Security.SecurityCritical] // auto-generated
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ private static extern int InternalCompareString(IntPtr handle, IntPtr handleOrigin, String localeName, String string1, int offset1, int length1,
+ String string2, int offset2, int length2, int flags);
+
+ // InternalFindNLSStringEx parameters is not exactly matching kernel32::FindNLSStringEx parameters.
+ // Call through to NewApis::FindNLSStringEx so we can get the right behavior
+ [System.Security.SecurityCritical] // auto-generated
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ private static extern int InternalFindNLSStringEx(IntPtr handle, IntPtr handleOrigin, String localeName, int flags, String source, int sourceCount, int startIndex, string target, int targetCount);
+
+ // Call through to NewAPis::LCMapStringEx so we can get appropriate behavior for all platforms
+ [System.Security.SecurityCritical] // auto-generated
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ private static extern int InternalGetSortKey(IntPtr handle, IntPtr handleOrigin, String localeName, int flags, String source, int sourceCount, byte[] target, int targetCount);
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/CultureData.cs b/src/mscorlib/src/System/Globalization/CultureData.cs
new file mode 100644
index 0000000000..ae1eeea298
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CultureData.cs
@@ -0,0 +1,3354 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Threading;
+#if !FEATURE_CORECLR
+ using System.Reflection;
+ using System.Resources;
+#endif
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
+ using System.Runtime.Versioning;
+ using System.Diagnostics.Contracts;
+ using System.Security;
+
+ //
+ // List of culture data
+ // Note the we cache overrides.
+ // Note that localized names (resource names) aren't available from here.
+ //
+
+ //
+ // Our names are a tad confusing.
+ //
+ // sWindowsName -- The name that windows thinks this culture is, ie:
+ // en-US if you pass in en-US
+ // de-DE_phoneb if you pass in de-DE_phoneb
+ // fj-FJ if you pass in fj (neutral, on a pre-Windows 7 machine)
+ // fj if you pass in fj (neutral, post-Windows 7 machine)
+ //
+ // sRealName -- The name you used to construct the culture, in pretty form
+ // en-US if you pass in EN-us
+ // en if you pass in en
+ // de-DE_phoneb if you pass in de-DE_phoneb
+ //
+ // sSpecificCulture -- The specific culture for this culture
+ // en-US for en-US
+ // en-US for en
+ // de-DE_phoneb for alt sort
+ // fj-FJ for fj (neutral)
+ //
+ // sName -- The IETF name of this culture (ie: no sort info, could be neutral)
+ // en-US if you pass in en-US
+ // en if you pass in en
+ // de-DE if you pass in de-DE_phoneb
+ //
+
+ // StructLayout is needed here otherwise compiler can re-arrange the fields.
+ // We have to keep this in-sync with the definition in comnlsinfo.h
+ //
+ // WARNING WARNING WARNING
+ //
+ // WARNING: Anything changed here also needs to be updated on the native side (object.h see type CultureDataBaseObject)
+ // WARNING: The type loader will rearrange class member offsets so the mscorwks!CultureDataBaseObject
+ // WARNING: must be manually structured to match the true loaded class layout
+ //
+ [FriendAccessAllowed]
+ internal class CultureData
+ {
+ const int undef = -1;
+
+ // Override flag
+ private String sRealName; // Name you passed in (ie: en-US, en, or de-DE_phoneb)
+ private String sWindowsName; // Name OS thinks the object is (ie: de-DE_phoneb, or en-US (even if en was passed in))
+
+ // Identity
+ private String sName; // locale name (ie: en-us, NO sort info, but could be neutral)
+ private String sParent; // Parent name (which may be a custom locale/culture)
+ private String sLocalizedDisplayName; // Localized pretty name for this locale
+ private String sEnglishDisplayName; // English pretty name for this locale
+ private String sNativeDisplayName; // Native pretty name for this locale
+ private String sSpecificCulture; // The culture name to be used in CultureInfo.CreateSpecificCulture(), en-US form if neutral, sort name if sort
+
+ // Language
+ private String sISO639Language; // ISO 639 Language Name
+ private String sLocalizedLanguage; // Localized name for this language
+ private String sEnglishLanguage; // English name for this language
+ private String sNativeLanguage; // Native name of this language
+
+ // Region
+ private String sRegionName; // (RegionInfo)
+ private int iGeoId = undef; // GeoId
+ private String sLocalizedCountry; // localized country name
+ private String sEnglishCountry; // english country name (RegionInfo)
+ private String sNativeCountry; // native country name
+ private String sISO3166CountryName; // ISO 3166 (RegionInfo), ie: US
+
+ // Numbers
+ private String sPositiveSign; // (user can override) positive sign
+ private String sNegativeSign; // (user can override) negative sign
+ private String[] saNativeDigits; // (user can override) native characters for digits 0-9
+ // (nfi populates these 5, don't have to be = undef)
+ private int iDigitSubstitution; // (user can override) Digit substitution 0=context, 1=none/arabic, 2=Native/national (2 seems to be unused)
+ private int iLeadingZeros; // (user can override) leading zeros 0 = no leading zeros, 1 = leading zeros
+ private int iDigits; // (user can override) number of fractional digits
+ private int iNegativeNumber; // (user can override) negative number format
+ private int[] waGrouping; // (user can override) grouping of digits
+ private String sDecimalSeparator; // (user can override) decimal separator
+ private String sThousandSeparator; // (user can override) thousands separator
+ private String sNaN; // Not a Number
+ private String sPositiveInfinity; // + Infinity
+ private String sNegativeInfinity; // - Infinity
+
+ // Percent
+ private int iNegativePercent = undef; // Negative Percent (0-3)
+ private int iPositivePercent = undef; // Positive Percent (0-11)
+ private String sPercent; // Percent (%) symbol
+ private String sPerMille; // PerMille (‰) symbol
+
+ // Currency
+ private String sCurrency; // (user can override) local monetary symbol
+ private String sIntlMonetarySymbol; // international monetary symbol (RegionInfo)
+ private String sEnglishCurrency; // English name for this currency
+ private String sNativeCurrency; // Native name for this currency
+ // (nfi populates these 4, don't have to be = undef)
+ private int iCurrencyDigits; // (user can override) # local monetary fractional digits
+ private int iCurrency; // (user can override) positive currency format
+ private int iNegativeCurrency; // (user can override) negative currency format
+ private int[] waMonetaryGrouping; // (user can override) monetary grouping of digits
+ private String sMonetaryDecimal; // (user can override) monetary decimal separator
+ private String sMonetaryThousand; // (user can override) monetary thousands separator
+
+ // Misc
+ private int iMeasure = undef; // (user can override) system of measurement 0=metric, 1=US (RegionInfo)
+ private String sListSeparator; // (user can override) list separator
+ // private int iPaperSize ; // default paper size (RegionInfo)
+
+ // Time
+ private String sAM1159; // (user can override) AM designator
+ private String sPM2359; // (user can override) PM designator
+ private String sTimeSeparator;
+ private volatile String[] saLongTimes; // (user can override) time format
+ private volatile String[] saShortTimes; // short time format
+ private volatile String[] saDurationFormats; // time duration format
+
+ // Calendar specific data
+ private int iFirstDayOfWeek = undef; // (user can override) first day of week (gregorian really)
+ private int iFirstWeekOfYear = undef; // (user can override) first week of year (gregorian really)
+ private volatile int[] waCalendars; // all available calendar type(s). The first one is the default calendar
+
+ // Store for specific data about each calendar
+ private CalendarData[] calendars; // Store for specific calendar data
+
+ // Text information
+ private int iReadingLayout = undef; // Reading layout data
+ // 0 - Left to right (eg en-US)
+ // 1 - Right to left (eg arabic locales)
+ // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
+ // 3 - Vertical top to bottom with columns proceeding to the right
+
+ private String sTextInfo; // Text info name to use for custom
+ private String sCompareInfo; // Compare info name (including sorting key) to use if custom
+ private String sScripts; // Typical Scripts for this locale (latn;cyrl; etc)
+
+ private int iDefaultAnsiCodePage = undef; // default ansi code page ID (ACP)
+ private int iDefaultOemCodePage = undef; // default oem code page ID (OCP or OEM)
+ private int iDefaultMacCodePage = undef; // default macintosh code page
+ private int iDefaultEbcdicCodePage = undef; // default EBCDIC code page
+
+ // These are desktop only, not coreclr
+ private int iLanguage; // locale ID (0409) - NO sort information
+ private String sAbbrevLang; // abbreviated language name (Windows Language Name) ex: ENU
+ private String sAbbrevCountry; // abbreviated country name (RegionInfo) (Windows Region Name) ex: USA
+ private String sISO639Language2; // 3 char ISO 639 lang name 2 ex: eng
+ private String sISO3166CountryName2; // 3 char ISO 3166 country name 2 2(RegionInfo) ex: USA (ISO)
+ private int iInputLanguageHandle=undef;// input language handle
+ private String sConsoleFallbackName; // The culture name for the console fallback UI culture
+ private String sKeyboardsToInstall; // Keyboard installation string.
+ private String fontSignature; // Font signature (16 WORDS)
+
+ // The bools all need to be in one spot
+ private bool bUseOverrides; // use user overrides?
+ private bool bNeutral; // Flags for the culture (ie: neutral or not right now)
+ private bool bWin32Installed; // Flags indicate if the culture is Win32 installed
+ private bool bFramework; // Flags for indicate if the culture is one of Whidbey cultures
+
+ // Region Name to Culture Name mapping table
+ // (In future would be nice to be in registry or something)
+
+ //Using a property so we avoid creating the dictionary untill we need it
+ private static Dictionary<string, string> RegionNames
+ {
+ get
+ {
+ if (s_RegionNames == null)
+ {
+ var regionNames = new Dictionary<string, string> {
+ { "029", "en-029" },
+ { "AE", "ar-AE" },
+ { "AF", "prs-AF" },
+ { "AL", "sq-AL" },
+ { "AM", "hy-AM" },
+ { "AR", "es-AR" },
+ { "AT", "de-AT" },
+ { "AU", "en-AU" },
+ { "AZ", "az-Cyrl-AZ" },
+ { "BA", "bs-Latn-BA" },
+ { "BD", "bn-BD" },
+ { "BE", "nl-BE" },
+ { "BG", "bg-BG" },
+ { "BH", "ar-BH" },
+ { "BN", "ms-BN" },
+ { "BO", "es-BO" },
+ { "BR", "pt-BR" },
+ { "BY", "be-BY" },
+ { "BZ", "en-BZ" },
+ { "CA", "en-CA" },
+ { "CH", "it-CH" },
+ { "CL", "es-CL" },
+ { "CN", "zh-CN" },
+ { "CO", "es-CO" },
+ { "CR", "es-CR" },
+ { "CS", "sr-Cyrl-CS" },
+ { "CZ", "cs-CZ" },
+ { "DE", "de-DE" },
+ { "DK", "da-DK" },
+ { "DO", "es-DO" },
+ { "DZ", "ar-DZ" },
+ { "EC", "es-EC" },
+ { "EE", "et-EE" },
+ { "EG", "ar-EG" },
+ { "ES", "es-ES" },
+ { "ET", "am-ET" },
+ { "FI", "fi-FI" },
+ { "FO", "fo-FO" },
+ { "FR", "fr-FR" },
+ { "GB", "en-GB" },
+ { "GE", "ka-GE" },
+ { "GL", "kl-GL" },
+ { "GR", "el-GR" },
+ { "GT", "es-GT" },
+ { "HK", "zh-HK" },
+ { "HN", "es-HN" },
+ { "HR", "hr-HR" },
+ { "HU", "hu-HU" },
+ { "ID", "id-ID" },
+ { "IE", "en-IE" },
+ { "IL", "he-IL" },
+ { "IN", "hi-IN" },
+ { "IQ", "ar-IQ" },
+ { "IR", "fa-IR" },
+ { "IS", "is-IS" },
+ { "IT", "it-IT" },
+ { "IV", "" },
+ { "JM", "en-JM" },
+ { "JO", "ar-JO" },
+ { "JP", "ja-JP" },
+ { "KE", "sw-KE" },
+ { "KG", "ky-KG" },
+ { "KH", "km-KH" },
+ { "KR", "ko-KR" },
+ { "KW", "ar-KW" },
+ { "KZ", "kk-KZ" },
+ { "LA", "lo-LA" },
+ { "LB", "ar-LB" },
+ { "LI", "de-LI" },
+ { "LK", "si-LK" },
+ { "LT", "lt-LT" },
+ { "LU", "lb-LU" },
+ { "LV", "lv-LV" },
+ { "LY", "ar-LY" },
+ { "MA", "ar-MA" },
+ { "MC", "fr-MC" },
+ { "ME", "sr-Latn-ME" },
+ { "MK", "mk-MK" },
+ { "MN", "mn-MN" },
+ { "MO", "zh-MO" },
+ { "MT", "mt-MT" },
+ { "MV", "dv-MV" },
+ { "MX", "es-MX" },
+ { "MY", "ms-MY" },
+ { "NG", "ig-NG" },
+ { "NI", "es-NI" },
+ { "NL", "nl-NL" },
+ { "NO", "nn-NO" },
+ { "NP", "ne-NP" },
+ { "NZ", "en-NZ" },
+ { "OM", "ar-OM" },
+ { "PA", "es-PA" },
+ { "PE", "es-PE" },
+ { "PH", "en-PH" },
+ { "PK", "ur-PK" },
+ { "PL", "pl-PL" },
+ { "PR", "es-PR" },
+ { "PT", "pt-PT" },
+ { "PY", "es-PY" },
+ { "QA", "ar-QA" },
+ { "RO", "ro-RO" },
+ { "RS", "sr-Latn-RS" },
+ { "RU", "ru-RU" },
+ { "RW", "rw-RW" },
+ { "SA", "ar-SA" },
+ { "SE", "sv-SE" },
+ { "SG", "zh-SG" },
+ { "SI", "sl-SI" },
+ { "SK", "sk-SK" },
+ { "SN", "wo-SN" },
+ { "SV", "es-SV" },
+ { "SY", "ar-SY" },
+ { "TH", "th-TH" },
+ { "TJ", "tg-Cyrl-TJ" },
+ { "TM", "tk-TM" },
+ { "TN", "ar-TN" },
+ { "TR", "tr-TR" },
+ { "TT", "en-TT" },
+ { "TW", "zh-TW" },
+ { "UA", "uk-UA" },
+ { "US", "en-US" },
+ { "UY", "es-UY" },
+ { "UZ", "uz-Cyrl-UZ" },
+ { "VE", "es-VE" },
+ { "VN", "vi-VN" },
+ { "YE", "ar-YE" },
+ { "ZA", "af-ZA" },
+ { "ZW", "en-ZW" }
+ };
+ s_RegionNames = regionNames;
+ }
+ return s_RegionNames;
+ }
+ }
+ private volatile static Dictionary<string, string> s_RegionNames;
+
+ /////////////////////////////////////////////////////////////////////////
+ // Build our invariant information
+ //
+ // We need an invariant instance, which we build hard-coded
+ /////////////////////////////////////////////////////////////////////////
+ internal static CultureData Invariant
+ {
+ get
+ {
+ if (s_Invariant == null)
+ {
+ // Make a new culturedata
+ CultureData invariant = new CultureData();
+
+ // Call the native code to get the value of bWin32Installed.
+ // For versions <= Vista, we set this to false for compatibility with v2.
+ // For Windows 7, the flag is true.
+ invariant.bUseOverrides = false;
+ invariant.sRealName = "";
+
+ // Ask the native code to fill it out for us, we only need the field IsWin32Installed
+ nativeInitCultureData(invariant);
+
+ // Basics
+ // Note that we override the resources since this IS NOT supposed to change (by definition)
+ invariant.bUseOverrides = false;
+ invariant.sRealName = ""; // Name you passed in (ie: en-US, en, or de-DE_phoneb)
+ invariant.sWindowsName = ""; // Name OS thinks the object is (ie: de-DE_phoneb, or en-US (even if en was passed in))
+
+ // Identity
+ invariant.sName = ""; // locale name (ie: en-us)
+ invariant.sParent = ""; // Parent name (which may be a custom locale/culture)
+ invariant.bNeutral = false; // Flags for the culture (ie: neutral or not right now)
+ // Don't set invariant.bWin32Installed, we used nativeInitCultureData for that.
+ invariant.bFramework = true;
+
+ invariant.sEnglishDisplayName = "Invariant Language (Invariant Country)"; // English pretty name for this locale
+ invariant.sNativeDisplayName = "Invariant Language (Invariant Country)"; // Native pretty name for this locale
+ invariant.sSpecificCulture = ""; // The culture name to be used in CultureInfo.CreateSpecificCulture()
+
+ // Language
+ invariant.sISO639Language = "iv"; // ISO 639 Language Name
+ invariant.sLocalizedLanguage = "Invariant Language"; // Display name for this Language
+ invariant.sEnglishLanguage = "Invariant Language"; // English name for this language
+ invariant.sNativeLanguage = "Invariant Language"; // Native name of this language
+
+ // Region
+ invariant.sRegionName = "IV"; // (RegionInfo)
+ // Unused for now:
+ // invariant.iCountry =1; // country code (RegionInfo)
+ invariant.iGeoId = 244; // GeoId (Windows Only)
+ invariant.sEnglishCountry = "Invariant Country"; // english country name (RegionInfo)
+ invariant.sNativeCountry = "Invariant Country"; // native country name (Windows Only)
+ invariant.sISO3166CountryName = "IV"; // (RegionInfo), ie: US
+
+ // Numbers
+ invariant.sPositiveSign = "+"; // positive sign
+ invariant.sNegativeSign = "-"; // negative sign
+ invariant.saNativeDigits = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; // native characters for digits 0-9
+ invariant.iDigitSubstitution = 1; // Digit substitution 0=context, 1=none/arabic, 2=Native/national (2 seems to be unused) (Windows Only)
+ invariant.iLeadingZeros = 1; // leading zeros 0=no leading zeros, 1=leading zeros
+ invariant.iDigits = 2; // number of fractional digits
+ invariant.iNegativeNumber = 1; // negative number format
+ invariant.waGrouping = new int[] { 3 }; // grouping of digits
+ invariant.sDecimalSeparator = "."; // decimal separator
+ invariant.sThousandSeparator = ","; // thousands separator
+ invariant.sNaN = "NaN"; // Not a Number
+ invariant.sPositiveInfinity = "Infinity"; // + Infinity
+ invariant.sNegativeInfinity = "-Infinity"; // - Infinity
+
+ // Percent
+ invariant.iNegativePercent = 0; // Negative Percent (0-3)
+ invariant.iPositivePercent = 0; // Positive Percent (0-11)
+ invariant.sPercent = "%"; // Percent (%) symbol
+ invariant.sPerMille = "\x2030"; // PerMille(‰) symbol
+
+ // Currency
+ invariant.sCurrency = "\x00a4"; // local monetary symbol "¤: for international monetary symbol
+ invariant.sIntlMonetarySymbol = "XDR"; // international monetary symbol (RegionInfo)
+ invariant.sEnglishCurrency = "International Monetary Fund"; // English name for this currency (Windows Only)
+ invariant.sNativeCurrency = "International Monetary Fund"; // Native name for this currency (Windows Only)
+ invariant.iCurrencyDigits = 2; // # local monetary fractional digits
+ invariant.iCurrency = 0; // positive currency format
+ invariant.iNegativeCurrency = 0; // negative currency format
+ invariant.waMonetaryGrouping = new int[] { 3 }; // monetary grouping of digits
+ invariant.sMonetaryDecimal = "."; // monetary decimal separator
+ invariant.sMonetaryThousand = ","; // monetary thousands separator
+
+ // Misc
+ invariant.iMeasure = 0; // system of measurement 0=metric, 1=US (RegionInfo)
+ invariant.sListSeparator = ","; // list separator
+ // Unused for now:
+ // invariant.iPaperSize =9; // default paper size (RegionInfo)
+ // invariant.waFontSignature ="\x0002\x0000\x0000\x0000\x0000\x0000\x0000\x8000\x0001\x0000\x0000\x8000\x0001\x0000\x0000\x8000"; // Font signature (16 WORDS) (Windows Only)
+
+ // Time
+ invariant.sAM1159 = "AM"; // AM designator
+ invariant.sPM2359 = "PM"; // PM designator
+ invariant.saLongTimes = new String[] { "HH:mm:ss" }; // time format
+ invariant.saShortTimes = new String[] { "HH:mm", "hh:mm tt", "H:mm", "h:mm tt" }; // short time format
+ invariant.saDurationFormats = new String[] { "HH:mm:ss" }; // time duration format
+
+ // Calendar specific data
+ invariant.iFirstDayOfWeek = 0; // first day of week
+ invariant.iFirstWeekOfYear = 0; // first week of year
+ invariant.waCalendars = new int[] { (int)CalendarId.GREGORIAN }; // all available calendar type(s). The first one is the default calendar
+
+ // Store for specific data about each calendar
+ invariant.calendars = new CalendarData[CalendarData.MAX_CALENDARS];
+ invariant.calendars[0] = CalendarData.Invariant;
+
+ // Text information
+ invariant.iReadingLayout = 0; // Reading Layout = RTL
+
+ invariant.sTextInfo = ""; // Text info name to use for custom
+ invariant.sCompareInfo = ""; // Compare info name (including sorting key) to use if custom
+ invariant.sScripts = "Latn;"; // Typical Scripts for this locale (latn,cyrl, etc)
+
+ invariant.iLanguage = 0x007f; // locale ID (0409) - NO sort information
+ invariant.iDefaultAnsiCodePage = 1252; // default ansi code page ID (ACP)
+ invariant.iDefaultOemCodePage = 437; // default oem code page ID (OCP or OEM)
+ invariant.iDefaultMacCodePage = 10000; // default macintosh code page
+ invariant.iDefaultEbcdicCodePage = 037; // default EBCDIC code page
+ invariant.sAbbrevLang = "IVL"; // abbreviated language name (Windows Language Name)
+ invariant.sAbbrevCountry = "IVC"; // abbreviated country name (RegionInfo) (Windows Region Name)
+ invariant.sISO639Language2 = "ivl"; // 3 char ISO 639 lang name 2
+ invariant.sISO3166CountryName2 = "ivc"; // 3 char ISO 3166 country name 2 2(RegionInfo)
+ invariant.iInputLanguageHandle = 0x007f; // input language handle
+ invariant.sConsoleFallbackName = ""; // The culture name for the console fallback UI culture
+ invariant.sKeyboardsToInstall = "0409:00000409"; // Keyboard installation string.
+ // Remember it
+ s_Invariant = invariant;
+ }
+ return s_Invariant;
+ }
+ }
+ private volatile static CultureData s_Invariant;
+
+
+#if !FEATURE_CORECLR
+ internal static volatile ResourceSet MscorlibResourceSet;
+#endif
+
+#if !FEATURE_CORECLR
+ [System.Security.SecurityCritical] // auto-generated
+ private static bool IsResourcePresent(String resourceKey)
+ {
+ if (MscorlibResourceSet == null)
+ {
+ MscorlibResourceSet = new ResourceSet(typeof(Environment).Assembly.GetManifestResourceStream("mscorlib.resources"));
+ }
+ return MscorlibResourceSet.GetString(resourceKey) != null;
+ }
+#endif
+
+ ///////////////
+ // Constructors //
+ ///////////////
+ // Cache of cultures we've already looked up
+ private static volatile Dictionary<String, CultureData> s_cachedCultures;
+
+ [FriendAccessAllowed]
+ internal static CultureData GetCultureData(String cultureName, bool useUserOverride)
+ {
+ // First do a shortcut for Invariant
+ if (String.IsNullOrEmpty(cultureName))
+ {
+ return CultureData.Invariant;
+ }
+
+ // Try the hash table first
+ String hashName = AnsiToLower(useUserOverride ? cultureName : cultureName + '*');
+ Dictionary<String, CultureData> tempHashTable = s_cachedCultures;
+ if (tempHashTable == null)
+ {
+ // No table yet, make a new one
+ tempHashTable = new Dictionary<String, CultureData>();
+ }
+ else
+ {
+ // Check the hash table
+ CultureData retVal;
+ lock (((ICollection)tempHashTable).SyncRoot)
+ {
+ tempHashTable.TryGetValue(hashName, out retVal);
+ }
+ if (retVal != null)
+ {
+ return retVal;
+ }
+ }
+
+ // Not found in the hash table, need to see if we can build one that works for us
+ CultureData culture = CreateCultureData(cultureName, useUserOverride);
+ if (culture == null)
+ {
+ return null;
+ }
+
+ // Found one, add it to the cache
+ lock (((ICollection)tempHashTable).SyncRoot)
+ {
+ tempHashTable[hashName] = culture;
+ }
+
+ // Copy the hashtable to the corresponding member variables. This will potentially overwrite
+ // new tables simultaneously created by a new thread, but maximizes thread safety.
+ s_cachedCultures = tempHashTable;
+
+ return culture;
+ }
+
+ private static CultureData CreateCultureData(string cultureName, bool useUserOverride)
+ {
+ CultureData culture = new CultureData();
+ culture.bUseOverrides = useUserOverride;
+ culture.sRealName = cultureName;
+
+ // Ask native code if that one's real
+ if (culture.InitCultureData() == false)
+ {
+#if !FEATURE_CORECLR
+ if (culture.InitCompatibilityCultureData() == false
+ && culture.InitLegacyAlternateSortData() == false)
+#endif
+ {
+ return null;
+ }
+ }
+
+ return culture;
+ }
+
+ private bool InitCultureData()
+ {
+ if (nativeInitCultureData(this) == false)
+ {
+ return false;
+ }
+
+#if !FEATURE_CORECLR
+ if (CultureInfo.IsTaiwanSku)
+ {
+ TreatTaiwanParentChainAsHavingTaiwanAsSpecific();
+ }
+#endif
+ return true;
+ }
+
+#if !FEATURE_CORECLR
+ [System.Security.SecuritySafeCritical]
+ private void TreatTaiwanParentChainAsHavingTaiwanAsSpecific()
+ {
+ if (IsNeutralInParentChainOfTaiwan() && IsOsPriorToWin7() && !IsReplacementCulture)
+ {
+ // force population of fields that should have information that is
+ // different than zh-TW:
+ string s = SNATIVELANGUAGE;
+ s = SENGLISHLANGUAGE;
+ s = SLOCALIZEDLANGUAGE;
+ s = STEXTINFO;
+ s = SCOMPAREINFO;
+ s = FONTSIGNATURE;
+ int i = IDEFAULTANSICODEPAGE;
+ i = IDEFAULTOEMCODEPAGE;
+ i = IDEFAULTMACCODEPAGE;
+
+ this.sSpecificCulture = "zh-TW";
+ this.sWindowsName = "zh-TW";
+ }
+ }
+
+ private bool IsNeutralInParentChainOfTaiwan()
+ {
+ return this.sRealName == "zh" || this.sRealName == "zh-Hant";
+ }
+
+ static readonly Version s_win7Version = new Version(6, 1);
+ static private bool IsOsPriorToWin7()
+ {
+ return Environment.OSVersion.Platform == PlatformID.Win32NT &&
+ Environment.OSVersion.Version < s_win7Version;
+ }
+ static private bool IsOsWin7OrPrior()
+ {
+ return Environment.OSVersion.Platform == PlatformID.Win32NT &&
+ Environment.OSVersion.Version < new Version(6, 2); // Win7 is 6.1.Build.Revision so we have to check for anything less than 6.2
+ }
+
+ private bool InitCompatibilityCultureData()
+ {
+ // for compatibility handle the deprecated ids: zh-chs, zh-cht
+ string cultureName = this.sRealName;
+
+ string fallbackCultureName;
+ string realCultureName;
+ switch (AnsiToLower(cultureName))
+ {
+ case "zh-chs":
+ fallbackCultureName = "zh-Hans";
+ realCultureName = "zh-CHS";
+ break;
+ case "zh-cht":
+ fallbackCultureName = "zh-Hant";
+ realCultureName = "zh-CHT";
+ break;
+ default:
+ return false;
+ }
+
+ this.sRealName = fallbackCultureName;
+ if (InitCultureData() == false)
+ {
+ return false;
+ }
+ // fixup our data
+ this.sName = realCultureName; // the name that goes back to the user
+ this.sParent = fallbackCultureName;
+ this.bFramework = true;
+
+ return true;
+ }
+
+ private bool InitLegacyAlternateSortData()
+ {
+ if (!CompareInfo.IsLegacy20SortingBehaviorRequested)
+ {
+ return false;
+ }
+
+ // For V2 compatibility, handle deprecated alternate sorts
+ string cultureName = this.sRealName;
+
+ switch (AnsiToLower(cultureName))
+ {
+ case "ko-kr_unicod":
+ cultureName = "ko-KR_unicod";
+ this.sRealName = "ko-KR";
+ this.iLanguage = 0x00010412;
+ break;
+ case "ja-jp_unicod":
+ cultureName = "ja-JP_unicod";
+ this.sRealName = "ja-JP";
+ this.iLanguage = 0x00010411;
+ break;
+ case "zh-hk_stroke":
+ cultureName = "zh-HK_stroke";
+ this.sRealName = "zh-HK";
+ this.iLanguage = 0x00020c04;
+ break;
+ default:
+ return false;
+ }
+
+ if (nativeInitCultureData(this) == false)
+ {
+ return false;
+ }
+
+ this.sRealName = cultureName;
+ this.sCompareInfo = cultureName;
+ this.bFramework = true;
+
+ return true;
+ }
+
+#if FEATURE_WIN32_REGISTRY
+ private static String s_RegionKey = @"System\CurrentControlSet\Control\Nls\RegionMapping";
+#endif // FEATURE_WIN32_REGISTRY
+
+#endif // !FEATURE_CORECLR
+ // Cache of regions we've already looked up
+ private static volatile Dictionary<String, CultureData> s_cachedRegions;
+
+ [System.Security.SecurityCritical] // auto-generated
+ internal static CultureData GetCultureDataForRegion(String cultureName, bool useUserOverride)
+ {
+ // First do a shortcut for Invariant
+ if (String.IsNullOrEmpty(cultureName))
+ {
+ return CultureData.Invariant;
+ }
+
+ //
+ // First check if GetCultureData() can find it (ie: its a real culture)
+ //
+ CultureData retVal = GetCultureData(cultureName, useUserOverride);
+ if (retVal != null && (retVal.IsNeutralCulture == false)) return retVal;
+
+ //
+ // Not a specific culture, perhaps it's region-only name
+ // (Remember this isn't a core clr path where that's not supported)
+ //
+
+ // If it was neutral remember that so that RegionInfo() can throw the right exception
+ CultureData neutral = retVal;
+
+ // Try the hash table next
+ String hashName = AnsiToLower(useUserOverride ? cultureName : cultureName + '*');
+ Dictionary<String, CultureData> tempHashTable = s_cachedRegions;
+ if (tempHashTable == null)
+ {
+ // No table yet, make a new one
+ tempHashTable = new Dictionary<String, CultureData>();
+ }
+ else
+ {
+ // Check the hash table
+ lock (((ICollection)tempHashTable).SyncRoot)
+ {
+ tempHashTable.TryGetValue(hashName, out retVal);
+ }
+ if (retVal != null)
+ {
+ return retVal;
+ }
+ }
+
+ //
+ // Not found in the hash table, look it up the hard way
+ //
+#if !FEATURE_CORECLR
+#if FEATURE_WIN32_REGISTRY
+ // First try the registry in case there are overrides of our table
+ try
+ {
+ // Open in read-only mode.
+ // Use InternalOpenSubKey so that we avoid the security check.
+ Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.LocalMachine.InternalOpenSubKey(s_RegionKey, false);
+
+ if (key != null)
+ {
+ try
+ {
+ Object value = key.InternalGetValue(cultureName, null, false, false);
+
+ if (value != null)
+ {
+ // Get the name of the locale to try.
+ String specificForRegion = value.ToString();
+
+ // See if it's real
+ retVal = GetCultureData(specificForRegion, useUserOverride);
+ }
+ }
+ finally
+ {
+ key.Close();
+ }
+ }
+ }
+ // If this fails for any reason, we'll just ignore it, likely it just isn't there.
+ catch (ObjectDisposedException) { }
+ catch (ArgumentException) { }
+#endif // FEATURE_WIN32_REGISTRY
+#endif // !FEATURE_CORECLR
+
+ // If not a valid mapping from the registry we'll have to try the hard coded table
+ if (retVal == null || (retVal.IsNeutralCulture == true))
+ {
+ // Not a valid mapping, try the hard coded table
+ if (RegionNames.ContainsKey(cultureName))
+ {
+ // Make sure we can get culture data for it
+ retVal = GetCultureData(RegionNames[cultureName], useUserOverride);
+ }
+ }
+
+ // If not found in the hard coded table we'll have to find a culture that works for us
+ if (retVal == null || (retVal.IsNeutralCulture == true))
+ {
+ // Not found in the hard coded table, need to see if we can find a culture that works for us
+ // Not a real culture name, see if it matches a region name
+ // (we just return the first culture we match)
+ CultureInfo[] specifics = SpecificCultures;
+ for (int i = 0; i < specifics.Length; i++)
+ {
+ if (String.Compare(specifics[i].m_cultureData.SREGIONNAME, cultureName, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ // Matched, use this culture
+ retVal = specifics[i].m_cultureData;
+ break;
+ }
+ }
+ }
+
+ // If we found one we can use, then cash it for next time
+ if (retVal != null && (retVal.IsNeutralCulture == false))
+ {
+ // first add it to the cache
+ lock (((ICollection)tempHashTable).SyncRoot)
+ {
+ tempHashTable[hashName] = retVal;
+ }
+
+ // Copy the hashtable to the corresponding member variables. This will potentially overwrite
+ // new tables simultaneously created by a new thread, but maximizes thread safety.
+ s_cachedRegions = tempHashTable;
+ }
+ else
+ {
+ // Unable to find a matching culture/region, return null or neutral
+ // (regionInfo throws a more specific exception on neutrals)
+ retVal = neutral;
+ }
+
+ // Return the found culture to use, null, or the neutral culture.
+ return retVal;
+ }
+
+#if FEATURE_USE_LCID
+ // Obtain locale name from LCID
+ // NOTE: This will get neutral names, unlike the OS API
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern String LCIDToLocaleName(int lcid);
+
+ // We'd rather people use the named version since this doesn't allow custom locales
+ internal static CultureData GetCultureData(int culture, bool bUseUserOverride)
+ {
+ String localeName = null;
+ CultureData retVal = null;
+
+#if !FEATURE_CORECLR
+ // If V2 legacy sort is requested, then provide deprecated alternate sorts
+ if (CompareInfo.IsLegacy20SortingBehaviorRequested)
+ {
+ switch (culture)
+ {
+ case 0x00010412:
+ localeName = "ko-KR_unicod";
+ break;
+ case 0x00010411:
+ localeName = "ja-JP_unicod";
+ break;
+ case 0x00020c04:
+ localeName = "zh-HK_stroke";
+ break;
+ }
+ }
+#endif
+
+ if (localeName == null)
+ {
+ // Convert the lcid to a name, then use that
+ // Note that this'll return neutral names (unlike Vista native API)
+ localeName = LCIDToLocaleName(culture);
+ }
+
+ // If its not valid, then throw
+ if (String.IsNullOrEmpty(localeName))
+ {
+ // Could be valid for Invariant
+ if (culture == 0x007f)
+ return Invariant;
+ }
+ else
+ {
+#if !FEATURE_CORECLR
+ switch (localeName)
+ {
+ // for compatibility with Whidbey, when requesting
+ // a locale from LCID, return the old localeName
+ case "zh-Hans":
+ localeName = "zh-CHS";
+ break;
+ case "zh-Hant":
+ localeName = "zh-CHT";
+ break;
+ }
+#endif
+ // Valid name, use it
+ retVal = GetCultureData(localeName, bUseUserOverride);
+ }
+
+ // If not successful, throw
+ if (retVal == null)
+ throw new CultureNotFoundException(
+ "culture", culture, Environment.GetResourceString("Argument_CultureNotSupported"));
+
+ // Return the one we found
+ return retVal;
+ }
+#endif
+
+ // Clear our internal caches
+ internal static void ClearCachedData()
+ {
+ s_cachedCultures = null;
+ s_cachedRegions = null;
+ s_replacementCultureNames = null;
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal static CultureInfo[] GetCultures(CultureTypes types)
+ {
+ // Disable warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete
+#pragma warning disable 618
+ // Validate flags
+ if ((int)types <= 0 || ((int)types & (int)~(CultureTypes.NeutralCultures | CultureTypes.SpecificCultures |
+ CultureTypes.InstalledWin32Cultures | CultureTypes.UserCustomCulture |
+ CultureTypes.ReplacementCultures | CultureTypes.WindowsOnlyCultures |
+ CultureTypes.FrameworkCultures)) != 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ "types",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), CultureTypes.NeutralCultures, CultureTypes.FrameworkCultures));
+ }
+
+ //
+ // CHANGE FROM Whidbey
+ //
+ // We have deprecated CultureTypes.FrameworkCultures.
+ // When this enum is used, we will enumerate Whidbey framework cultures (for compatibility).
+ //
+
+ // We have deprecated CultureTypes.WindowsOnlyCultures.
+ // When this enum is used, we will return an empty array for this enum.
+ if ((types & CultureTypes.WindowsOnlyCultures) != 0)
+ {
+ // Remove the enum as it is an no-op.
+ types &= (~CultureTypes.WindowsOnlyCultures);
+ }
+
+ String[] cultureNames = null;
+
+ //
+ // Call nativeEnumCultureNames() to get a string array of culture names based on the specified
+ // enumeration type.
+ //
+ // nativeEnumCultureNames is a QCall. We need to use a reference to return the string array
+ // allocated from the QCall. That ref has to be wrapped as object handle.
+ // See vm\qcall.h for details in QCall.
+ //
+
+ if (nativeEnumCultureNames((int)types, JitHelpers.GetObjectHandleOnStack(ref cultureNames)) == 0)
+ {
+ return new CultureInfo[0];
+ }
+
+ int arrayLength = cultureNames.Length;
+
+ if ((types & (CultureTypes.NeutralCultures | CultureTypes.FrameworkCultures)) != 0) // add zh-CHT and zh-CHS
+ {
+ arrayLength += 2;
+ }
+
+ CultureInfo[] cultures = new CultureInfo[arrayLength];
+
+ for (int i = 0; i < cultureNames.Length; i++)
+ {
+ cultures[i] = new CultureInfo(cultureNames[i]);
+ }
+
+ if ((types & (CultureTypes.NeutralCultures | CultureTypes.FrameworkCultures)) != 0) // add zh-CHT and zh-CHS
+ {
+ Contract.Assert(arrayLength == cultureNames.Length + 2, "CultureData.nativeEnumCultureNames() Incorrect array size");
+ cultures[cultureNames.Length] = new CultureInfo("zh-CHS");
+ cultures[cultureNames.Length + 1] = new CultureInfo("zh-CHT");
+ }
+
+#pragma warning restore 618
+
+ return cultures;
+ }
+
+ internal static volatile CultureInfo[] specificCultures;
+
+ private static CultureInfo[] SpecificCultures
+ {
+ get
+ {
+ if (specificCultures == null)
+ specificCultures = GetCultures(CultureTypes.SpecificCultures);
+
+ return specificCultures;
+ }
+ }
+
+ internal bool IsReplacementCulture
+ {
+ get
+ {
+ return IsReplacementCultureName(this.SNAME);
+ }
+ }
+
+ internal static volatile String[] s_replacementCultureNames;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Cache for the known replacement cultures.
+ // This is used by CultureInfo.CultureType to check if a culture is a
+ // replacement culture.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ private static bool IsReplacementCultureName(String name)
+ {
+ Contract.Assert(name != null, "IsReplacementCultureName(): name should not be null");
+ String[] replacementCultureNames = s_replacementCultureNames;
+ if (replacementCultureNames == null)
+ {
+ if (nativeEnumCultureNames((int)CultureTypes.ReplacementCultures, JitHelpers.GetObjectHandleOnStack(ref replacementCultureNames)) == 0)
+ {
+ return false;
+ }
+
+ // Even if we don't have any replacement cultures, the returned replacementCultureNames will still an empty string array, not null.
+ Contract.Assert(name != null, "IsReplacementCultureName(): replacementCultureNames should not be null");
+ Array.Sort(replacementCultureNames);
+ s_replacementCultureNames = replacementCultureNames;
+ }
+ return Array.BinarySearch(replacementCultureNames, name) >= 0;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // All the accessors
+ //
+ // Accessors for our data object items
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ ///////////
+ // Identity //
+ ///////////
+
+ // The real name used to construct the locale (ie: de-DE_phoneb)
+ internal String CultureName
+ {
+ get
+ {
+ Contract.Assert(this.sRealName != null, "[CultureData.CultureName] Expected this.sRealName to be populated by COMNlsInfo::nativeInitCultureData already");
+ // since windows doesn't know about zh-CHS and zh-CHT,
+ // we leave sRealName == zh-Hanx but we still need to
+ // pretend that it was zh-CHX.
+ switch (this.sName)
+ {
+ case "zh-CHS":
+ case "zh-CHT":
+ return this.sName;
+ }
+ return this.sRealName;
+ }
+ }
+
+ // Are overrides enabled?
+ internal bool UseUserOverride
+ {
+ get
+ {
+ return this.bUseOverrides;
+ }
+ }
+
+ // locale name (ie: de-DE, NO sort information)
+ internal String SNAME
+ {
+ get
+ {
+ // Contract.Assert(this.sName != null,
+ // "[CultureData.SNAME] Expected this.sName to be populated by COMNlsInfo::nativeInitCultureData already");
+ if (this.sName == null)
+ {
+ this.sName = String.Empty;
+ }
+ return this.sName;
+ }
+ }
+
+ // Parent name (which may be a custom locale/culture)
+ internal String SPARENT
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sParent == null)
+ {
+ // Ask using the real name, so that we get parents of neutrals
+ this.sParent = DoGetLocaleInfo(this.sRealName, LOCALE_SPARENT);
+
+#if !FEATURE_CORECLR
+ // for compatibility, the chain should be:
+ // zh-CN -> zh-CHS -> zh-Hans -> zh
+ // zh-TW -> zh-CHT -> zh-Hant -> zh
+ Contract.Assert(this.sName != "zh-CHS" && this.sName != "zh-CHT",
+ "sParent should have been initialized for zh-CHS and zh-CHT when they were constructed, otherwise we get recursion");
+ switch (this.sParent)
+ {
+ case "zh-Hans":
+ this.sParent = "zh-CHS";
+ break;
+ case "zh-Hant":
+ this.sParent = "zh-CHT";
+ break;
+ }
+#endif
+
+ }
+ return this.sParent;
+ }
+ }
+
+ // Localized pretty name for this locale (ie: Inglis (estados Unitos))
+ internal String SLOCALIZEDDISPLAYNAME
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sLocalizedDisplayName == null)
+ {
+#if !FEATURE_CORECLR
+ String resourceKey = "Globalization.ci_" + this.sName;
+ if (IsResourcePresent(resourceKey))
+ {
+ this.sLocalizedDisplayName = Environment.GetResourceString(resourceKey);
+ }
+#endif
+ // If it hasn't been found (Windows 8 and up), fallback to the system
+ if (String.IsNullOrEmpty(this.sLocalizedDisplayName))
+ {
+ // If its neutral use the language name
+ if (this.IsNeutralCulture)
+ {
+ this.sLocalizedDisplayName = this.SLOCALIZEDLANGUAGE;
+ }
+ else
+ {
+ // We have to make the neutral distinction in case the OS returns a specific name
+ if (CultureInfo.UserDefaultUICulture.Name.Equals(Thread.CurrentThread.CurrentUICulture.Name))
+ {
+ this.sLocalizedDisplayName = DoGetLocaleInfo(LOCALE_SLOCALIZEDDISPLAYNAME);
+ }
+ if (String.IsNullOrEmpty(this.sLocalizedDisplayName))
+ {
+ this.sLocalizedDisplayName = this.SNATIVEDISPLAYNAME;
+ }
+ }
+ }
+ }
+ return this.sLocalizedDisplayName;
+ }
+ }
+
+ // English pretty name for this locale (ie: English (United States))
+ internal String SENGDISPLAYNAME
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sEnglishDisplayName == null)
+ {
+ // If its neutral use the language name
+ if (this.IsNeutralCulture)
+ {
+ this.sEnglishDisplayName = this.SENGLISHLANGUAGE;
+#if !FEATURE_CORECLR
+ // differentiate the legacy display names
+ switch (this.sName)
+ {
+ case "zh-CHS":
+ case "zh-CHT":
+ this.sEnglishDisplayName += " Legacy";
+ break;
+ }
+#endif
+
+ }
+ else
+ {
+ this.sEnglishDisplayName = DoGetLocaleInfo(LOCALE_SENGLISHDISPLAYNAME);
+
+ // if it isn't found build one:
+ if (String.IsNullOrEmpty(this.sEnglishDisplayName))
+ {
+ // Our existing names mostly look like:
+ // "English" + "United States" -> "English (United States)"
+ // "Azeri (Latin)" + "Azerbaijan" -> "Azeri (Latin, Azerbaijan)"
+ if (this.SENGLISHLANGUAGE.EndsWith(')'))
+ {
+ // "Azeri (Latin)" + "Azerbaijan" -> "Azeri (Latin, Azerbaijan)"
+ this.sEnglishDisplayName =
+ this.SENGLISHLANGUAGE.Substring(0, this.sEnglishLanguage.Length - 1) +
+ ", " + this.SENGCOUNTRY + ")";
+ }
+ else
+ {
+ // "English" + "United States" -> "English (United States)"
+ this.sEnglishDisplayName = this.SENGLISHLANGUAGE + " (" + this.SENGCOUNTRY + ")";
+ }
+ }
+ }
+ }
+ return this.sEnglishDisplayName;
+ }
+ }
+
+ // Native pretty name for this locale (ie: Deutsch (Deutschland))
+ internal String SNATIVEDISPLAYNAME
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sNativeDisplayName == null)
+ {
+ // If its neutral use the language name
+ if (this.IsNeutralCulture)
+ {
+ this.sNativeDisplayName = this.SNATIVELANGUAGE;
+#if !FEATURE_CORECLR
+ // differentiate the legacy display names
+ switch (this.sName)
+ {
+ case "zh-CHS":
+ this.sNativeDisplayName += " \u65E7\u7248";
+ break;
+ case "zh-CHT":
+ this.sNativeDisplayName += " \u820A\u7248";
+ break;
+ }
+#endif
+ }
+ else
+ {
+#if !FEATURE_CORECLR
+ if (IsIncorrectNativeLanguageForSinhala())
+ {
+ // work around bug in Windows 7 for native name of Sinhala
+ this.sNativeDisplayName ="\x0dc3\x0dd2\x0d82\x0dc4\x0dbd (\x0DC1\x0DCA\x200D\x0DBB\x0DD3\x0020\x0DBD\x0D82\x0D9A\x0DCF)";
+ }
+ else
+#endif
+ {
+ this.sNativeDisplayName = DoGetLocaleInfo(LOCALE_SNATIVEDISPLAYNAME);
+ }
+
+ // if it isn't found build one:
+ if (String.IsNullOrEmpty(this.sNativeDisplayName))
+ {
+ // These should primarily be "Deutsch (Deutschland)" type names
+ this.sNativeDisplayName = this.SNATIVELANGUAGE + " (" + this.SNATIVECOUNTRY + ")";
+ }
+ }
+ }
+ return this.sNativeDisplayName;
+ }
+ }
+
+ // The culture name to be used in CultureInfo.CreateSpecificCulture()
+ internal String SSPECIFICCULTURE
+ {
+ get
+ {
+ // This got populated when ComNlsInfo::nativeInitCultureData told us we had a culture
+ Contract.Assert(this.sSpecificCulture != null, "[CultureData.SSPECIFICCULTURE] Expected this.sSpecificCulture to be populated by COMNlsInfo::nativeInitCultureData already");
+ return this.sSpecificCulture;
+ }
+ }
+
+ /////////////
+ // Language //
+ /////////////
+
+ // iso 639 language name, ie: en
+ internal String SISO639LANGNAME
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sISO639Language == null)
+ {
+ this.sISO639Language = DoGetLocaleInfo(LOCALE_SISO639LANGNAME);
+ }
+ return this.sISO639Language;
+ }
+ }
+
+ // iso 639 language name, ie: eng
+ internal String SISO639LANGNAME2
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sISO639Language2 == null)
+ {
+ this.sISO639Language2 = DoGetLocaleInfo(LOCALE_SISO639LANGNAME2);
+ }
+ return this.sISO639Language2;
+ }
+ }
+
+ // abbreviated windows language name (ie: enu) (non-standard, avoid this)
+ internal String SABBREVLANGNAME
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sAbbrevLang == null)
+ {
+ this.sAbbrevLang = DoGetLocaleInfo(LOCALE_SABBREVLANGNAME);
+ }
+ return this.sAbbrevLang;
+ }
+ }
+
+ // Localized name for this language (Windows Only) ie: Inglis
+ // This is only valid for Windows 8 and higher neutrals:
+ internal String SLOCALIZEDLANGUAGE
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sLocalizedLanguage == null)
+ {
+ if (CultureInfo.UserDefaultUICulture.Name.Equals(Thread.CurrentThread.CurrentUICulture.Name))
+ {
+ this.sLocalizedLanguage = DoGetLocaleInfo(LOCALE_SLOCALIZEDLANGUAGENAME);
+ }
+ // Some OS's might not have this resource or LCTYPE
+ if (String.IsNullOrEmpty(this.sLocalizedLanguage))
+ {
+ this.sLocalizedLanguage = SNATIVELANGUAGE;
+ }
+ }
+
+ return this.sLocalizedLanguage;
+ }
+ }
+
+ // English name for this language (Windows Only) ie: German
+ internal String SENGLISHLANGUAGE
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sEnglishLanguage == null)
+ {
+ this.sEnglishLanguage = DoGetLocaleInfo(LOCALE_SENGLISHLANGUAGENAME);
+ }
+ return this.sEnglishLanguage;
+ }
+ }
+
+ // Native name of this language (Windows Only) ie: Deutsch
+ internal String SNATIVELANGUAGE
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sNativeLanguage == null)
+ {
+#if !FEATURE_CORECLR
+ if (IsIncorrectNativeLanguageForSinhala())
+ {
+ this.sNativeLanguage = "\x0dc3\x0dd2\x0d82\x0dc4\x0dbd";
+ }
+ else
+#endif
+ {
+ this.sNativeLanguage = DoGetLocaleInfo(LOCALE_SNATIVELANGUAGENAME);
+ }
+ }
+ return this.sNativeLanguage;
+ }
+ }
+
+#if !FEATURE_CORECLR
+ private bool IsIncorrectNativeLanguageForSinhala()
+ {
+ return IsOsWin7OrPrior()
+ && (sName == "si-LK" || sName == "si")
+ && !IsReplacementCulture;
+ }
+#endif
+
+ ///////////
+ // Region //
+ ///////////
+
+ // region name (eg US)
+ internal String SREGIONNAME
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sRegionName == null)
+ {
+ this.sRegionName = DoGetLocaleInfo(LOCALE_SISO3166CTRYNAME);
+ }
+ return this.sRegionName;
+ }
+ }
+
+ // (user can override) country code (RegionInfo)
+ internal int ICOUNTRY
+ {
+ get
+ {
+ return DoGetLocaleInfoInt(LOCALE_ICOUNTRY);
+ }
+ }
+
+ // GeoId
+ internal int IGEOID
+ {
+ get
+ {
+ if (this.iGeoId == undef)
+ {
+ this.iGeoId = DoGetLocaleInfoInt(LOCALE_IGEOID);
+ }
+ return this.iGeoId;
+ }
+ }
+
+ // localized name for the country
+ internal string SLOCALIZEDCOUNTRY
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sLocalizedCountry == null)
+ {
+#if !FEATURE_CORECLR
+ String resourceKey = "Globalization.ri_" + this.SREGIONNAME;
+ if (IsResourcePresent(resourceKey))
+ {
+ this.sLocalizedCountry = Environment.GetResourceString(resourceKey);
+ }
+#endif
+ // If it hasn't been found (Windows 8 and up), fallback to the system
+ if (String.IsNullOrEmpty(this.sLocalizedCountry))
+ {
+ // We have to make the neutral distinction in case the OS returns a specific name
+ if (CultureInfo.UserDefaultUICulture.Name.Equals(Thread.CurrentThread.CurrentUICulture.Name))
+ {
+ this.sLocalizedCountry = DoGetLocaleInfo(LOCALE_SLOCALIZEDCOUNTRYNAME);
+ }
+ if (String.IsNullOrEmpty(this.sLocalizedDisplayName))
+ {
+ this.sLocalizedCountry = SNATIVECOUNTRY;
+ }
+ }
+ }
+ return this.sLocalizedCountry;
+ }
+ }
+
+ // english country name (RegionInfo) ie: Germany
+ internal String SENGCOUNTRY
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sEnglishCountry == null)
+ {
+ this.sEnglishCountry = DoGetLocaleInfo(LOCALE_SENGLISHCOUNTRYNAME);
+ }
+ return this.sEnglishCountry;
+ }
+ }
+
+ // native country name (RegionInfo) ie: Deutschland
+ internal String SNATIVECOUNTRY
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sNativeCountry == null)
+ {
+ this.sNativeCountry = DoGetLocaleInfo(LOCALE_SNATIVECOUNTRYNAME);
+ }
+ return this.sNativeCountry;
+ }
+ }
+
+ // ISO 3166 Country Name
+ internal String SISO3166CTRYNAME
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sISO3166CountryName == null)
+ {
+ this.sISO3166CountryName = DoGetLocaleInfo(LOCALE_SISO3166CTRYNAME);
+ }
+ return this.sISO3166CountryName;
+ }
+ }
+
+ // ISO 3166 Country Name
+ internal String SISO3166CTRYNAME2
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sISO3166CountryName2 == null)
+ {
+ this.sISO3166CountryName2 = DoGetLocaleInfo(LOCALE_SISO3166CTRYNAME2);
+ }
+ return this.sISO3166CountryName2;
+ }
+ }
+
+ // abbreviated Country Name (windows version, non-standard, avoid)
+ internal String SABBREVCTRYNAME
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sAbbrevCountry == null)
+ {
+ this.sAbbrevCountry = DoGetLocaleInfo(LOCALE_SABBREVCTRYNAME);
+ }
+ return this.sAbbrevCountry;
+ }
+ }
+
+ // Default Country
+ private int IDEFAULTCOUNTRY
+ {
+ get
+ {
+ return DoGetLocaleInfoInt(LOCALE_IDEFAULTCOUNTRY);
+ }
+ }
+
+ // Console fallback name (ie: locale to use for console apps for unicode-only locales)
+ internal int IINPUTLANGUAGEHANDLE
+ {
+ get
+ {
+ if (this.iInputLanguageHandle == undef)
+ {
+ if (IsSupplementalCustomCulture)
+ {
+ this.iInputLanguageHandle = 0x0409;
+ }
+ else
+ {
+ // Input Language is same as LCID for built-in cultures
+ this.iInputLanguageHandle = this.ILANGUAGE;
+ }
+ }
+ return this.iInputLanguageHandle;
+ }
+ }
+
+ // Console fallback name (ie: locale to use for console apps for unicode-only locales)
+ internal String SCONSOLEFALLBACKNAME
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sConsoleFallbackName == null)
+ {
+ string consoleFallbackName = DoGetLocaleInfo(LOCALE_SCONSOLEFALLBACKNAME);
+ if (consoleFallbackName == "es-ES_tradnl")
+ {
+ consoleFallbackName = "es-ES";
+ }
+ this.sConsoleFallbackName = consoleFallbackName;
+ }
+ return this.sConsoleFallbackName;
+ }
+ }
+
+ /////////////
+ // Numbers //
+ ////////////
+
+ // internal String sPositiveSign ; // (user can override) positive sign
+ // internal String sNegativeSign ; // (user can override) negative sign
+ // internal String[] saNativeDigits ; // (user can override) native characters for digits 0-9
+ // internal int iDigitSubstitution ; // (user can override) Digit substitution 0=context, 1=none/arabic, 2=Native/national (2 seems to be unused) (Windows Only)
+ // internal int iDigits ; // (user can override) number of fractional digits
+ // internal int iNegativeNumber ; // (user can override) negative number format
+
+ // Leading zeroes
+ private bool ILEADINGZEROS
+ {
+ get
+ {
+ return (DoGetLocaleInfoInt(LOCALE_ILZERO) == 1);
+ }
+ }
+
+
+ // (user can override) grouping of digits
+ internal int[] WAGROUPING
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.waGrouping == null || UseUserOverride)
+ {
+ this.waGrouping = ConvertWin32GroupString(DoGetLocaleInfo(LOCALE_SGROUPING));
+ }
+ return this.waGrouping;
+ }
+ }
+
+
+ // internal String sDecimalSeparator ; // (user can override) decimal separator
+ // internal String sThousandSeparator ; // (user can override) thousands separator
+
+ // Not a Number
+ internal String SNAN
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sNaN == null)
+ {
+ this.sNaN = DoGetLocaleInfo(LOCALE_SNAN);
+ }
+ return this.sNaN;
+ }
+ }
+
+ // + Infinity
+ internal String SPOSINFINITY
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sPositiveInfinity == null)
+ {
+ this.sPositiveInfinity = DoGetLocaleInfo(LOCALE_SPOSINFINITY);
+ }
+ return this.sPositiveInfinity;
+ }
+ }
+
+ // - Infinity
+ internal String SNEGINFINITY
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sNegativeInfinity == null)
+ {
+ this.sNegativeInfinity = DoGetLocaleInfo(LOCALE_SNEGINFINITY);
+ }
+ return this.sNegativeInfinity;
+ }
+ }
+
+
+ ////////////
+ // Percent //
+ ///////////
+
+ // Negative Percent (0-3)
+ internal int INEGATIVEPERCENT
+ {
+ get
+ {
+ if (this.iNegativePercent == undef)
+ {
+ // Note that <= Windows Vista this is synthesized by native code
+ this.iNegativePercent = DoGetLocaleInfoInt(LOCALE_INEGATIVEPERCENT);
+ }
+ return this.iNegativePercent;
+ }
+ }
+
+ // Positive Percent (0-11)
+ internal int IPOSITIVEPERCENT
+ {
+ get
+ {
+ if (this.iPositivePercent == undef)
+ {
+ // Note that <= Windows Vista this is synthesized by native code
+ this.iPositivePercent = DoGetLocaleInfoInt(LOCALE_IPOSITIVEPERCENT);
+ }
+ return this.iPositivePercent;
+ }
+ }
+
+ // Percent (%) symbol
+ internal String SPERCENT
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sPercent == null)
+ {
+ // Note that <= Windows Vista this is synthesized by native code
+ this.sPercent = DoGetLocaleInfo(LOCALE_SPERCENT);
+ }
+ return this.sPercent;
+ }
+ }
+
+ // PerMille (‰) symbol
+ internal String SPERMILLE
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sPerMille == null)
+ {
+ // Note that <= Windows Vista this is synthesized by native code
+ this.sPerMille = DoGetLocaleInfo(LOCALE_SPERMILLE);
+ }
+ return this.sPerMille;
+ }
+ }
+
+ /////////////
+ // Currency //
+ /////////////
+
+ // (user can override) local monetary symbol, eg: $
+ internal String SCURRENCY
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sCurrency == null || UseUserOverride)
+ {
+ this.sCurrency = DoGetLocaleInfo(LOCALE_SCURRENCY);
+ }
+ return this.sCurrency;
+ }
+ }
+
+ // international monetary symbol (RegionInfo), eg: USD
+ internal String SINTLSYMBOL
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sIntlMonetarySymbol == null)
+ {
+ this.sIntlMonetarySymbol = DoGetLocaleInfo(LOCALE_SINTLSYMBOL);
+ }
+ return this.sIntlMonetarySymbol;
+ }
+ }
+
+ // English name for this currency (RegionInfo), eg: US Dollar
+ internal String SENGLISHCURRENCY
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sEnglishCurrency == null)
+ {
+ this.sEnglishCurrency = DoGetLocaleInfo(LOCALE_SENGCURRNAME);
+ }
+ return this.sEnglishCurrency;
+ }
+ }
+
+ // Native name for this currency (RegionInfo), eg: Schweiz Frank
+ internal String SNATIVECURRENCY
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sNativeCurrency == null)
+ {
+ this.sNativeCurrency = DoGetLocaleInfo(LOCALE_SNATIVECURRNAME);
+ }
+ return this.sNativeCurrency;
+ }
+ }
+
+ // internal int iCurrencyDigits ; // (user can override) # local monetary fractional digits
+ // internal int iCurrency ; // (user can override) positive currency format
+ // internal int iNegativeCurrency ; // (user can override) negative currency format
+
+ // (user can override) monetary grouping of digits
+ internal int[] WAMONGROUPING
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.waMonetaryGrouping == null || UseUserOverride)
+ {
+ this.waMonetaryGrouping = ConvertWin32GroupString(DoGetLocaleInfo(LOCALE_SMONGROUPING));
+ }
+ return this.waMonetaryGrouping;
+ }
+ }
+
+ // internal String sMonetaryDecimal ; // (user can override) monetary decimal separator
+ // internal String sMonetaryThousand ; // (user can override) monetary thousands separator
+
+ /////////
+ // Misc //
+ /////////
+
+ // (user can override) system of measurement 0=metric, 1=US (RegionInfo)
+ internal int IMEASURE
+ {
+ get
+ {
+ if (this.iMeasure == undef || UseUserOverride)
+ {
+ this.iMeasure = DoGetLocaleInfoInt(LOCALE_IMEASURE);
+ }
+ return this.iMeasure;
+ }
+ }
+
+ // (user can override) list Separator
+ internal String SLIST
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sListSeparator == null || UseUserOverride)
+ {
+ this.sListSeparator = DoGetLocaleInfo(LOCALE_SLIST);
+ }
+ return this.sListSeparator;
+ }
+ }
+
+ // Paper size
+ private int IPAPERSIZE
+ {
+ get
+ {
+ return DoGetLocaleInfoInt(LOCALE_IPAPERSIZE);
+ }
+ }
+
+ ////////////////////////////
+ // Calendar/Time (Gregorian) //
+ ////////////////////////////
+
+ // (user can override) AM designator
+ internal String SAM1159
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sAM1159 == null || UseUserOverride)
+ {
+ this.sAM1159 = DoGetLocaleInfo(LOCALE_S1159);
+ }
+ return this.sAM1159;
+ }
+ }
+
+ // (user can override) PM designator
+ internal String SPM2359
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.sPM2359 == null || UseUserOverride)
+ {
+ this.sPM2359 = DoGetLocaleInfo(LOCALE_S2359);
+ }
+ return this.sPM2359;
+ }
+ }
+
+ // (user can override) time format
+ internal String[] LongTimes
+ {
+ get
+ {
+ if (this.saLongTimes == null || UseUserOverride)
+ {
+ String[] longTimes = DoEnumTimeFormats();
+ if (longTimes == null || longTimes.Length == 0)
+ {
+ this.saLongTimes = Invariant.saLongTimes;
+ }
+ else
+ {
+ this.saLongTimes = longTimes;
+ }
+ }
+ return this.saLongTimes;
+ }
+ }
+
+ // short time format
+ // Short times (derived from long times format)
+ internal String[] ShortTimes
+ {
+ get
+ {
+ if (this.saShortTimes == null || UseUserOverride)
+ {
+ // Try to get the short times from the OS/culture.dll
+ String[] shortTimes = DoEnumShortTimeFormats();
+
+ if (shortTimes == null || shortTimes.Length == 0)
+ {
+ //
+ // If we couldn't find short times, then compute them from long times
+ // (eg: CORECLR on < Win7 OS & fallback for missing culture.dll)
+ //
+ shortTimes = DeriveShortTimesFromLong();
+ }
+
+ // Found short times, use them
+ this.saShortTimes = shortTimes;
+ }
+ return this.saShortTimes;
+ }
+ }
+
+ private string[] DeriveShortTimesFromLong()
+ {
+ // Our logic is to look for h,H,m,s,t. If we find an s, then we check the string
+ // between it and the previous marker, if any. If its a short, unescaped separator,
+ // then we don't retain that part.
+ // We then check after the ss and remove anything before the next h,H,m,t...
+ string[] shortTimes = new string[LongTimes.Length];
+
+ for (int i = 0; i < LongTimes.Length; i++)
+ {
+ shortTimes[i] = StripSecondsFromPattern(LongTimes[i]);
+ }
+ return shortTimes;
+ }
+
+ private static string StripSecondsFromPattern(string time)
+ {
+ bool bEscape = false;
+ int iLastToken = -1;
+
+ // Find the seconds
+ for (int j = 0; j < time.Length; j++)
+ {
+ // Change escape mode?
+ if (time[j] == '\'')
+ {
+ // Continue
+ bEscape = !bEscape;
+ continue;
+ }
+
+ // See if there was a single \
+ if (time[j] == '\\')
+ {
+ // Skip next char
+ j++;
+ continue;
+ }
+
+ if (bEscape)
+ {
+ continue;
+ }
+
+ switch (time[j])
+ {
+ // Check for seconds
+ case 's':
+ // Found seconds, see if there was something unescaped and short between
+ // the last marker and the seconds. Windows says separator can be a
+ // maximum of three characters (without null)
+ // If 1st or last characters were ', then ignore it
+ if ((j - iLastToken) <= 4 && (j - iLastToken) > 1 &&
+ (time[iLastToken + 1] != '\'') &&
+ (time[j - 1] != '\''))
+ {
+ // There was something there we want to remember
+ if (iLastToken >= 0)
+ {
+ j = iLastToken + 1;
+ }
+ }
+
+ bool containsSpace;
+ int endIndex = GetIndexOfNextTokenAfterSeconds(time, j, out containsSpace);
+ StringBuilder sb = new StringBuilder(time.Substring(0, j));
+ if (containsSpace)
+ {
+ sb.Append(' ');
+ }
+ sb.Append(time.Substring(endIndex));
+ time = sb.ToString();
+ break;
+ case 'm':
+ case 'H':
+ case 'h':
+ iLastToken = j;
+ break;
+ }
+ }
+ return time;
+ }
+
+ private static int GetIndexOfNextTokenAfterSeconds(string time, int index, out bool containsSpace)
+ {
+ bool bEscape = false;
+ containsSpace = false;
+ for (; index < time.Length; index++)
+ {
+ switch (time[index])
+ {
+ case '\'':
+ bEscape = !bEscape;
+ continue;
+ case '\\':
+ index++;
+ if (time[index] == ' ')
+ {
+ containsSpace = true;
+ }
+ continue;
+ case ' ':
+ containsSpace = true;
+ break;
+ case 't':
+ case 'm':
+ case 'H':
+ case 'h':
+ if (bEscape)
+ {
+ continue;
+ }
+ return index;
+ }
+ }
+ containsSpace = false;
+ return index;
+ }
+
+ // time duration format
+ internal String[] SADURATION
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (this.saDurationFormats == null)
+ {
+ String durationFormat = DoGetLocaleInfo(LOCALE_SDURATION);
+ this.saDurationFormats = new String[] { ReescapeWin32String(durationFormat) };
+ }
+ return this.saDurationFormats;
+ }
+ }
+
+ // (user can override) first day of week
+ internal int IFIRSTDAYOFWEEK
+ {
+ get
+ {
+ if (this.iFirstDayOfWeek == undef || UseUserOverride)
+ {
+ // Have to convert it from windows to .Net formats
+ this.iFirstDayOfWeek = ConvertFirstDayOfWeekMonToSun(DoGetLocaleInfoInt(LOCALE_IFIRSTDAYOFWEEK));
+ }
+ return this.iFirstDayOfWeek;
+ }
+ }
+
+ // (user can override) first week of year
+ internal int IFIRSTWEEKOFYEAR
+ {
+ get
+ {
+ if (this.iFirstWeekOfYear == undef || UseUserOverride)
+ {
+ this.iFirstWeekOfYear = DoGetLocaleInfoInt(LOCALE_IFIRSTWEEKOFYEAR);
+ }
+ return this.iFirstWeekOfYear;
+ }
+ }
+
+ // (user can override default only) short date format
+ internal String[] ShortDates(int calendarId)
+ {
+ return GetCalendar(calendarId).saShortDates;
+ }
+
+ // (user can override default only) long date format
+ internal String[] LongDates(int calendarId)
+ {
+ return GetCalendar(calendarId).saLongDates;
+ }
+
+ // (user can override) date year/month format.
+ internal String[] YearMonths(int calendarId)
+ {
+ return GetCalendar(calendarId).saYearMonths;
+ }
+
+ // day names
+ internal string[] DayNames(int calendarId)
+ {
+ return GetCalendar(calendarId).saDayNames;
+ }
+
+ // abbreviated day names
+ internal string[] AbbreviatedDayNames(int calendarId)
+ {
+ // Get abbreviated day names for this calendar from the OS if necessary
+ return GetCalendar(calendarId).saAbbrevDayNames;
+ }
+
+ // The super short day names
+ internal string[] SuperShortDayNames(int calendarId)
+ {
+ return GetCalendar(calendarId).saSuperShortDayNames;
+ }
+
+ // month names
+ internal string[] MonthNames(int calendarId)
+ {
+ return GetCalendar(calendarId).saMonthNames;
+ }
+
+ // Genitive month names
+ internal string[] GenitiveMonthNames(int calendarId)
+ {
+ return GetCalendar(calendarId).saMonthGenitiveNames;
+ }
+
+ // month names
+ internal string[] AbbreviatedMonthNames(int calendarId)
+ {
+ return GetCalendar(calendarId).saAbbrevMonthNames;
+ }
+
+ // Genitive month names
+ internal string[] AbbreviatedGenitiveMonthNames(int calendarId)
+ {
+ return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
+ }
+
+ // Leap year month names
+ // Note: This only applies to Hebrew, and it basically adds a "1" to the 6th month name
+ // the non-leap names skip the 7th name in the normal month name array
+ internal string[] LeapYearMonthNames(int calendarId)
+ {
+ return GetCalendar(calendarId).saLeapYearMonthNames;
+ }
+
+ // month/day format (single string, no override)
+ internal String MonthDay(int calendarId)
+ {
+ return GetCalendar(calendarId).sMonthDay;
+ }
+
+
+
+ /////////////
+ // Calendars //
+ /////////////
+
+ // all available calendar type(s), The first one is the default calendar.
+ internal int[] CalendarIds
+ {
+ get
+ {
+ if (this.waCalendars == null)
+ {
+ // We pass in an array of ints, and native side fills it up with count calendars.
+ // We then have to copy that list to a new array of the right size.
+ // Default calendar should be first
+ int[] calendarInts = new int[23];
+ Contract.Assert(this.sWindowsName != null, "[CultureData.CalendarIds] Expected this.sWindowsName to be populated by COMNlsInfo::nativeInitCultureData already");
+ int count = CalendarData.nativeGetCalendars(this.sWindowsName, this.bUseOverrides, calendarInts);
+
+ // See if we had a calendar to add.
+ if (count == 0)
+ {
+ // Failed for some reason, just grab Gregorian from Invariant
+ this.waCalendars = Invariant.waCalendars;
+ }
+ else
+ {
+ // The OS may not return calendar 4 for zh-TW, but we've always allowed it.
+ if (this.sWindowsName == "zh-TW")
+ {
+ bool found = false;
+
+ // Do we need to insert calendar 4?
+ for (int i = 0; i < count; i++)
+ {
+ // Stop if we found calendar four
+ if (calendarInts[i] == Calendar.CAL_TAIWAN)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ // If not found then insert it
+ if (!found)
+ {
+ // Insert it as the 2nd calendar
+ count++;
+ // Copy them from the 2nd position to the end, -1 for skipping 1st, -1 for one being added.
+ Array.Copy(calendarInts, 1, calendarInts, 2, 23 - 1 - 1);
+ calendarInts[1] = Calendar.CAL_TAIWAN;
+ }
+ }
+
+ // It worked, remember the list
+ int[] temp = new int[count];
+ Array.Copy(calendarInts, temp, count);
+
+ // Want 1st calendar to be default
+ // Prior to Vista the enumeration didn't have default calendar first
+ // Only a coreclr concern, culture.dll does the right thing.
+#if FEATURE_CORECLR
+ if (temp.Length > 1)
+ {
+ int i = DoGetLocaleInfoInt(LOCALE_ICALENDARTYPE);
+ if (temp[1] == i)
+ {
+ temp[1] = temp[0];
+ temp[0] = i;
+ }
+ }
+#endif
+
+ this.waCalendars = temp;
+ }
+ }
+
+ return this.waCalendars;
+ }
+ }
+
+ // Native calendar names. index of optional calendar - 1, empty if no optional calendar at that number
+ internal String CalendarName(int calendarId)
+ {
+ // Get the calendar
+ return GetCalendar(calendarId).sNativeName;
+ }
+
+ internal CalendarData GetCalendar(int calendarId)
+ {
+ Contract.Assert(calendarId > 0 && calendarId <= CalendarData.MAX_CALENDARS,
+ "[CultureData.GetCalendar] Expect calendarId to be in a valid range");
+
+ // arrays are 0 based, calendarIds are 1 based
+ int calendarIndex = calendarId - 1;
+
+ // Have to have calendars
+ if (calendars == null)
+ {
+ calendars = new CalendarData[CalendarData.MAX_CALENDARS];
+ }
+
+ // we need the following local variable to avoid returning null
+ // when another thread creates a new array of CalendarData (above)
+ // right after we insert the newly created CalendarData (below)
+ CalendarData calendarData = calendars[calendarIndex];
+ // Make sure that calendar has data
+ if (calendarData == null || UseUserOverride)
+ {
+ Contract.Assert(this.sWindowsName != null, "[CultureData.GetCalendar] Expected this.sWindowsName to be populated by COMNlsInfo::nativeInitCultureData already");
+ calendarData = new CalendarData(this.sWindowsName, calendarId, this.UseUserOverride);
+#if !FEATURE_CORECLR
+ //Work around issue where Win7 data for MonthDay contains invalid two sets of data separated by semicolon
+ //even though MonthDay is not enumerated
+ if (IsOsWin7OrPrior() && !IsSupplementalCustomCulture && !IsReplacementCulture)
+ {
+ calendarData.FixupWin7MonthDaySemicolonBug();
+ }
+#endif
+ calendars[calendarIndex] = calendarData;
+ }
+
+ return calendarData;
+ }
+
+ internal int CurrentEra(int calendarId)
+ {
+ return GetCalendar(calendarId).iCurrentEra;
+ }
+
+ ///////////////////
+ // Text Information //
+ ///////////////////
+
+ // IsRightToLeft
+ internal bool IsRightToLeft
+ {
+ get
+ {
+ // Returns one of the following 4 reading layout values:
+ // 0 - Left to right (eg en-US)
+ // 1 - Right to left (eg arabic locales)
+ // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
+ // 3 - Vertical top to bottom with columns proceeding to the right
+ return (this.IREADINGLAYOUT == 1);
+ }
+ }
+
+ // IREADINGLAYOUT
+ // Returns one of the following 4 reading layout values:
+ // 0 - Left to right (eg en-US)
+ // 1 - Right to left (eg arabic locales)
+ // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
+ // 3 - Vertical top to bottom with columns proceeding to the right
+ //
+ // If exposed as a public API, we'd have an enum with those 4 values
+ private int IREADINGLAYOUT
+ {
+ get
+ {
+ if (this.iReadingLayout == undef)
+ {
+ Contract.Assert(this.sRealName != null, "[CultureData.IsRightToLeft] Expected this.sRealName to be populated by COMNlsInfo::nativeInitCultureData already");
+ this.iReadingLayout = DoGetLocaleInfoInt(LOCALE_IREADINGLAYOUT);
+ }
+
+ return (this.iReadingLayout);
+ }
+ }
+
+ // The TextInfo name never includes that alternate sort and is always specific
+ // For customs, it uses the SortLocale (since the textinfo is not exposed in Win7)
+ // en -> en-US
+ // en-US -> en-US
+ // fj (custom neutral) -> en-US (assuming that en-US is the sort locale for fj)
+ // fj_FJ (custom specific) -> en-US (assuming that en-US is the sort locale for fj-FJ)
+ // es-ES_tradnl -> es-ES
+ internal String STEXTINFO // Text info name to use for text information
+ {
+ [System.Security.SecuritySafeCritical]
+ get
+ {
+ if (this.sTextInfo == null)
+ {
+ // LOCALE_SSORTLOCALE is broken in Win7 for Alt sorts.
+ // It is also not supported downlevel without culture.dll.
+ if (IsNeutralCulture || IsSupplementalCustomCulture)
+ {
+ string sortLocale = DoGetLocaleInfo(LOCALE_SSORTLOCALE);
+ this.sTextInfo = GetCultureData(sortLocale, bUseOverrides).SNAME;
+ }
+
+ if (this.sTextInfo == null)
+ {
+ this.sTextInfo = this.SNAME; // removes alternate sort
+ }
+ }
+
+ return this.sTextInfo;
+ }
+ }
+
+ // Compare info name (including sorting key) to use if custom
+ internal String SCOMPAREINFO
+ {
+ [System.Security.SecuritySafeCritical]
+ get
+ {
+ if (this.sCompareInfo == null)
+ {
+ // LOCALE_SSORTLOCALE is broken in Win7 for Alt sorts.
+ // It is also not supported downlevel without culture.dll.
+ // We really only need it for the custom locale case though
+ // since for all other cases, it is the same as sWindowsName
+ if (IsSupplementalCustomCulture)
+ {
+ this.sCompareInfo = DoGetLocaleInfo(LOCALE_SSORTLOCALE);
+ }
+
+ if (this.sCompareInfo == null)
+ {
+ this.sCompareInfo = this.sWindowsName;
+ }
+ }
+
+ return this.sCompareInfo;
+ }
+ }
+
+ internal bool IsSupplementalCustomCulture
+ {
+ get
+ {
+ return IsCustomCultureId(this.ILANGUAGE);
+ }
+ }
+
+ // Typical Scripts for this locale (latn;cyrl; etc)
+
+ private String SSCRIPTS
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ if (this.sScripts == null)
+ {
+ this.sScripts = DoGetLocaleInfo(LOCALE_SSCRIPTS);
+ }
+ return this.sScripts;
+ }
+ }
+
+ private String SOPENTYPELANGUAGETAG
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return DoGetLocaleInfo(LOCALE_SOPENTYPELANGUAGETAG);
+ }
+ }
+
+ private String FONTSIGNATURE
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ if (this.fontSignature == null)
+ {
+ this.fontSignature = DoGetLocaleInfo(LOCALE_FONTSIGNATURE);
+ }
+ return this.fontSignature;
+ }
+ }
+
+ private String SKEYBOARDSTOINSTALL
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return DoGetLocaleInfo(LOCALE_SKEYBOARDSTOINSTALL);
+ }
+ }
+
+
+ internal int IDEFAULTANSICODEPAGE // default ansi code page ID (ACP)
+ {
+ get
+ {
+ if (this.iDefaultAnsiCodePage == undef)
+ {
+ this.iDefaultAnsiCodePage = DoGetLocaleInfoInt(LOCALE_IDEFAULTANSICODEPAGE);
+ }
+ return this.iDefaultAnsiCodePage;
+ }
+ }
+
+ internal int IDEFAULTOEMCODEPAGE // default oem code page ID (OCP or OEM)
+ {
+ get
+ {
+ if (this.iDefaultOemCodePage == undef)
+ {
+ this.iDefaultOemCodePage = DoGetLocaleInfoInt(LOCALE_IDEFAULTCODEPAGE);
+ }
+ return this.iDefaultOemCodePage;
+ }
+ }
+
+ internal int IDEFAULTMACCODEPAGE // default macintosh code page
+ {
+ get
+ {
+ if (this.iDefaultMacCodePage == undef)
+ {
+ this.iDefaultMacCodePage = DoGetLocaleInfoInt(LOCALE_IDEFAULTMACCODEPAGE);
+ }
+ return this.iDefaultMacCodePage;
+ }
+ }
+
+ internal int IDEFAULTEBCDICCODEPAGE // default EBCDIC code page
+ {
+ get
+ {
+ if (this.iDefaultEbcdicCodePage == undef)
+ {
+ this.iDefaultEbcdicCodePage = DoGetLocaleInfoInt(LOCALE_IDEFAULTEBCDICCODEPAGE);
+ }
+ return this.iDefaultEbcdicCodePage;
+ }
+ }
+
+ // Obtain locale name from LCID
+ // NOTE: This will get neutral names, unlike the OS API
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern int LocaleNameToLCID(String localeName);
+
+ // These are desktop only, not coreclr
+ // locale ID (0409), including sort information
+ internal int ILANGUAGE
+ {
+ get
+ {
+ if (this.iLanguage == 0)
+ {
+ Contract.Assert(this.sRealName != null, "[CultureData.ILANGUAGE] Expected this.sRealName to be populated by COMNlsInfo::nativeInitCultureData already");
+ this.iLanguage = LocaleNameToLCID(this.sRealName);
+ }
+ return this.iLanguage;
+ }
+ }
+
+ internal bool IsWin32Installed
+ {
+ get { return this.bWin32Installed; }
+ }
+
+ internal bool IsFramework
+ {
+ get { return this.bFramework; }
+ }
+
+ ////////////////////
+ // Derived properties //
+ ////////////////////
+
+ internal bool IsNeutralCulture
+ {
+ get
+ {
+ // NlsInfo::nativeInitCultureData told us if we're neutral or not
+ return this.bNeutral;
+ }
+ }
+
+ internal bool IsInvariantCulture
+ {
+ get
+ {
+ return String.IsNullOrEmpty(this.SNAME);
+ }
+ }
+
+ // Get an instance of our default calendar
+ internal Calendar DefaultCalendar
+ {
+ get
+ {
+ int defaultCalId = DoGetLocaleInfoInt(LOCALE_ICALENDARTYPE);
+ if (defaultCalId == 0)
+ {
+ defaultCalId = this.CalendarIds[0];
+ }
+
+ return CultureInfo.GetCalendarInstance(defaultCalId);
+ }
+ }
+
+ // All of our era names
+ internal String[] EraNames(int calendarId)
+ {
+ Contract.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
+
+ return this.GetCalendar(calendarId).saEraNames;
+ }
+
+ internal String[] AbbrevEraNames(int calendarId)
+ {
+ Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
+
+ return this.GetCalendar(calendarId).saAbbrevEraNames;
+ }
+
+ internal String[] AbbreviatedEnglishEraNames(int calendarId)
+ {
+ Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
+
+ return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
+ }
+
+ // String array DEFAULTS
+ // Note: GetDTFIOverrideValues does the user overrides for these, so we don't have to.
+
+
+ // Time separator (derived from time format)
+ internal String TimeSeparator
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ if (sTimeSeparator == null || UseUserOverride)
+ {
+ string longTimeFormat = ReescapeWin32String(DoGetLocaleInfo(LOCALE_STIMEFORMAT));
+ if (String.IsNullOrEmpty(longTimeFormat))
+ {
+ longTimeFormat = LongTimes[0];
+ }
+
+ // Compute STIME from time format
+ sTimeSeparator = GetTimeSeparator(longTimeFormat);
+ }
+ return sTimeSeparator;
+ }
+ }
+
+ // Date separator (derived from short date format)
+ internal String DateSeparator(int calendarId)
+ {
+ return GetDateSeparator(ShortDates(calendarId)[0]);
+ }
+
+ //////////////////////////////////////
+ // Helper Functions to get derived properties //
+ //////////////////////////////////////
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Unescape a NLS style quote string
+ //
+ // This removes single quotes:
+ // 'fred' -> fred
+ // 'fred -> fred
+ // fred' -> fred
+ // fred's -> freds
+ //
+ // This removes the first \ of escaped characters:
+ // fred\'s -> fred's
+ // a\\b -> a\b
+ // a\b -> ab
+ //
+ // We don't build the stringbuilder unless we find a ' or a \. If we find a ' or a \, we
+ // always build a stringbuilder because we need to remove the ' or \.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ static private String UnescapeNlsString(String str, int start, int end)
+ {
+ Contract.Requires(str != null);
+ Contract.Requires(start >= 0);
+ Contract.Requires(end >= 0);
+ StringBuilder result = null;
+
+ for (int i = start; i < str.Length && i <= end; i++)
+ {
+ switch (str[i])
+ {
+ case '\'':
+ if (result == null)
+ {
+ result = new StringBuilder(str, start, i - start, str.Length);
+ }
+ break;
+ case '\\':
+ if (result == null)
+ {
+ result = new StringBuilder(str, start, i - start, str.Length);
+ }
+ ++i;
+ if (i < str.Length)
+ {
+ result.Append(str[i]);
+ }
+ break;
+ default:
+ if (result != null)
+ {
+ result.Append(str[i]);
+ }
+ break;
+ }
+ }
+
+ if (result == null)
+ return (str.Substring(start, end - start + 1));
+
+ return (result.ToString());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Reescape a Win32 style quote string as a NLS+ style quoted string
+ //
+ // This is also the escaping style used by custom culture data files
+ //
+ // NLS+ uses \ to escape the next character, whether in a quoted string or
+ // not, so we always have to change \ to \\.
+ //
+ // NLS+ uses \' to escape a quote inside a quoted string so we have to change
+ // '' to \' (if inside a quoted string)
+ //
+ // We don't build the stringbuilder unless we find something to change
+ ////////////////////////////////////////////////////////////////////////////
+ static internal String ReescapeWin32String(String str)
+ {
+ // If we don't have data, then don't try anything
+ if (str == null)
+ return null;
+
+ StringBuilder result = null;
+
+ bool inQuote = false;
+ for (int i = 0; i < str.Length; i++)
+ {
+ // Look for quote
+ if (str[i] == '\'')
+ {
+ // Already in quote?
+ if (inQuote)
+ {
+ // See another single quote. Is this '' of 'fred''s' or '''', or is it an ending quote?
+ if (i + 1 < str.Length && str[i + 1] == '\'')
+ {
+ // Found another ', so we have ''. Need to add \' instead.
+ // 1st make sure we have our stringbuilder
+ if (result == null)
+ result = new StringBuilder(str, 0, i, str.Length * 2);
+
+ // Append a \' and keep going (so we don't turn off quote mode)
+ result.Append("\\'");
+ i++;
+ continue;
+ }
+
+ // Turning off quote mode, fall through to add it
+ inQuote = false;
+ }
+ else
+ {
+ // Found beginning quote, fall through to add it
+ inQuote = true;
+ }
+ }
+ // Is there a single \ character?
+ else if (str[i] == '\\')
+ {
+ // Found a \, need to change it to \\
+ // 1st make sure we have our stringbuilder
+ if (result == null)
+ result = new StringBuilder(str, 0, i, str.Length * 2);
+
+ // Append our \\ to the string & continue
+ result.Append("\\\\");
+ continue;
+ }
+
+ // If we have a builder we need to add our character
+ if (result != null)
+ result.Append(str[i]);
+ }
+
+ // Unchanged string? , just return input string
+ if (result == null)
+ return str;
+
+ // String changed, need to use the builder
+ return result.ToString();
+ }
+
+ static internal String[] ReescapeWin32Strings(String[] array)
+ {
+ if (array != null)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ array[i] = ReescapeWin32String(array[i]);
+ }
+ }
+
+ return array;
+ }
+
+ // NOTE: this method is used through reflection by System.Globalization.CultureXmlParser.ReadDateElement()
+ // and breaking changes here will not show up at build time, only at run time.
+ static private String GetTimeSeparator(String format)
+ {
+ // Time format separator (ie: : in 12:39:00)
+ //
+ // We calculate this from the provided time format
+ //
+
+ //
+ // Find the time separator so that we can pretend we know STIME.
+ //
+ return GetSeparator(format, "Hhms");
+ }
+
+ // NOTE: this method is used through reflection by System.Globalization.CultureXmlParser.ReadDateElement()
+ // and breaking changes here will not show up at build time, only at run time.
+ static private String GetDateSeparator(String format)
+ {
+ // Date format separator (ie: / in 9/1/03)
+ //
+ // We calculate this from the provided short date
+ //
+
+ //
+ // Find the date separator so that we can pretend we know SDATE.
+ //
+ return GetSeparator(format, "dyM");
+ }
+
+ private static string GetSeparator(string format, string timeParts)
+ {
+ int index = IndexOfTimePart(format, 0, timeParts);
+
+ if (index != -1)
+ {
+ // Found a time part, find out when it changes
+ char cTimePart = format[index];
+
+ do
+ {
+ index++;
+ } while (index < format.Length && format[index] == cTimePart);
+
+ int separatorStart = index;
+
+ // Now we need to find the end of the separator
+ if (separatorStart < format.Length)
+ {
+ int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
+ if (separatorEnd != -1)
+ {
+ // From [separatorStart, count) is our string, except we need to unescape
+ return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
+ }
+ }
+ }
+
+ return String.Empty;
+ }
+
+ private static int IndexOfTimePart(string format, int startIndex, string timeParts)
+ {
+ Contract.Assert(startIndex >= 0, "startIndex cannot be negative");
+ Contract.Assert(timeParts.IndexOfAny(new char[] { '\'', '\\' }) == -1, "timeParts cannot include quote characters");
+ bool inQuote = false;
+ for (int i = startIndex; i < format.Length; ++i)
+ {
+ // See if we have a time Part
+ if (!inQuote && timeParts.IndexOf(format[i]) != -1)
+ {
+ return i;
+ }
+ switch (format[i])
+ {
+ case '\\':
+ if (i + 1 < format.Length)
+ {
+ ++i;
+ switch (format[i])
+ {
+ case '\'':
+ case '\\':
+ break;
+ default:
+ --i; //backup since we will move over this next
+ break;
+ }
+ }
+ break;
+ case '\'':
+ inQuote = !inQuote;
+ break;
+ }
+ }
+
+ return -1;
+ }
+
+ [System.Security.SecurityCritical]
+ string DoGetLocaleInfo(uint lctype)
+ {
+ Contract.Assert(this.sWindowsName != null, "[CultureData.DoGetLocaleInfo] Expected this.sWindowsName to be populated by COMNlsInfo::nativeInitCultureData already");
+ return DoGetLocaleInfo(this.sWindowsName, lctype);
+ }
+
+ // For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the
+ // "windows" name, which can be specific for downlevel (< windows 7) os's.
+ [System.Security.SecurityCritical] // auto-generated
+ string DoGetLocaleInfo(string localeName, uint lctype)
+ {
+ // Fix lctype if we don't want overrides
+ if (!UseUserOverride)
+ {
+ lctype |= LOCALE_NOUSEROVERRIDE;
+ }
+
+ // Ask OS for data
+ Contract.Assert(localeName != null, "[CultureData.DoGetLocaleInfo] Expected localeName to be not be null");
+ string result = CultureInfo.nativeGetLocaleInfoEx(localeName, lctype);
+ if (result == null)
+ {
+ // Failed, just use empty string
+ result = String.Empty;
+ }
+
+ return result;
+ }
+
+ int DoGetLocaleInfoInt(uint lctype)
+ {
+ // Fix lctype if we don't want overrides
+ if (!UseUserOverride)
+ {
+ lctype |= LOCALE_NOUSEROVERRIDE;
+ }
+
+ // Ask OS for data, note that we presume it returns success, so we have to know that
+ // sWindowsName is valid before calling.
+ Contract.Assert(this.sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected this.sWindowsName to be populated by COMNlsInfo::nativeInitCultureData already");
+ int result = CultureInfo.nativeGetLocaleInfoExInt(this.sWindowsName, lctype);
+
+ return result;
+ }
+
+ String[] DoEnumTimeFormats()
+ {
+ // Note that this gets overrides for us all the time
+ Contract.Assert(this.sWindowsName != null, "[CultureData.DoEnumTimeFormats] Expected this.sWindowsName to be populated by COMNlsInfo::nativeInitCultureData already");
+ String[] result = ReescapeWin32Strings(nativeEnumTimeFormats(this.sWindowsName, 0, UseUserOverride));
+
+ return result;
+ }
+
+ String[] DoEnumShortTimeFormats()
+ {
+ // Note that this gets overrides for us all the time
+ Contract.Assert(this.sWindowsName != null, "[CultureData.DoEnumShortTimeFormats] Expected this.sWindowsName to be populated by COMNlsInfo::nativeInitCultureData already");
+ String[] result = ReescapeWin32Strings(nativeEnumTimeFormats(this.sWindowsName, TIME_NOSECONDS, UseUserOverride));
+
+ return result;
+ }
+
+ /////////////////
+ // Static Helpers //
+ ////////////////
+ internal static bool IsCustomCultureId(int cultureId)
+ {
+ if (cultureId == CultureInfo.LOCALE_CUSTOM_DEFAULT || cultureId == CultureInfo.LOCALE_CUSTOM_UNSPECIFIED)
+ return true;
+
+ return false;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Parameters:
+ // calendarValueOnly Retrieve the values which are affected by the calendar change of DTFI.
+ // This will cause values like longTimePattern not be retrieved since it is
+ // not affected by the Calendar property in DTFI.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ [System.Security.SecurityCritical] // auto-generated
+ internal void GetNFIValues(NumberFormatInfo nfi)
+ {
+ if (this.IsInvariantCulture)
+ {
+ nfi.positiveSign = this.sPositiveSign;
+ nfi.negativeSign = this.sNegativeSign;
+
+ nfi.nativeDigits = this.saNativeDigits;
+ nfi.digitSubstitution = this.iDigitSubstitution;
+
+ nfi.numberGroupSeparator = this.sThousandSeparator;
+ nfi.numberDecimalSeparator = this.sDecimalSeparator;
+ nfi.numberDecimalDigits = this.iDigits;
+ nfi.numberNegativePattern = this.iNegativeNumber;
+
+ nfi.currencySymbol = this.sCurrency;
+ nfi.currencyGroupSeparator = this.sMonetaryThousand;
+ nfi.currencyDecimalSeparator = this.sMonetaryDecimal;
+ nfi.currencyDecimalDigits = this.iCurrencyDigits;
+ nfi.currencyNegativePattern = this.iNegativeCurrency;
+ nfi.currencyPositivePattern = this.iCurrency;
+ }
+ else
+ {
+ //
+ // We don't have information for the following four. All cultures use
+ // the same value of the number formatting values.
+ //
+ // PercentDecimalDigits
+ // PercentDecimalSeparator
+ // PercentGroupSize
+ // PercentGroupSeparator
+ //
+
+ //
+ // Ask native side for our data.
+ //
+ Contract.Assert(this.sWindowsName != null, "[CultureData.GetNFIValues] Expected this.sWindowsName to be populated by COMNlsInfo::nativeInitCultureData already");
+ CultureData.nativeGetNumberFormatInfoValues(this.sWindowsName, nfi, UseUserOverride);
+ }
+
+
+ //
+ // Gather additional data
+ //
+ nfi.numberGroupSizes = this.WAGROUPING;
+ nfi.currencyGroupSizes = this.WAMONGROUPING;
+
+ // prefer the cached value since these do not have user overrides
+ nfi.percentNegativePattern = this.INEGATIVEPERCENT;
+ nfi.percentPositivePattern = this.IPOSITIVEPERCENT;
+ nfi.percentSymbol = this.SPERCENT;
+ nfi.perMilleSymbol = this.SPERMILLE;
+
+ nfi.negativeInfinitySymbol = this.SNEGINFINITY;
+ nfi.positiveInfinitySymbol = this.SPOSINFINITY;
+ nfi.nanSymbol = this.SNAN;
+
+ //
+ // We don't have percent values, so use the number values
+ //
+ nfi.percentDecimalDigits = nfi.numberDecimalDigits;
+ nfi.percentDecimalSeparator = nfi.numberDecimalSeparator;
+ nfi.percentGroupSizes = nfi.numberGroupSizes;
+ nfi.percentGroupSeparator = nfi.numberGroupSeparator;
+
+ //
+ // Clean up a few odd values
+ //
+
+ // Windows usually returns an empty positive sign, but we like it to be "+"
+ if (nfi.positiveSign == null || nfi.positiveSign.Length == 0) nfi.positiveSign = "+";
+
+ //Special case for Italian. The currency decimal separator in the control panel is the empty string. When the user
+ //specifies C4 as the currency format, this results in the number apparently getting multiplied by 10000 because the
+ //decimal point doesn't show up. We'll just workaround this here because our default currency format will never use nfi.
+ if (nfi.currencyDecimalSeparator == null || nfi.currencyDecimalSeparator.Length == 0)
+ {
+ nfi.currencyDecimalSeparator = nfi.numberDecimalSeparator;
+ }
+
+#if !FEATURE_CORECLR
+ if ((932 == this.IDEFAULTANSICODEPAGE) ||
+ (949 == this.IDEFAULTANSICODEPAGE))
+ {
+ // Legacy behavior for cultures that use Japanese/Korean default ANSI code pages
+ // Note that this is a code point, not a character. On Japanese/Korean machines this
+ // will be rendered as their currency symbol, not rendered as a "\"
+ nfi.ansiCurrencySymbol = "\x5c";
+ }
+#endif // !FEATURE_CORECLR
+ }
+
+ static private int ConvertFirstDayOfWeekMonToSun(int iTemp)
+ {
+ // Convert Mon-Sun to Sun-Sat format
+ iTemp++;
+ if (iTemp > 6)
+ {
+ // Wrap Sunday and convert invalid data to Sunday
+ iTemp = 0;
+ }
+ return iTemp;
+ }
+
+ // Helper
+ // This is ONLY used for caching names and shouldn't be used for anything else
+ internal static string AnsiToLower(string testString)
+ {
+ StringBuilder sb = new StringBuilder(testString.Length);
+
+ for (int ich = 0; ich < testString.Length; ich++)
+ {
+ char ch = testString[ich];
+ sb.Append(ch <= 'Z' && ch >= 'A' ? (char)(ch - 'A' + 'a') : ch);
+ }
+
+ return (sb.ToString());
+ }
+
+ // If we get a group from windows, then its in 3;0 format with the 0 backwards
+ // of how NLS+ uses it (ie: if the string has a 0, then the int[] shouldn't and vice versa)
+ // EXCEPT in the case where the list only contains 0 in which NLS and NLS+ have the same meaning.
+ static private int[] ConvertWin32GroupString(String win32Str)
+ {
+ // None of these cases make any sense
+ if (win32Str == null || win32Str.Length == 0)
+ {
+ return (new int[] { 3 });
+ }
+
+ if (win32Str[0] == '0')
+ {
+ return (new int[] { 0 });
+ }
+
+ // Since its in n;n;n;n;n format, we can always get the length quickly
+ int[] values;
+ if (win32Str[win32Str.Length - 1] == '0')
+ {
+ // Trailing 0 gets dropped. 1;0 -> 1
+ values = new int[(win32Str.Length / 2)];
+ }
+ else
+ {
+ // Need extra space for trailing zero 1 -> 1;0
+ values = new int[(win32Str.Length / 2) + 2];
+ values[values.Length - 1] = 0;
+ }
+
+ int i;
+ int j;
+ for (i = 0, j = 0; i < win32Str.Length && j < values.Length; i += 2, j++)
+ {
+ // Note that this # shouldn't ever be zero, 'cause 0 is only at end
+ // But we'll test because its registry that could be anything
+ if (win32Str[i] < '1' || win32Str[i] > '9')
+ return new int[] { 3 };
+
+ values[j] = (int)(win32Str[i] - '0');
+ }
+
+ return (values);
+ }
+
+ // LCTYPES for GetLocaleInfo
+ private const uint LOCALE_NOUSEROVERRIDE = 0x80000000; // do not use user overrides
+ private const uint LOCALE_RETURN_NUMBER = 0x20000000; // return number instead of string
+
+ // Modifier for genitive names
+ private const uint LOCALE_RETURN_GENITIVE_NAMES = 0x10000000; //Flag to return the Genitive forms of month names
+
+ //
+ // The following LCTypes are mutually exclusive in that they may NOT
+ // be used in combination with each other.
+ //
+
+ //
+ // These are the various forms of the name of the locale:
+ //
+ private const uint LOCALE_SLOCALIZEDDISPLAYNAME = 0x00000002; // localized name of locale, eg "German (Germany)" in UI language
+ private const uint LOCALE_SENGLISHDISPLAYNAME = 0x00000072; // Display name (language + country usually) in English, eg "German (Germany)"
+ private const uint LOCALE_SNATIVEDISPLAYNAME = 0x00000073; // Display name in native locale language, eg "Deutsch (Deutschland)
+
+ private const uint LOCALE_SLOCALIZEDLANGUAGENAME = 0x0000006f; // Language Display Name for a language, eg "German" in UI language
+ private const uint LOCALE_SENGLISHLANGUAGENAME = 0x00001001; // English name of language, eg "German"
+ private const uint LOCALE_SNATIVELANGUAGENAME = 0x00000004; // native name of language, eg "Deutsch"
+
+ private const uint LOCALE_SLOCALIZEDCOUNTRYNAME = 0x00000006; // localized name of country, eg "Germany" in UI language
+ private const uint LOCALE_SENGLISHCOUNTRYNAME = 0x00001002; // English name of country, eg "Germany"
+ private const uint LOCALE_SNATIVECOUNTRYNAME = 0x00000008; // native name of country, eg "Deutschland"
+
+
+ // private const uint LOCALE_ILANGUAGE =0x00000001; // language id // Don't use, use NewApis::LocaleNameToLCID instead (GetLocaleInfo doesn't return neutrals)
+
+ // private const uint LOCALE_SLANGUAGE =LOCALE_SLOCALIZEDDISPLAYNAME; // localized name of language (use LOCALE_SLOCALIZEDDISPLAYNAME instead)
+ // private const uint LOCALE_SENGLANGUAGE =LOCALE_SENGLISHLANGUAGENAME; // English name of language (use LOCALE_SENGLISHLANGUAGENAME instead)
+ private const uint LOCALE_SABBREVLANGNAME = 0x00000003; // abbreviated language name
+ // private const uint LOCALE_SNATIVELANGNAME =LOCALE_SNATIVELANGUAGENAME; // native name of language (use LOCALE_SNATIVELANGUAGENAME instead)
+
+ private const uint LOCALE_ICOUNTRY = 0x00000005; // country code
+ // private const uint LOCALE_SCOUNTRY =LOCALE_SLOCALIZEDCOUNTRYNAME; // localized name of country (use LOCALE_SLOCALIZEDCOUNTRYNAME instead)
+ // private const uint LOCALE_SENGCOUNTRY =LOCALE_SENGLISHCOUNTRYNAME; // English name of country (use LOCALE_SENGLISHCOUNTRYNAME instead)
+ private const uint LOCALE_SABBREVCTRYNAME = 0x00000007; // abbreviated country name
+ // private const uint LOCALE_SNATIVECTRYNAME =LOCALE_SNATIVECOUNTRYNAME; // native name of country ( use LOCALE_SNATIVECOUNTRYNAME instead)
+ private const uint LOCALE_IGEOID = 0x0000005B; // geographical location id
+
+ private const uint LOCALE_IDEFAULTLANGUAGE = 0x00000009; // default language id
+ private const uint LOCALE_IDEFAULTCOUNTRY = 0x0000000A; // default country code
+ private const uint LOCALE_IDEFAULTCODEPAGE = 0x0000000B; // default oem code page
+ private const uint LOCALE_IDEFAULTANSICODEPAGE = 0x00001004; // default ansi code page
+ private const uint LOCALE_IDEFAULTMACCODEPAGE = 0x00001011; // default mac code page
+
+ private const uint LOCALE_SLIST = 0x0000000C; // list item separator
+ private const uint LOCALE_IMEASURE = 0x0000000D; // 0 = metric, 1 = US
+
+ private const uint LOCALE_SDECIMAL = 0x0000000E; // decimal separator
+ private const uint LOCALE_STHOUSAND = 0x0000000F; // thousand separator
+ private const uint LOCALE_SGROUPING = 0x00000010; // digit grouping
+ private const uint LOCALE_IDIGITS = 0x00000011; // number of fractional digits
+ private const uint LOCALE_ILZERO = 0x00000012; // leading zeros for decimal
+ private const uint LOCALE_INEGNUMBER = 0x00001010; // negative number mode
+ private const uint LOCALE_SNATIVEDIGITS = 0x00000013; // native digits for 0-9
+
+ private const uint LOCALE_SCURRENCY = 0x00000014; // local monetary symbol
+ private const uint LOCALE_SINTLSYMBOL = 0x00000015; // uintl monetary symbol
+ private const uint LOCALE_SMONDECIMALSEP = 0x00000016; // monetary decimal separator
+ private const uint LOCALE_SMONTHOUSANDSEP = 0x00000017; // monetary thousand separator
+ private const uint LOCALE_SMONGROUPING = 0x00000018; // monetary grouping
+ private const uint LOCALE_ICURRDIGITS = 0x00000019; // # local monetary digits
+ private const uint LOCALE_IINTLCURRDIGITS = 0x0000001A; // # uintl monetary digits
+ private const uint LOCALE_ICURRENCY = 0x0000001B; // positive currency mode
+ private const uint LOCALE_INEGCURR = 0x0000001C; // negative currency mode
+
+ private const uint LOCALE_SDATE = 0x0000001D; // date separator (derived from LOCALE_SSHORTDATE, use that instead)
+ private const uint LOCALE_STIME = 0x0000001E; // time separator (derived from LOCALE_STIMEFORMAT, use that instead)
+ private const uint LOCALE_SSHORTDATE = 0x0000001F; // short date format string
+ private const uint LOCALE_SLONGDATE = 0x00000020; // long date format string
+ private const uint LOCALE_STIMEFORMAT = 0x00001003; // time format string
+ private const uint LOCALE_IDATE = 0x00000021; // short date format ordering (derived from LOCALE_SSHORTDATE, use that instead)
+ private const uint LOCALE_ILDATE = 0x00000022; // long date format ordering (derived from LOCALE_SLONGDATE, use that instead)
+ private const uint LOCALE_ITIME = 0x00000023; // time format specifier (derived from LOCALE_STIMEFORMAT, use that instead)
+ private const uint LOCALE_ITIMEMARKPOSN = 0x00001005; // time marker position (derived from LOCALE_STIMEFORMAT, use that instead)
+ private const uint LOCALE_ICENTURY = 0x00000024; // century format specifier (short date, LOCALE_SSHORTDATE is preferred)
+ private const uint LOCALE_ITLZERO = 0x00000025; // leading zeros in time field (derived from LOCALE_STIMEFORMAT, use that instead)
+ private const uint LOCALE_IDAYLZERO = 0x00000026; // leading zeros in day field (short date, LOCALE_SSHORTDATE is preferred)
+ private const uint LOCALE_IMONLZERO = 0x00000027; // leading zeros in month field (short date, LOCALE_SSHORTDATE is preferred)
+ private const uint LOCALE_S1159 = 0x00000028; // AM designator
+ private const uint LOCALE_S2359 = 0x00000029; // PM designator
+
+ private const uint LOCALE_ICALENDARTYPE = 0x00001009; // type of calendar specifier
+ private const uint LOCALE_IOPTIONALCALENDAR = 0x0000100B; // additional calendar types specifier
+ private const uint LOCALE_IFIRSTDAYOFWEEK = 0x0000100C; // first day of week specifier
+ private const uint LOCALE_IFIRSTWEEKOFYEAR = 0x0000100D; // first week of year specifier
+
+ private const uint LOCALE_SDAYNAME1 = 0x0000002A; // long name for Monday
+ private const uint LOCALE_SDAYNAME2 = 0x0000002B; // long name for Tuesday
+ private const uint LOCALE_SDAYNAME3 = 0x0000002C; // long name for Wednesday
+ private const uint LOCALE_SDAYNAME4 = 0x0000002D; // long name for Thursday
+ private const uint LOCALE_SDAYNAME5 = 0x0000002E; // long name for Friday
+ private const uint LOCALE_SDAYNAME6 = 0x0000002F; // long name for Saturday
+ private const uint LOCALE_SDAYNAME7 = 0x00000030; // long name for Sunday
+ private const uint LOCALE_SABBREVDAYNAME1 = 0x00000031; // abbreviated name for Monday
+ private const uint LOCALE_SABBREVDAYNAME2 = 0x00000032; // abbreviated name for Tuesday
+ private const uint LOCALE_SABBREVDAYNAME3 = 0x00000033; // abbreviated name for Wednesday
+ private const uint LOCALE_SABBREVDAYNAME4 = 0x00000034; // abbreviated name for Thursday
+ private const uint LOCALE_SABBREVDAYNAME5 = 0x00000035; // abbreviated name for Friday
+ private const uint LOCALE_SABBREVDAYNAME6 = 0x00000036; // abbreviated name for Saturday
+ private const uint LOCALE_SABBREVDAYNAME7 = 0x00000037; // abbreviated name for Sunday
+ private const uint LOCALE_SMONTHNAME1 = 0x00000038; // long name for January
+ private const uint LOCALE_SMONTHNAME2 = 0x00000039; // long name for February
+ private const uint LOCALE_SMONTHNAME3 = 0x0000003A; // long name for March
+ private const uint LOCALE_SMONTHNAME4 = 0x0000003B; // long name for April
+ private const uint LOCALE_SMONTHNAME5 = 0x0000003C; // long name for May
+ private const uint LOCALE_SMONTHNAME6 = 0x0000003D; // long name for June
+ private const uint LOCALE_SMONTHNAME7 = 0x0000003E; // long name for July
+ private const uint LOCALE_SMONTHNAME8 = 0x0000003F; // long name for August
+ private const uint LOCALE_SMONTHNAME9 = 0x00000040; // long name for September
+ private const uint LOCALE_SMONTHNAME10 = 0x00000041; // long name for October
+ private const uint LOCALE_SMONTHNAME11 = 0x00000042; // long name for November
+ private const uint LOCALE_SMONTHNAME12 = 0x00000043; // long name for December
+ private const uint LOCALE_SMONTHNAME13 = 0x0000100E; // long name for 13th month (if exists)
+ private const uint LOCALE_SABBREVMONTHNAME1 = 0x00000044; // abbreviated name for January
+ private const uint LOCALE_SABBREVMONTHNAME2 = 0x00000045; // abbreviated name for February
+ private const uint LOCALE_SABBREVMONTHNAME3 = 0x00000046; // abbreviated name for March
+ private const uint LOCALE_SABBREVMONTHNAME4 = 0x00000047; // abbreviated name for April
+ private const uint LOCALE_SABBREVMONTHNAME5 = 0x00000048; // abbreviated name for May
+ private const uint LOCALE_SABBREVMONTHNAME6 = 0x00000049; // abbreviated name for June
+ private const uint LOCALE_SABBREVMONTHNAME7 = 0x0000004A; // abbreviated name for July
+ private const uint LOCALE_SABBREVMONTHNAME8 = 0x0000004B; // abbreviated name for August
+ private const uint LOCALE_SABBREVMONTHNAME9 = 0x0000004C; // abbreviated name for September
+ private const uint LOCALE_SABBREVMONTHNAME10 = 0x0000004D; // abbreviated name for October
+ private const uint LOCALE_SABBREVMONTHNAME11 = 0x0000004E; // abbreviated name for November
+ private const uint LOCALE_SABBREVMONTHNAME12 = 0x0000004F; // abbreviated name for December
+ private const uint LOCALE_SABBREVMONTHNAME13 = 0x0000100F; // abbreviated name for 13th month (if exists)
+
+ private const uint LOCALE_SPOSITIVESIGN = 0x00000050; // positive sign
+ private const uint LOCALE_SNEGATIVESIGN = 0x00000051; // negative sign
+ private const uint LOCALE_IPOSSIGNPOSN = 0x00000052; // positive sign position (derived from INEGCURR)
+ private const uint LOCALE_INEGSIGNPOSN = 0x00000053; // negative sign position (derived from INEGCURR)
+ private const uint LOCALE_IPOSSYMPRECEDES = 0x00000054; // mon sym precedes pos amt (derived from ICURRENCY)
+ private const uint LOCALE_IPOSSEPBYSPACE = 0x00000055; // mon sym sep by space from pos amt (derived from ICURRENCY)
+ private const uint LOCALE_INEGSYMPRECEDES = 0x00000056; // mon sym precedes neg amt (derived from INEGCURR)
+ private const uint LOCALE_INEGSEPBYSPACE = 0x00000057; // mon sym sep by space from neg amt (derived from INEGCURR)
+
+ private const uint LOCALE_FONTSIGNATURE = 0x00000058; // font signature
+ private const uint LOCALE_SISO639LANGNAME = 0x00000059; // ISO abbreviated language name
+ private const uint LOCALE_SISO3166CTRYNAME = 0x0000005A; // ISO abbreviated country name
+
+ private const uint LOCALE_IDEFAULTEBCDICCODEPAGE = 0x00001012; // default ebcdic code page
+ private const uint LOCALE_IPAPERSIZE = 0x0000100A; // 1 = letter, 5 = legal, 8 = a3, 9 = a4
+ private const uint LOCALE_SENGCURRNAME = 0x00001007; // english name of currency
+ private const uint LOCALE_SNATIVECURRNAME = 0x00001008; // native name of currency
+ private const uint LOCALE_SYEARMONTH = 0x00001006; // year month format string
+ private const uint LOCALE_SSORTNAME = 0x00001013; // sort name
+ private const uint LOCALE_IDIGITSUBSTITUTION = 0x00001014; // 0 = context, 1 = none, 2 = national
+
+ private const uint LOCALE_SNAME = 0x0000005c; // locale name (with sort info) (ie: de-DE_phoneb)
+ private const uint LOCALE_SDURATION = 0x0000005d; // time duration format
+ private const uint LOCALE_SKEYBOARDSTOINSTALL = 0x0000005e; // (windows only) keyboards to install
+ private const uint LOCALE_SSHORTESTDAYNAME1 = 0x00000060; // Shortest day name for Monday
+ private const uint LOCALE_SSHORTESTDAYNAME2 = 0x00000061; // Shortest day name for Tuesday
+ private const uint LOCALE_SSHORTESTDAYNAME3 = 0x00000062; // Shortest day name for Wednesday
+ private const uint LOCALE_SSHORTESTDAYNAME4 = 0x00000063; // Shortest day name for Thursday
+ private const uint LOCALE_SSHORTESTDAYNAME5 = 0x00000064; // Shortest day name for Friday
+ private const uint LOCALE_SSHORTESTDAYNAME6 = 0x00000065; // Shortest day name for Saturday
+ private const uint LOCALE_SSHORTESTDAYNAME7 = 0x00000066; // Shortest day name for Sunday
+ private const uint LOCALE_SISO639LANGNAME2 = 0x00000067; // 3 character ISO abbreviated language name
+ private const uint LOCALE_SISO3166CTRYNAME2 = 0x00000068; // 3 character ISO country name
+ private const uint LOCALE_SNAN = 0x00000069; // Not a Number
+ private const uint LOCALE_SPOSINFINITY = 0x0000006a; // + Infinity
+ private const uint LOCALE_SNEGINFINITY = 0x0000006b; // - Infinity
+ private const uint LOCALE_SSCRIPTS = 0x0000006c; // Typical scripts in the locale
+ private const uint LOCALE_SPARENT = 0x0000006d; // Fallback name for resources
+ private const uint LOCALE_SCONSOLEFALLBACKNAME = 0x0000006e; // Fallback name for within the console
+ // private const uint LOCALE_SLANGDISPLAYNAME =LOCALE_SLOCALIZEDLANGUAGENAME; // Language Display Name for a language (use LOCALE_SLOCALIZEDLANGUAGENAME instead)
+
+ // Windows 7 LCTYPES
+ private const uint LOCALE_IREADINGLAYOUT = 0x00000070; // Returns one of the following 4 reading layout values:
+ // 0 - Left to right (eg en-US)
+ // 1 - Right to left (eg arabic locales)
+ // 2 - Vertical top to bottom with columns to the left and also left to right (ja-JP locales)
+ // 3 - Vertical top to bottom with columns proceeding to the right
+ private const uint LOCALE_INEUTRAL = 0x00000071; // Returns 0 for specific cultures, 1 for neutral cultures.
+ private const uint LOCALE_INEGATIVEPERCENT = 0x00000074; // Returns 0-11 for the negative percent format
+ private const uint LOCALE_IPOSITIVEPERCENT = 0x00000075; // Returns 0-3 for the positive percent formatIPOSITIVEPERCENT
+ private const uint LOCALE_SPERCENT = 0x00000076; // Returns the percent symbol
+ private const uint LOCALE_SPERMILLE = 0x00000077; // Returns the permille (U+2030) symbol
+ private const uint LOCALE_SMONTHDAY = 0x00000078; // Returns the preferred month/day format
+ private const uint LOCALE_SSHORTTIME = 0x00000079; // Returns the preferred short time format (ie: no seconds, just h:mm)
+ private const uint LOCALE_SOPENTYPELANGUAGETAG = 0x0000007a; // Open type language tag, eg: "latn" or "dflt"
+ private const uint LOCALE_SSORTLOCALE = 0x0000007b; // Name of locale to use for sorting/collation/casing behavior.
+
+ // Time formats enumerations
+ internal const uint TIME_NOSECONDS = 0x00000002; // Don't use seconds (get short time format for enumtimeformats on win7+)
+
+ // Get our initial minimal culture data (name, parent, etc.)
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern bool nativeInitCultureData(CultureData cultureData);
+
+ // Grab the NumberFormatInfo data
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern bool nativeGetNumberFormatInfoValues(String localeName, NumberFormatInfo nfi, bool useUserOverride);
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private static extern String[] nativeEnumTimeFormats(String localeName, uint dwFlags, bool useUserOverride);
+
+ [System.Security.SecurityCritical] // auto-generated
+ [SuppressUnmanagedCodeSecurityAttribute()]
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ internal static extern int nativeEnumCultureNames(int cultureTypes, ObjectHandleOnStack retStringArray);
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/CultureInfo.cs b/src/mscorlib/src/System/Globalization/CultureInfo.cs
new file mode 100644
index 0000000000..d620d2dc24
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CultureInfo.cs
@@ -0,0 +1,2023 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+//
+// Purpose: This class represents the software preferences of a particular
+// culture or community. It includes information such as the
+// language, writing system, and a calendar used by the culture
+// as well as methods for common operations such as printing
+// dates and sorting strings.
+//
+//
+//
+// !!!! NOTE WHEN CHANGING THIS CLASS !!!!
+//
+// If adding or removing members to this class, please update CultureInfoBaseObject
+// in ndp/clr/src/vm/object.h. Note, the "actual" layout of the class may be
+// different than the order in which members are declared. For instance, all
+// reference types will come first in the class before value types (like ints, bools, etc)
+// regardless of the order in which they are declared. The best way to see the
+// actual order of the class is to do a !dumpobj on an instance of the managed
+// object inside of the debugger.
+//
+////////////////////////////////////////////////////////////////////////////
+
+namespace System.Globalization {
+ using System;
+ using System.Security;
+ using System.Threading;
+ using System.Collections;
+ using System.Runtime;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
+ using System.Runtime.Serialization;
+ using System.Runtime.Versioning;
+ using System.Security.Permissions;
+ using System.Reflection;
+ using Microsoft.Win32;
+ using System.Diagnostics.Contracts;
+ using System.Resources;
+
+ [Serializable]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public partial class CultureInfo : ICloneable, IFormatProvider {
+ //--------------------------------------------------------------------//
+ // Internal Information //
+ //--------------------------------------------------------------------//
+
+ //--------------------------------------------------------------------//
+ // Data members to be serialized:
+ //--------------------------------------------------------------------//
+
+ // We use an RFC4646 type string to construct CultureInfo.
+ // This string is stored in m_name and is authoritative.
+ // We use the m_cultureData to get the data for our object
+
+ // WARNING
+ // WARNING: All member fields declared here must also be in ndp/clr/src/vm/object.h
+ // WARNING: They aren't really private because object.h can access them, but other C# stuff cannot
+ // WARNING: The type loader will rearrange class member offsets so the mscorwks!CultureInfoBaseObject
+ // WARNING: must be manually structured to match the true loaded class layout
+ // WARNING
+ internal bool m_isReadOnly;
+ internal CompareInfo compareInfo;
+ internal TextInfo textInfo;
+ // Not serialized for now since we only build it privately for use in the CARIB (so rebuilding is OK)
+#if !FEATURE_CORECLR
+ [NonSerialized]internal RegionInfo regionInfo;
+#endif
+ internal NumberFormatInfo numInfo;
+ internal DateTimeFormatInfo dateTimeInfo;
+ internal Calendar calendar;
+ [OptionalField(VersionAdded = 1)]
+ internal int m_dataItem; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey/Everett)
+ [OptionalField(VersionAdded = 1)]
+ internal int cultureID = 0x007f; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey/Everett)
+ //
+ // The CultureData instance that we are going to read data from.
+ // For supported culture, this will be the CultureData instance that read data from mscorlib assembly.
+ // For customized culture, this will be the CultureData instance that read data from user customized culture binary file.
+ //
+ [NonSerialized]internal CultureData m_cultureData;
+
+ [NonSerialized]internal bool m_isInherited;
+#if FEATURE_LEAK_CULTURE_INFO
+ [NonSerialized]private bool m_isSafeCrossDomain;
+ [NonSerialized]private int m_createdDomainID;
+#endif // !FEATURE_CORECLR
+#if !FEATURE_CORECLR
+ [NonSerialized]private CultureInfo m_consoleFallbackCulture;
+#endif // !FEATURE_CORECLR
+
+ // Names are confusing. Here are 3 names we have:
+ //
+ // new CultureInfo() m_name m_nonSortName m_sortName
+ // en-US en-US en-US en-US
+ // de-de_phoneb de-DE_phoneb de-DE de-DE_phoneb
+ // fj-fj (custom) fj-FJ fj-FJ en-US (if specified sort is en-US)
+ // en en
+ //
+ // Note that in Silverlight we ask the OS for the text and sort behavior, so the
+ // textinfo and compareinfo names are the same as the name
+
+ // Note that the name used to be serialized for Everett; it is now serialized
+ // because alernate sorts can have alternate names.
+ // This has a de-DE, de-DE_phoneb or fj-FJ style name
+ internal string m_name;
+
+ // This will hold the non sorting name to be returned from CultureInfo.Name property.
+ // This has a de-DE style name even for de-DE_phoneb type cultures
+ [NonSerialized]private string m_nonSortName;
+
+ // This will hold the sorting name to be returned from CultureInfo.SortName property.
+ // This might be completely unrelated to the culture name if a custom culture. Ie en-US for fj-FJ.
+ // Otherwise its the sort name, ie: de-DE or de-DE_phoneb
+ [NonSerialized]private string m_sortName;
+
+
+ //--------------------------------------------------------------------//
+ //
+ // Static data members
+ //
+ //--------------------------------------------------------------------//
+
+ //Get the current user default culture. This one is almost always used, so we create it by default.
+ private static volatile CultureInfo s_userDefaultCulture;
+
+ //
+ // All of the following will be created on demand.
+ //
+
+ //The Invariant culture;
+ private static volatile CultureInfo s_InvariantCultureInfo;
+
+ //The culture used in the user interface. This is mostly used to load correct localized resources.
+ private static volatile CultureInfo s_userDefaultUICulture;
+
+ //This is the UI culture used to install the OS.
+ private static volatile CultureInfo s_InstalledUICultureInfo;
+
+ //These are defaults that we use if a thread has not opted into having an explicit culture
+ private static volatile CultureInfo s_DefaultThreadCurrentUICulture;
+ private static volatile CultureInfo s_DefaultThreadCurrentCulture;
+
+ //This is a cache of all previously created cultures. Valid keys are LCIDs or the name. We use two hashtables to track them,
+ // depending on how they are called.
+ private static volatile Hashtable s_LcidCachedCultures;
+ private static volatile Hashtable s_NameCachedCultures;
+
+#if FEATURE_APPX
+ // When running under AppX, we use this to get some information about the language list
+ [SecurityCritical]
+ private static volatile WindowsRuntimeResourceManagerBase s_WindowsRuntimeResourceManager;
+
+ [ThreadStatic]
+ private static bool ts_IsDoingAppXCultureInfoLookup;
+#endif
+
+ //The parent culture.
+ [NonSerialized]private CultureInfo m_parent;
+
+ // LOCALE constants of interest to us internally and privately for LCID functions
+ // (ie: avoid using these and use names if possible)
+ internal const int LOCALE_NEUTRAL = 0x0000;
+ private const int LOCALE_USER_DEFAULT = 0x0400;
+ private const int LOCALE_SYSTEM_DEFAULT = 0x0800;
+ internal const int LOCALE_CUSTOM_DEFAULT = 0x0c00;
+ internal const int LOCALE_CUSTOM_UNSPECIFIED = 0x1000;
+ internal const int LOCALE_INVARIANT = 0x007F;
+ private const int LOCALE_TRADITIONAL_SPANISH = 0x040a;
+
+ //
+ // The CultureData instance that reads the data provided by our CultureData class.
+ //
+ //Using a field initializer rather than a static constructor so that the whole class can be lazy
+ //init.
+ private static readonly bool init = Init();
+ private static bool Init()
+ {
+
+ if (s_InvariantCultureInfo == null)
+ {
+ CultureInfo temp = new CultureInfo("", false);
+ temp.m_isReadOnly = true;
+ s_InvariantCultureInfo = temp;
+ }
+ // First we set it to Invariant in case someone needs it before we're done finding it.
+ // For example, if we throw an exception in InitUserDefaultCulture, we will still need an valid
+ // s_userDefaultCulture to be used in Thread.CurrentCulture.
+ s_userDefaultCulture = s_userDefaultUICulture = s_InvariantCultureInfo;
+
+ s_userDefaultCulture = InitUserDefaultCulture();
+ s_userDefaultUICulture = InitUserDefaultUICulture();
+ return true;
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ static CultureInfo InitUserDefaultCulture()
+ {
+ String strDefault = GetDefaultLocaleName(LOCALE_USER_DEFAULT);
+ if (strDefault == null)
+ {
+ strDefault = GetDefaultLocaleName(LOCALE_SYSTEM_DEFAULT);
+
+ if (strDefault == null)
+ {
+ // If system default doesn't work, keep using the invariant
+ return (CultureInfo.InvariantCulture);
+ }
+ }
+ CultureInfo temp = GetCultureByName(strDefault, true);
+
+ temp.m_isReadOnly = true;
+
+ return (temp);
+ }
+
+ static CultureInfo InitUserDefaultUICulture()
+ {
+ String strDefault = GetUserDefaultUILanguage();
+
+ // In most of cases, UserDefaultCulture == UserDefaultUICulture, so we should use the same instance if possible.
+ if (strDefault == UserDefaultCulture.Name)
+ {
+ return (UserDefaultCulture);
+ }
+
+ CultureInfo temp = GetCultureByName( strDefault, true);
+
+ if (temp == null)
+ {
+ return (CultureInfo.InvariantCulture);
+ }
+
+ temp.m_isReadOnly = true;
+
+ return (temp);
+ }
+
+#if FEATURE_APPX
+ [SecuritySafeCritical]
+ internal static CultureInfo GetCultureInfoForUserPreferredLanguageInAppX()
+ {
+ // If a call to GetCultureInfoForUserPreferredLanguageInAppX() generated a recursive
+ // call to itself, return null, since we don't want to stack overflow. For example,
+ // this can happen if some code in this method ends up calling CultureInfo.CurrentCulture
+ // (which is common on check'd build because of BCLDebug logging which calls Int32.ToString()).
+ // In this case, returning null will mean CultureInfo.CurrentCulture gets the default Win32
+ // value, which should be fine.
+ if(ts_IsDoingAppXCultureInfoLookup)
+ {
+ return null;
+ }
+
+ // If running within a compilation process (mscorsvw.exe, for example), it is illegal to
+ // load any non-mscorlib assembly for execution. Since WindowsRuntimeResourceManager lives
+ // in System.Runtime.WindowsRuntime, caller will need to fall back to default Win32 value,
+ // which should be fine because we should only ever need to access FX resources during NGEN.
+ // FX resources are always loaded from satellite assemblies - even in AppX processes (see the
+ // comments in code:System.Resources.ResourceManager.SetAppXConfiguration for more details).
+ if (AppDomain.IsAppXNGen)
+ {
+ return null;
+ }
+
+ CultureInfo toReturn = null;
+
+ try
+ {
+ ts_IsDoingAppXCultureInfoLookup = true;
+
+ if(s_WindowsRuntimeResourceManager == null)
+ {
+ s_WindowsRuntimeResourceManager = ResourceManager.GetWinRTResourceManager();
+ }
+
+ toReturn = s_WindowsRuntimeResourceManager.GlobalResourceContextBestFitCultureInfo;
+ }
+ finally
+ {
+ ts_IsDoingAppXCultureInfoLookup = false;
+ }
+
+ return toReturn;
+ }
+
+ [SecuritySafeCritical]
+ internal static bool SetCultureInfoForUserPreferredLanguageInAppX(CultureInfo ci)
+ {
+ // If running within a compilation process (mscorsvw.exe, for example), it is illegal to
+ // load any non-mscorlib assembly for execution. Since WindowsRuntimeResourceManager lives
+ // in System.Runtime.WindowsRuntime, caller will need to fall back to default Win32 value,
+ // which should be fine because we should only ever need to access FX resources during NGEN.
+ // FX resources are always loaded from satellite assemblies - even in AppX processes (see the
+ // comments in code:System.Resources.ResourceManager.SetAppXConfiguration for more details).
+ if (AppDomain.IsAppXNGen)
+ {
+ return false;
+ }
+
+ if (s_WindowsRuntimeResourceManager == null)
+ {
+ s_WindowsRuntimeResourceManager = ResourceManager.GetWinRTResourceManager();
+ }
+
+ return s_WindowsRuntimeResourceManager.SetGlobalResourceContextDefaultCulture(ci);
+ }
+#endif
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CultureInfo Constructors
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public CultureInfo(String name) : this(name, true) {
+ }
+
+
+ public CultureInfo(String name, bool useUserOverride) {
+ if (name==null) {
+ throw new ArgumentNullException("name",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ // Get our data providing record
+ this.m_cultureData = CultureData.GetCultureData(name, useUserOverride);
+
+ if (this.m_cultureData == null) {
+ throw new CultureNotFoundException("name", name, Environment.GetResourceString("Argument_CultureNotSupported"));
+ }
+
+ this.m_name = this.m_cultureData.CultureName;
+ this.m_isInherited = (this.GetType() != typeof(System.Globalization.CultureInfo));
+ }
+
+
+#if FEATURE_USE_LCID
+ public CultureInfo(int culture) : this(culture, true) {
+ }
+
+ public CultureInfo(int culture, bool useUserOverride) {
+ // We don't check for other invalid LCIDS here...
+ if (culture < 0) {
+ throw new ArgumentOutOfRangeException("culture",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+ Contract.EndContractBlock();
+
+ InitializeFromCultureId(culture, useUserOverride);
+ }
+
+ private void InitializeFromCultureId(int culture, bool useUserOverride)
+ {
+ switch (culture)
+ {
+ case LOCALE_CUSTOM_DEFAULT:
+ case LOCALE_SYSTEM_DEFAULT:
+ case LOCALE_NEUTRAL:
+ case LOCALE_USER_DEFAULT:
+ case LOCALE_CUSTOM_UNSPECIFIED:
+ // Can't support unknown custom cultures and we do not support neutral or
+ // non-custom user locales.
+ throw new CultureNotFoundException(
+ "culture", culture, Environment.GetResourceString("Argument_CultureNotSupported"));
+
+ default:
+ // Now see if this LCID is supported in the system default CultureData table.
+ this.m_cultureData = CultureData.GetCultureData(culture, useUserOverride);
+ break;
+ }
+ this.m_isInherited = (this.GetType() != typeof(System.Globalization.CultureInfo));
+ this.m_name = this.m_cultureData.CultureName;
+ }
+#endif // FEATURE_USE_LCID
+
+ //
+ // CheckDomainSafetyObject throw if the object is customized object which cannot be attached to
+ // other object (like CultureInfo or DateTimeFormatInfo).
+ //
+
+ internal static void CheckDomainSafetyObject(Object obj, Object container)
+ {
+ if (obj.GetType().Assembly != typeof(System.Globalization.CultureInfo).Assembly) {
+
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("InvalidOperation_SubclassedObject"),
+ obj.GetType(),
+ container.GetType()));
+ }
+ Contract.EndContractBlock();
+ }
+
+#region Serialization
+ // We need to store the override from the culture data record.
+ private bool m_useUserOverride;
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+#if FEATURE_USE_LCID
+ // Whidbey+ should remember our name
+ // but v1 and v1.1 did not store name -- only lcid
+ // Whidbey did not store actual alternate sort name in m_name
+ // like we do in v4 so we can't use name for alternate sort
+ // e.g. for es-ES_tradnl: v2 puts es-ES in m_name; v4 puts es-ES_tradnl
+ if (m_name == null || IsAlternateSortLcid(cultureID))
+ {
+ Contract.Assert(cultureID >=0, "[CultureInfo.OnDeserialized] cultureID >= 0");
+ InitializeFromCultureId(cultureID, m_useUserOverride);
+ }
+ else
+ {
+#endif
+ Contract.Assert(m_name != null, "[CultureInfo.OnDeserialized] m_name != null");
+
+ this.m_cultureData = CultureData.GetCultureData(m_name, m_useUserOverride);
+ if (this.m_cultureData == null)
+ throw new CultureNotFoundException(
+ "m_name", m_name, Environment.GetResourceString("Argument_CultureNotSupported"));
+
+#if FEATURE_USE_LCID
+ }
+#endif
+ m_isInherited = (this.GetType() != typeof(System.Globalization.CultureInfo));
+
+ // in case we have non customized CultureInfo object we shouldn't allow any customized object
+ // to be attached to it for cross app domain safety.
+ if (this.GetType().Assembly == typeof(System.Globalization.CultureInfo).Assembly)
+ {
+ if (textInfo != null)
+ {
+ CheckDomainSafetyObject(textInfo, this);
+ }
+
+ if (compareInfo != null)
+ {
+ CheckDomainSafetyObject(compareInfo, this);
+ }
+ }
+ }
+
+#if FEATURE_USE_LCID
+ // A locale ID is a 32 bit value which is the combination of a
+ // language ID, a sort ID, and a reserved area. The bits are
+ // allocated as follows:
+ //
+ // +------------------------+-------+--------------------------------+
+ // | Reserved |Sort ID| Language ID |
+ // +------------------------+-------+--------------------------------+
+ // 31 20 19 16 15 0 bit
+ private const int LOCALE_SORTID_MASK = 0x000f0000;
+
+ static private bool IsAlternateSortLcid(int lcid)
+ {
+ if(lcid == LOCALE_TRADITIONAL_SPANISH)
+ {
+ return true;
+ }
+
+ return (lcid & LOCALE_SORTID_MASK) != 0;
+ }
+#endif
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+ this.m_name = this.m_cultureData.CultureName;
+ this.m_useUserOverride = this.m_cultureData.UseUserOverride;
+#if FEATURE_USE_LCID
+ // for compatibility with v2 serialize cultureID
+ this.cultureID = this.m_cultureData.ILANGUAGE;
+#endif
+ }
+#endregion Serialization
+
+#if FEATURE_LEAK_CULTURE_INFO
+ // Is it safe to send this CultureInfo as an instance member of a Thread cross AppDomain boundaries?
+ // For Silverlight, the answer is always no.
+ internal bool IsSafeCrossDomain {
+ get {
+ Contract.Assert(m_createdDomainID != 0, "[CultureInfo.IsSafeCrossDomain] m_createdDomainID != 0");
+ return m_isSafeCrossDomain;
+ }
+ }
+
+ internal int CreatedDomainID {
+ get {
+ Contract.Assert(m_createdDomainID != 0, "[CultureInfo.CreatedDomain] m_createdDomainID != 0");
+ return m_createdDomainID;
+ }
+ }
+
+ internal void StartCrossDomainTracking() {
+
+ // If we have decided about cross domain safety of this instance, we are done
+ if (m_createdDomainID != 0)
+ return;
+
+ // If FEATURE_LEAK_CULTURE_INFO isn't enabled, we never want to pass
+ // CultureInfo as an instance member of a Thread.
+ if (CanSendCrossDomain())
+ {
+ m_isSafeCrossDomain = true;
+ }
+
+ // m_createdDomainID has to be assigned last. We use it to signal that we have
+ // completed the check.
+ System.Threading.Thread.MemoryBarrier();
+ m_createdDomainID = Thread.GetDomainID();
+ }
+#endif // FEATURE_LEAK_CULTURE_INFO
+
+ // Is it safe to pass the CultureInfo cross AppDomain boundaries, not necessarily as an instance
+ // member of Thread. This is different from IsSafeCrossDomain, which implies passing the CultureInfo
+ // as a Thread instance member.
+ internal bool CanSendCrossDomain()
+ {
+ bool isSafe = false;
+ if (this.GetType() == typeof(System.Globalization.CultureInfo))
+ {
+ isSafe = true;
+ }
+ return isSafe;
+ }
+
+ // Constructor called by SQL Server's special munged culture - creates a culture with
+ // a TextInfo and CompareInfo that come from a supplied alternate source. This object
+ // is ALWAYS read-only.
+ // Note that we really cannot use an LCID version of this override as the cached
+ // name we create for it has to include both names, and the logic for this is in
+ // the GetCultureInfo override *only*.
+ internal CultureInfo(String cultureName, String textAndCompareCultureName)
+ {
+ if (cultureName==null) {
+ throw new ArgumentNullException("cultureName",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ this.m_cultureData = CultureData.GetCultureData(cultureName, false);
+ if (this.m_cultureData == null)
+ throw new CultureNotFoundException(
+ "cultureName", cultureName, Environment.GetResourceString("Argument_CultureNotSupported"));
+
+ this.m_name = this.m_cultureData.CultureName;
+
+ CultureInfo altCulture = GetCultureInfo(textAndCompareCultureName);
+ this.compareInfo = altCulture.CompareInfo;
+ this.textInfo = altCulture.TextInfo;
+ }
+
+ // We do this to try to return the system UI language and the default user languages
+ // The callers should have a fallback if this fails (like Invariant)
+ private static CultureInfo GetCultureByName(String name, bool userOverride)
+ {
+ // Try to get our culture
+ try
+ {
+ return userOverride ? new CultureInfo(name) : CultureInfo.GetCultureInfo(name);
+ }
+ catch (ArgumentException)
+ {
+ }
+
+ return null;
+ }
+
+ //
+ // Return a specific culture. A tad irrelevent now since we always return valid data
+ // for neutral locales.
+ //
+ // Note that there's interesting behavior that tries to find a smaller name, ala RFC4647,
+ // if we can't find a bigger name. That doesn't help with things like "zh" though, so
+ // the approach is of questionable value
+ //
+ public static CultureInfo CreateSpecificCulture(String name) {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+
+ CultureInfo culture;
+
+ try {
+ culture = new CultureInfo(name);
+ } catch(ArgumentException) {
+ // When CultureInfo throws this exception, it may be because someone passed the form
+ // like "az-az" because it came out of an http accept lang. We should try a little
+ // parsing to perhaps fall back to "az" here and use *it* to create the neutral.
+
+ int idx;
+
+ culture = null;
+ for(idx = 0; idx < name.Length; idx++) {
+ if('-' == name[idx]) {
+ try {
+ culture = new CultureInfo(name.Substring(0, idx));
+ break;
+ } catch(ArgumentException) {
+ // throw the original exception so the name in the string will be right
+ throw;
+ }
+ }
+ }
+
+ if(null == culture) {
+ // nothing to save here; throw the original exception
+ throw;
+ }
+ }
+
+ //In the most common case, they've given us a specific culture, so we'll just return that.
+ if (!(culture.IsNeutralCulture)) {
+ return culture;
+ }
+
+ return (new CultureInfo(culture.m_cultureData.SSPECIFICCULTURE));
+ }
+
+ internal static bool VerifyCultureName(String cultureName, bool throwException)
+ {
+ // This function is used by ResourceManager.GetResourceFileName().
+ // ResourceManager searches for resource using CultureInfo.Name,
+ // so we should check against CultureInfo.Name.
+
+ for (int i=0; i<cultureName.Length; i++) {
+ char c = cultureName[i];
+
+ if (Char.IsLetterOrDigit(c) || c=='-' || c=='_') {
+ continue;
+ }
+ if (throwException) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidResourceCultureName", cultureName));
+ }
+ return false;
+ }
+ return true;
+
+ }
+
+ internal static bool VerifyCultureName(CultureInfo culture, bool throwException) {
+ Contract.Assert(culture!=null, "[CultureInfo.VerifyCultureName]culture!=null");
+
+ //If we have an instance of one of our CultureInfos, the user can't have changed the
+ //name and we know that all names are valid in files.
+ if (!culture.m_isInherited) {
+ return true;
+ }
+
+ return VerifyCultureName(culture.Name, throwException);
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CurrentCulture
+ //
+ // This instance provides methods based on the current user settings.
+ // These settings are volatile and may change over the lifetime of the
+ // thread.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public static CultureInfo CurrentCulture
+ {
+ get {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+
+#if !FEATURE_CORECLR
+ return Thread.CurrentThread.CurrentCulture;
+#else
+ // In the case of CoreCLR, Thread.m_CurrentCulture and
+ // Thread.m_CurrentUICulture are thread static so as not to let
+ // CultureInfo objects leak across AppDomain boundaries. The
+ // fact that these fields are thread static introduces overhead
+ // in accessing them (through Thread.CurrentCulture). There is
+ // also overhead in accessing Thread.CurrentThread. In this
+ // case, we can avoid the overhead of Thread.CurrentThread
+ // because these fields are thread static, and so do not
+ // require a Thread instance to be accessed.
+#if FEATURE_APPX
+ if(AppDomain.IsAppXModel()) {
+ CultureInfo culture = GetCultureInfoForUserPreferredLanguageInAppX();
+ if (culture != null)
+ return culture;
+ }
+#endif
+ return Thread.m_CurrentCulture ??
+ s_DefaultThreadCurrentCulture ??
+ s_userDefaultCulture ??
+ UserDefaultCulture;
+#endif
+ }
+
+ set {
+#if FEATURE_APPX
+ if (value == null) {
+ throw new ArgumentNullException("value");
+ }
+
+ if (AppDomain.IsAppXModel()) {
+ if (SetCultureInfoForUserPreferredLanguageInAppX(value)) {
+ // successfully set the culture, otherwise fallback to legacy path
+ return;
+ }
+ }
+#endif
+ Thread.CurrentThread.CurrentCulture = value;
+ }
+ }
+
+ //
+ // This is the equivalence of the Win32 GetUserDefaultLCID()
+ //
+ internal static CultureInfo UserDefaultCulture {
+ get
+ {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+
+ CultureInfo temp = s_userDefaultCulture;
+ if (temp == null)
+ {
+ //
+ // setting the s_userDefaultCulture with invariant culture before intializing it is a protection
+ // against recursion problem just in case if somebody called CurrentCulture from the CultureInfo
+ // creation path. the recursion can happen if the current user culture is a replaced custom culture.
+ //
+
+ s_userDefaultCulture = CultureInfo.InvariantCulture;
+ temp = InitUserDefaultCulture();
+ s_userDefaultCulture = temp;
+ }
+ return (temp);
+ }
+ }
+
+ //
+ // This is the equivalence of the Win32 GetUserDefaultUILanguage()
+ //
+ internal static CultureInfo UserDefaultUICulture {
+ get {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+
+ CultureInfo temp = s_userDefaultUICulture;
+ if (temp == null)
+ {
+ //
+ // setting the s_userDefaultCulture with invariant culture before intializing it is a protection
+ // against recursion problem just in case if somebody called CurrentUICulture from the CultureInfo
+ // creation path. the recursion can happen if the current user culture is a replaced custom culture.
+ //
+
+ s_userDefaultUICulture = CultureInfo.InvariantCulture;
+
+ temp = InitUserDefaultUICulture();
+ s_userDefaultUICulture = temp;
+ }
+ return (temp);
+ }
+ }
+
+
+ public static CultureInfo CurrentUICulture {
+ get {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+
+#if !FEATURE_CORECLR
+ return Thread.CurrentThread.CurrentUICulture;
+#else
+ // In the case of CoreCLR, Thread.m_CurrentCulture and
+ // Thread.m_CurrentUICulture are thread static so as not to let
+ // CultureInfo objects leak across AppDomain boundaries. The
+ // fact that these fields are thread static introduces overhead
+ // in accessing them (through Thread.CurrentCulture). There is
+ // also overhead in accessing Thread.CurrentThread. In this
+ // case, we can avoid the overhead of Thread.CurrentThread
+ // because these fields are thread static, and so do not
+ // require a Thread instance to be accessed.
+#if FEATURE_APPX
+ if(AppDomain.IsAppXModel()) {
+ CultureInfo culture = GetCultureInfoForUserPreferredLanguageInAppX();
+ if (culture != null)
+ return culture;
+ }
+#endif
+ return Thread.m_CurrentUICulture ??
+ s_DefaultThreadCurrentUICulture ??
+ s_userDefaultUICulture ??
+ UserDefaultUICulture;
+#endif
+ }
+
+ set {
+#if FEATURE_APPX
+ if (value == null) {
+ throw new ArgumentNullException("value");
+ }
+
+ if (AppDomain.IsAppXModel()) {
+ if (SetCultureInfoForUserPreferredLanguageInAppX(value)) {
+ // successfully set the culture, otherwise fallback to legacy path
+ return;
+ }
+ }
+#endif
+ Thread.CurrentThread.CurrentUICulture = value;
+ }
+ }
+
+
+ //
+ // This is the equivalence of the Win32 GetSystemDefaultUILanguage()
+ //
+ public static CultureInfo InstalledUICulture {
+ get {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+
+ CultureInfo temp = s_InstalledUICultureInfo;
+ if (temp == null) {
+ String strDefault = GetSystemDefaultUILanguage();
+ temp = GetCultureByName(strDefault, true);
+
+ if (temp == null)
+ {
+ temp = InvariantCulture;
+ }
+
+ temp.m_isReadOnly = true;
+ s_InstalledUICultureInfo = temp;
+ }
+ return (temp);
+ }
+ }
+
+ public static CultureInfo DefaultThreadCurrentCulture {
+ get {
+ return s_DefaultThreadCurrentCulture;
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+#pragma warning disable 618
+ [SecurityPermission(SecurityAction.Demand, ControlThread = true)]
+#pragma warning restore 618
+ set {
+
+ // If you add pre-conditions to this method, check to see if you also need to
+ // add them to Thread.CurrentCulture.set.
+
+ s_DefaultThreadCurrentCulture = value;
+ }
+ }
+
+ public static CultureInfo DefaultThreadCurrentUICulture {
+ get {
+ return s_DefaultThreadCurrentUICulture;
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+#pragma warning disable 618
+ [SecurityPermission(SecurityAction.Demand, ControlThread = true)]
+#pragma warning restore 618
+ set {
+
+ //If they're trying to use a Culture with a name that we can't use in resource lookup,
+ //don't even let them set it on the thread.
+
+ // If you add more pre-conditions to this method, check to see if you also need to
+ // add them to Thread.CurrentUICulture.set.
+
+ if (value != null)
+ {
+ CultureInfo.VerifyCultureName(value, true);
+ }
+
+ s_DefaultThreadCurrentUICulture = value;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // InvariantCulture
+ //
+ // This instance provides methods, for example for casing and sorting,
+ // that are independent of the system and current user settings. It
+ // should be used only by processes such as some system services that
+ // require such invariant results (eg. file systems). In general,
+ // the results are not linguistically correct and do not match any
+ // culture info.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public static CultureInfo InvariantCulture {
+ [Pure]
+ get {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+ return (s_InvariantCultureInfo);
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Parent
+ //
+ // Return the parent CultureInfo for the current instance.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ public virtual CultureInfo Parent
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+
+ if (null == m_parent)
+ {
+ try
+ {
+ string parentName = this.m_cultureData.SPARENT;
+
+ if (String.IsNullOrEmpty(parentName))
+ {
+ m_parent = InvariantCulture;
+ }
+ else
+ {
+ m_parent = new CultureInfo(parentName, this.m_cultureData.UseUserOverride);
+ }
+ }
+ catch (ArgumentException)
+ {
+ // For whatever reason our IPARENT or SPARENT wasn't correct, so use invariant
+ // We can't allow ourselves to fail. In case of custom cultures the parent of the
+ // current custom culture isn't installed.
+ m_parent = InvariantCulture;
+ }
+ }
+ return m_parent;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // LCID
+ //
+ // Returns a properly formed culture identifier for the current
+ // culture info.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+#if FEATURE_USE_LCID
+ public virtual int LCID {
+ get {
+ return (this.m_cultureData.ILANGUAGE);
+ }
+ }
+#endif
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // BaseInputLanguage
+ //
+ // Essentially an LCID, though one that may be different than LCID in the case
+ // of a customized culture (LCID == LOCALE_CUSTOM_UNSPECIFIED).
+ //
+ ////////////////////////////////////////////////////////////////////////
+#if FEATURE_USE_LCID
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual int KeyboardLayoutId
+ {
+ get
+ {
+ int keyId = this.m_cultureData.IINPUTLANGUAGEHANDLE;
+
+ // Not a customized culture, return the default Keyboard layout ID, which is the same as the language ID.
+ return (keyId);
+ }
+ }
+#endif
+
+ public static CultureInfo[] GetCultures(CultureTypes types) {
+ Contract.Ensures(Contract.Result<CultureInfo[]>() != null);
+ // internally we treat UserCustomCultures as Supplementals but v2
+ // treats as Supplementals and Replacements
+ if((types & CultureTypes.UserCustomCulture) == CultureTypes.UserCustomCulture)
+ {
+ types |= CultureTypes.ReplacementCultures;
+ }
+ return (CultureData.GetCultures(types));
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Name
+ //
+ // Returns the full name of the CultureInfo. The name is in format like
+ // "en-US" This version does NOT include sort information in the name.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String Name {
+ get {
+ Contract.Ensures(Contract.Result<String>() != null);
+
+ // We return non sorting name here.
+ if (this.m_nonSortName == null) {
+ this.m_nonSortName = this.m_cultureData.SNAME;
+ if (this.m_nonSortName == null) {
+ this.m_nonSortName = String.Empty;
+ }
+ }
+ return this.m_nonSortName;
+ }
+ }
+
+ // This one has the sort information (ie: de-DE_phoneb)
+ internal String SortName
+ {
+ get
+ {
+ if (this.m_sortName == null)
+ {
+ this.m_sortName = this.m_cultureData.SCOMPAREINFO;
+ }
+
+ return this.m_sortName;
+ }
+ }
+
+#if !FEATURE_CORECLR
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String IetfLanguageTag
+ {
+ get
+ {
+ Contract.Ensures(Contract.Result<String>() != null);
+
+ // special case the compatibility cultures
+ switch (this.Name)
+ {
+ case "zh-CHT":
+ return "zh-Hant";
+ case "zh-CHS":
+ return "zh-Hans";
+ default:
+ return this.Name;
+ }
+ }
+ }
+#endif
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // DisplayName
+ //
+ // Returns the full name of the CultureInfo in the localized language.
+ // For example, if the localized language of the runtime is Spanish and the CultureInfo is
+ // US English, "Ingles (Estados Unidos)" will be returned.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String DisplayName
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ Contract.Ensures(Contract.Result<String>() != null);
+ Contract.Assert(m_name != null, "[CultureInfo.DisplayName]Always expect m_name to be set");
+
+ return m_cultureData.SLOCALIZEDDISPLAYNAME;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetNativeName
+ //
+ // Returns the full name of the CultureInfo in the native language.
+ // For example, if the CultureInfo is US English, "English
+ // (United States)" will be returned.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String NativeName {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ Contract.Ensures(Contract.Result<String>() != null);
+ return (this.m_cultureData.SNATIVEDISPLAYNAME);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetEnglishName
+ //
+ // Returns the full name of the CultureInfo in English.
+ // For example, if the CultureInfo is US English, "English
+ // (United States)" will be returned.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String EnglishName {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ Contract.Ensures(Contract.Result<String>() != null);
+ return (this.m_cultureData.SENGDISPLAYNAME);
+ }
+ }
+
+ // ie: en
+ public virtual String TwoLetterISOLanguageName {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ Contract.Ensures(Contract.Result<String>() != null);
+ return (this.m_cultureData.SISO639LANGNAME);
+ }
+ }
+
+ // ie: eng
+ public virtual String ThreeLetterISOLanguageName {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ Contract.Ensures(Contract.Result<String>() != null);
+ return (this.m_cultureData.SISO639LANGNAME2);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ThreeLetterWindowsLanguageName
+ //
+ // Returns the 3 letter windows language name for the current instance. eg: "ENU"
+ // The ISO names are much preferred
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String ThreeLetterWindowsLanguageName {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ Contract.Ensures(Contract.Result<String>() != null);
+ return (this.m_cultureData.SABBREVLANGNAME);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CompareInfo Read-Only Property
+ //
+ // Gets the CompareInfo for this culture.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual CompareInfo CompareInfo
+ {
+ get
+ {
+ Contract.Ensures(Contract.Result<CompareInfo>() != null);
+
+ if (this.compareInfo == null)
+ {
+ // Since CompareInfo's don't have any overrideable properties, get the CompareInfo from
+ // the Non-Overridden CultureInfo so that we only create one CompareInfo per culture
+ CompareInfo temp = UseUserOverride
+ ? GetCultureInfo(this.m_name).CompareInfo
+ : new CompareInfo(this);
+ if (CompatibilitySwitches.IsCompatibilityBehaviorDefined)
+ {
+ this.compareInfo = temp;
+ }
+ else
+ {
+ return temp;
+ }
+ }
+ return (compareInfo);
+ }
+ }
+
+#if !FEATURE_CORECLR
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // RegionInfo
+ //
+ // Gets the RegionInfo for this culture.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ private RegionInfo Region
+ {
+ get
+ {
+ if (regionInfo==null)
+ {
+ // Make a new regionInfo
+ RegionInfo tempRegionInfo = new RegionInfo(this.m_cultureData);
+ regionInfo = tempRegionInfo;
+ }
+ return (regionInfo);
+ }
+ }
+#endif // FEATURE_CORECLR
+
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // TextInfo
+ //
+ // Gets the TextInfo for this culture.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public virtual TextInfo TextInfo {
+ get {
+ Contract.Ensures(Contract.Result<TextInfo>() != null);
+
+ if (textInfo==null)
+ {
+ // Make a new textInfo
+ TextInfo tempTextInfo = new TextInfo(this.m_cultureData);
+ tempTextInfo.SetReadOnlyState(m_isReadOnly);
+
+ if (CompatibilitySwitches.IsCompatibilityBehaviorDefined)
+ {
+ textInfo = tempTextInfo;
+ }
+ else
+ {
+ return tempTextInfo;
+ }
+ }
+ return (textInfo);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Equals
+ //
+ // Implements Object.Equals(). Returns a boolean indicating whether
+ // or not object refers to the same CultureInfo as the current instance.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override bool Equals(Object value)
+ {
+ if (Object.ReferenceEquals(this, value))
+ return true;
+
+ CultureInfo that = value as CultureInfo;
+
+ if (that != null)
+ {
+ // using CompareInfo to verify the data passed through the constructor
+ // CultureInfo(String cultureName, String textAndCompareCultureName)
+
+ return (this.Name.Equals(that.Name) && this.CompareInfo.Equals(that.CompareInfo));
+ }
+
+ return (false);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCode
+ //
+ // Implements Object.GetHashCode(). Returns the hash code for the
+ // CultureInfo. The hash code is guaranteed to be the same for CultureInfo A
+ // and B where A.Equals(B) is true.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ public override int GetHashCode()
+ {
+ return (this.Name.GetHashCode() + this.CompareInfo.GetHashCode());
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Implements Object.ToString(). Returns the name of the CultureInfo,
+ // eg. "de-DE_phoneb", "en-US", or "fj-FJ".
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override String ToString()
+ {
+ Contract.Ensures(Contract.Result<String>() != null);
+
+ Contract.Assert(m_name != null, "[CultureInfo.ToString]Always expect m_name to be set");
+ return m_name;
+ }
+
+
+ public virtual Object GetFormat(Type formatType) {
+ if (formatType == typeof(NumberFormatInfo)) {
+ return (NumberFormat);
+ }
+ if (formatType == typeof(DateTimeFormatInfo)) {
+ return (DateTimeFormat);
+ }
+ return (null);
+ }
+
+ public virtual bool IsNeutralCulture {
+ get {
+ return this.m_cultureData.IsNeutralCulture;
+ }
+ }
+
+#if !FEATURE_CORECLR
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public CultureTypes CultureTypes
+ {
+ get
+ {
+ CultureTypes types = 0;
+
+ if (m_cultureData.IsNeutralCulture)
+ types |= CultureTypes.NeutralCultures;
+ else
+ types |= CultureTypes.SpecificCultures;
+
+ types |= m_cultureData.IsWin32Installed ? CultureTypes.InstalledWin32Cultures : 0;
+
+// Disable warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete
+#pragma warning disable 618
+ types |= m_cultureData.IsFramework ? CultureTypes.FrameworkCultures : 0;
+
+#pragma warning restore 618
+ types |= m_cultureData.IsSupplementalCustomCulture ? CultureTypes.UserCustomCulture : 0;
+ types |= m_cultureData.IsReplacementCulture ? CultureTypes.ReplacementCultures | CultureTypes.UserCustomCulture : 0;
+
+ return types;
+ }
+ }
+#endif
+
+ public virtual NumberFormatInfo NumberFormat {
+ get
+ {
+ Contract.Ensures(Contract.Result<NumberFormatInfo>() != null);
+
+ if (numInfo == null) {
+ NumberFormatInfo temp = new NumberFormatInfo(this.m_cultureData);
+ temp.isReadOnly = m_isReadOnly;
+ numInfo = temp;
+ }
+ return (numInfo);
+ }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Obj"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ numInfo = value;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetDateTimeFormatInfo
+ //
+ // Create a DateTimeFormatInfo, and fill in the properties according to
+ // the CultureID.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public virtual DateTimeFormatInfo DateTimeFormat {
+ get {
+ Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null);
+
+ if (dateTimeInfo == null) {
+ // Change the calendar of DTFI to the specified calendar of this CultureInfo.
+ DateTimeFormatInfo temp = new DateTimeFormatInfo(
+ this.m_cultureData, this.Calendar);
+ temp.m_isReadOnly = m_isReadOnly;
+ System.Threading.Thread.MemoryBarrier();
+ dateTimeInfo = temp;
+ }
+ return (dateTimeInfo);
+ }
+
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Obj"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ dateTimeInfo = value;
+ }
+ }
+
+
+
+ public void ClearCachedData() {
+ s_userDefaultUICulture = null;
+ s_userDefaultCulture = null;
+
+ RegionInfo.s_currentRegionInfo = null;
+#if !FEATURE_CORECLR // System.TimeZone does not exist in CoreCLR
+ TimeZone.ResetTimeZone();
+#endif // FEATURE_CORECLR
+ TimeZoneInfo.ClearCachedData();
+ // Delete the cached cultures.
+ s_LcidCachedCultures = null;
+ s_NameCachedCultures = null;
+
+ CultureData.ClearCachedData();
+ }
+
+ /*=================================GetCalendarInstance==========================
+ **Action: Map a Win32 CALID to an instance of supported calendar.
+ **Returns: An instance of calendar.
+ **Arguments: calType The Win32 CALID
+ **Exceptions:
+ ** Shouldn't throw exception since the calType value is from our data table or from Win32 registry.
+ ** If we are in trouble (like getting a weird value from Win32 registry), just return the GregorianCalendar.
+ ============================================================================*/
+ internal static Calendar GetCalendarInstance(int calType) {
+ if (calType==Calendar.CAL_GREGORIAN) {
+ return (new GregorianCalendar());
+ }
+ return GetCalendarInstanceRare(calType);
+ }
+
+ //This function exists as a shortcut to prevent us from loading all of the non-gregorian
+ //calendars unless they're required.
+ internal static Calendar GetCalendarInstanceRare(int calType) {
+ Contract.Assert(calType!=Calendar.CAL_GREGORIAN, "calType!=Calendar.CAL_GREGORIAN");
+
+ switch (calType) {
+ case Calendar.CAL_GREGORIAN_US: // Gregorian (U.S.) calendar
+ case Calendar.CAL_GREGORIAN_ME_FRENCH: // Gregorian Middle East French calendar
+ case Calendar.CAL_GREGORIAN_ARABIC: // Gregorian Arabic calendar
+ case Calendar.CAL_GREGORIAN_XLIT_ENGLISH: // Gregorian Transliterated English calendar
+ case Calendar.CAL_GREGORIAN_XLIT_FRENCH: // Gregorian Transliterated French calendar
+ return (new GregorianCalendar((GregorianCalendarTypes)calType));
+ case Calendar.CAL_TAIWAN: // Taiwan Era calendar
+ return (new TaiwanCalendar());
+ case Calendar.CAL_JAPAN: // Japanese Emperor Era calendar
+ return (new JapaneseCalendar());
+ case Calendar.CAL_KOREA: // Korean Tangun Era calendar
+ return (new KoreanCalendar());
+ case Calendar.CAL_THAI: // Thai calendar
+ return (new ThaiBuddhistCalendar());
+ case Calendar.CAL_HIJRI: // Hijri (Arabic Lunar) calendar
+ return (new HijriCalendar());
+ case Calendar.CAL_HEBREW: // Hebrew (Lunar) calendar
+ return (new HebrewCalendar());
+ case Calendar.CAL_UMALQURA:
+ return (new UmAlQuraCalendar());
+ case Calendar.CAL_PERSIAN:
+ return (new PersianCalendar());
+ case Calendar.CAL_CHINESELUNISOLAR:
+ return (new ChineseLunisolarCalendar());
+ case Calendar.CAL_JAPANESELUNISOLAR:
+ return (new JapaneseLunisolarCalendar());
+ case Calendar.CAL_KOREANLUNISOLAR:
+ return (new KoreanLunisolarCalendar());
+ case Calendar.CAL_TAIWANLUNISOLAR:
+ return (new TaiwanLunisolarCalendar());
+ }
+ return (new GregorianCalendar());
+ }
+
+
+ /*=================================Calendar==========================
+ **Action: Return/set the default calendar used by this culture.
+ ** This value can be overridden by regional option if this is a current culture.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ** ArgumentNull_Obj if the set value is null.
+ ============================================================================*/
+
+
+ public virtual Calendar Calendar {
+ get {
+ Contract.Ensures(Contract.Result<Calendar>() != null);
+ if (calendar == null) {
+ Contract.Assert(this.m_cultureData.CalendarIds.Length > 0, "this.m_cultureData.CalendarIds.Length > 0");
+ // Get the default calendar for this culture. Note that the value can be
+ // from registry if this is a user default culture.
+ Calendar newObj = this.m_cultureData.DefaultCalendar;
+
+ System.Threading.Thread.MemoryBarrier();
+ newObj.SetReadOnlyState(m_isReadOnly);
+ calendar = newObj;
+ }
+ return (calendar);
+ }
+ }
+
+ /*=================================OptionCalendars==========================
+ **Action: Return an array of the optional calendar for this culture.
+ **Returns: an array of Calendar.
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+
+ public virtual Calendar[] OptionalCalendars {
+ get {
+ Contract.Ensures(Contract.Result<Calendar[]>() != null);
+
+ //
+ // This property always returns a new copy of the calendar array.
+ //
+ int[] calID = this.m_cultureData.CalendarIds;
+ Calendar [] cals = new Calendar[calID.Length];
+ for (int i = 0; i < cals.Length; i++) {
+ cals[i] = GetCalendarInstance(calID[i]);
+ }
+ return (cals);
+ }
+ }
+
+
+ public bool UseUserOverride {
+ get {
+ return (this.m_cultureData.UseUserOverride);
+ }
+ }
+
+#if !FEATURE_CORECLR
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public CultureInfo GetConsoleFallbackUICulture()
+ {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+
+ CultureInfo temp = m_consoleFallbackCulture;
+ if (temp == null)
+ {
+ temp = CreateSpecificCulture(this.m_cultureData.SCONSOLEFALLBACKNAME);
+ temp.m_isReadOnly = true;
+ m_consoleFallbackCulture = temp;
+ }
+ return (temp);
+ }
+#endif
+
+ public virtual Object Clone()
+ {
+ Contract.Ensures(Contract.Result<Object>() != null);
+
+ CultureInfo ci = (CultureInfo)MemberwiseClone();
+ ci.m_isReadOnly = false;
+
+ //If this is exactly our type, we can make certain optimizations so that we don't allocate NumberFormatInfo or DTFI unless
+ //they've already been allocated. If this is a derived type, we'll take a more generic codepath.
+ if (!m_isInherited)
+ {
+ if (this.dateTimeInfo != null)
+ {
+ ci.dateTimeInfo = (DateTimeFormatInfo)this.dateTimeInfo.Clone();
+ }
+ if (this.numInfo != null)
+ {
+ ci.numInfo = (NumberFormatInfo)this.numInfo.Clone();
+ }
+
+ }
+ else
+ {
+ ci.DateTimeFormat = (DateTimeFormatInfo)this.DateTimeFormat.Clone();
+ ci.NumberFormat = (NumberFormatInfo)this.NumberFormat.Clone();
+ }
+
+ if (textInfo != null)
+ {
+ ci.textInfo = (TextInfo) textInfo.Clone();
+ }
+
+ if (calendar != null)
+ {
+ ci.calendar = (Calendar) calendar.Clone();
+ }
+
+ return (ci);
+ }
+
+
+ public static CultureInfo ReadOnly(CultureInfo ci) {
+ if (ci == null) {
+ throw new ArgumentNullException("ci");
+ }
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+ Contract.EndContractBlock();
+
+ if (ci.IsReadOnly) {
+ return (ci);
+ }
+ CultureInfo newInfo = (CultureInfo)(ci.MemberwiseClone());
+
+ if (!ci.IsNeutralCulture)
+ {
+ //If this is exactly our type, we can make certain optimizations so that we don't allocate NumberFormatInfo or DTFI unless
+ //they've already been allocated. If this is a derived type, we'll take a more generic codepath.
+ if (!ci.m_isInherited) {
+ if (ci.dateTimeInfo != null) {
+ newInfo.dateTimeInfo = DateTimeFormatInfo.ReadOnly(ci.dateTimeInfo);
+ }
+ if (ci.numInfo != null) {
+ newInfo.numInfo = NumberFormatInfo.ReadOnly(ci.numInfo);
+ }
+
+ } else {
+ newInfo.DateTimeFormat = DateTimeFormatInfo.ReadOnly(ci.DateTimeFormat);
+ newInfo.NumberFormat = NumberFormatInfo.ReadOnly(ci.NumberFormat);
+ }
+ }
+
+ if (ci.textInfo != null)
+ {
+ newInfo.textInfo = TextInfo.ReadOnly(ci.textInfo);
+ }
+
+ if (ci.calendar != null)
+ {
+ newInfo.calendar = Calendar.ReadOnly(ci.calendar);
+ }
+
+ // Don't set the read-only flag too early.
+ // We should set the read-only flag here. Otherwise, info.DateTimeFormat will not be able to set.
+ newInfo.m_isReadOnly = true;
+
+ return (newInfo);
+ }
+
+
+ public bool IsReadOnly {
+ get {
+ return (m_isReadOnly);
+ }
+ }
+
+ private void VerifyWritable() {
+ if (m_isReadOnly) {
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ }
+ Contract.EndContractBlock();
+ }
+
+ // For resource lookup, we consider a culture the invariant culture by name equality.
+ // We perform this check frequently during resource lookup, so adding a property for
+ // improved readability.
+ internal bool HasInvariantCultureName
+ {
+ get { return Name == CultureInfo.InvariantCulture.Name; }
+ }
+
+ // Helper function both both overloads of GetCachedReadOnlyCulture. If lcid is 0, we use the name.
+ // If lcid is -1, use the altName and create one of those special SQL cultures.
+ internal static CultureInfo GetCultureInfoHelper(int lcid, string name, string altName)
+ {
+ // There is a race condition in this code with the side effect that the second thread's value
+ // clobbers the first in the dictionary. This is an acceptable race condition since the CultureInfo objects
+ // are content equal (but not reference equal). Since we make no guarantees there, this race condition is
+ // acceptable.
+ // See code:Dictionary#DictionaryVersusHashtableThreadSafety for details on Dictionary versus
+ // Hashtable thread safety.
+
+ // retval is our return value.
+ CultureInfo retval;
+
+ // Temporary hashtable for the names.
+ Hashtable tempNameHT = s_NameCachedCultures;
+
+ if (name != null)
+ {
+ name = CultureData.AnsiToLower(name);
+ }
+
+ if (altName != null)
+ {
+ altName = CultureData.AnsiToLower(altName);
+ }
+
+ // We expect the same result for both hashtables, but will test individually for added safety.
+ if (tempNameHT == null)
+ {
+ tempNameHT = Hashtable.Synchronized(new Hashtable());
+ }
+ else
+ {
+ // If we are called by name, check if the object exists in the hashtable. If so, return it.
+ if (lcid == -1)
+ {
+ retval = (CultureInfo)tempNameHT[name + '\xfffd' + altName];
+ if (retval != null)
+ {
+ return retval;
+ }
+ }
+ else if (lcid == 0)
+ {
+ retval = (CultureInfo)tempNameHT[name];
+ if (retval != null)
+ {
+ return retval;
+ }
+ }
+ }
+#if FEATURE_USE_LCID
+ // Next, the Lcid table.
+ Hashtable tempLcidHT = s_LcidCachedCultures;
+
+ if (tempLcidHT == null)
+ {
+ // Case insensitive is not an issue here, save the constructor call.
+ tempLcidHT = Hashtable.Synchronized(new Hashtable());
+ }
+ else
+ {
+ // If we were called by Lcid, check if the object exists in the table. If so, return it.
+ if (lcid > 0)
+ {
+ retval = (CultureInfo) tempLcidHT[lcid];
+ if (retval != null)
+ {
+ return retval;
+ }
+ }
+ }
+#endif
+ // We now have two temporary hashtables and the desired object was not found.
+ // We'll construct it. We catch any exceptions from the constructor call and return null.
+ try
+ {
+ switch(lcid)
+ {
+ case -1:
+ // call the private constructor
+ retval = new CultureInfo(name, altName);
+ break;
+
+ case 0:
+ retval = new CultureInfo(name, false);
+ break;
+
+ default:
+#if FEATURE_USE_LCID
+ retval = new CultureInfo(lcid, false);
+ break;
+#else
+ return null;
+#endif
+ }
+ }
+ catch(ArgumentException)
+ {
+ return null;
+ }
+
+ // Set it to read-only
+ retval.m_isReadOnly = true;
+
+ if (lcid == -1)
+ {
+ // This new culture will be added only to the name hash table.
+ tempNameHT[name + '\xfffd' + altName] = retval;
+
+ // when lcid == -1 then TextInfo object is already get created and we need to set it as read only.
+ retval.TextInfo.SetReadOnlyState(true);
+ }
+ else
+ {
+ // Remember our name (as constructed). Do NOT use alternate sort name versions because
+ // we have internal state representing the sort. (So someone would get the wrong cached version)
+ string newName = CultureData.AnsiToLower(retval.m_name);
+
+ // We add this new culture info object to both tables.
+ tempNameHT[newName] = retval;
+#if FEATURE_USE_LCID
+ const int LCID_ZH_CHS_HANS = 0x0004;
+ const int LCID_ZH_CHT_HANT = 0x7c04;
+
+ if ((retval.LCID == LCID_ZH_CHS_HANS && newName == "zh-hans")
+ || (retval.LCID == LCID_ZH_CHT_HANT && newName == "zh-hant"))
+ {
+ // do nothing because we only want zh-CHS and zh-CHT to cache
+ // by lcid
+ }
+ else
+ {
+ tempLcidHT[retval.LCID] = retval;
+ }
+
+#endif
+ }
+
+#if FEATURE_USE_LCID
+ // Copy the two hashtables to the corresponding member variables. This will potentially overwrite
+ // new tables simultaneously created by a new thread, but maximizes thread safety.
+ if(-1 != lcid)
+ {
+ // Only when we modify the lcid hash table, is there a need to overwrite.
+ s_LcidCachedCultures = tempLcidHT;
+ }
+#endif
+
+ s_NameCachedCultures = tempNameHT;
+
+ // Finally, return our new CultureInfo object.
+ return retval;
+ }
+
+#if FEATURE_USE_LCID
+ // Gets a cached copy of the specified culture from an internal hashtable (or creates it
+ // if not found). (LCID version)... use named version
+ public static CultureInfo GetCultureInfo(int culture)
+ {
+ // Must check for -1 now since the helper function uses the value to signal
+ // the altCulture code path for SQL Server.
+ // Also check for zero as this would fail trying to add as a key to the hash.
+ if (culture <= 0) {
+ throw new ArgumentOutOfRangeException("culture",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+ Contract.EndContractBlock();
+ CultureInfo retval = GetCultureInfoHelper(culture, null, null);
+ if (null == retval)
+ {
+ throw new CultureNotFoundException(
+ "culture", culture, Environment.GetResourceString("Argument_CultureNotSupported"));
+ }
+ return retval;
+ }
+#endif
+
+ // Gets a cached copy of the specified culture from an internal hashtable (or creates it
+ // if not found). (Named version)
+ public static CultureInfo GetCultureInfo(string name)
+ {
+ // Make sure we have a valid, non-zero length string as name
+ if (name == null)
+ {
+ throw new ArgumentNullException("name");
+ }
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+ Contract.EndContractBlock();
+
+ CultureInfo retval = GetCultureInfoHelper(0, name, null);
+ if (retval == null)
+ {
+ throw new CultureNotFoundException(
+ "name", name, Environment.GetResourceString("Argument_CultureNotSupported"));
+
+ }
+ return retval;
+ }
+
+ // Gets a cached copy of the specified culture from an internal hashtable (or creates it
+ // if not found).
+ public static CultureInfo GetCultureInfo(string name, string altName)
+ {
+ // Make sure we have a valid, non-zero length string as name
+ if (null == name)
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ if (null == altName)
+ {
+ throw new ArgumentNullException("altName");
+ }
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+ Contract.EndContractBlock();
+
+ CultureInfo retval = GetCultureInfoHelper(-1, name, altName);
+ if (retval == null)
+ {
+ throw new CultureNotFoundException("name or altName",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("Argument_OneOfCulturesNotSupported"),
+ name,
+ altName));
+ }
+ return retval;
+ }
+
+
+ // This function is deprecated, we don't like it
+ public static CultureInfo GetCultureInfoByIetfLanguageTag(string name)
+ {
+ Contract.Ensures(Contract.Result<CultureInfo>() != null);
+
+ // Disallow old zh-CHT/zh-CHS names
+ if (name == "zh-CHT" || name == "zh-CHS")
+ {
+ throw new CultureNotFoundException(
+ "name",
+ String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Argument_CultureIetfNotSupported"), name)
+ );
+ }
+
+ CultureInfo ci = GetCultureInfo(name);
+
+ // Disallow alt sorts and es-es_TS
+ if (ci.LCID > 0xffff || ci.LCID == 0x040a)
+ {
+ throw new CultureNotFoundException(
+ "name",
+ String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Argument_CultureIetfNotSupported"), name)
+ );
+ }
+
+ return ci;
+ }
+
+ private static volatile bool s_isTaiwanSku;
+ private static volatile bool s_haveIsTaiwanSku;
+ internal static bool IsTaiwanSku
+ {
+ get
+ {
+ if (!s_haveIsTaiwanSku)
+ {
+ s_isTaiwanSku = (GetSystemDefaultUILanguage() == "zh-TW");
+ s_haveIsTaiwanSku = true;
+ }
+ return (bool)s_isTaiwanSku;
+ }
+ }
+
+ //
+ // Helper Methods.
+ //
+
+ // Get Locale Info Ex calls. So we don't have to muck with the different int/string return types we declared two of these:
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern String nativeGetLocaleInfoEx(String localeName, uint field);
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern int nativeGetLocaleInfoExInt(String localeName, uint field);
+
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern bool nativeSetThreadLocale(String localeName);
+
+ [System.Security.SecurityCritical]
+ private static String GetDefaultLocaleName(int localeType)
+ {
+ Contract.Assert(localeType == LOCALE_USER_DEFAULT || localeType == LOCALE_SYSTEM_DEFAULT, "[CultureInfo.GetDefaultLocaleName] localeType must be LOCALE_USER_DEFAULT or LOCALE_SYSTEM_DEFAULT");
+
+ string localeName = null;
+ if(InternalGetDefaultLocaleName(localeType, JitHelpers.GetStringHandleOnStack(ref localeName)))
+ {
+ return localeName;
+ }
+ return string.Empty;
+ }
+
+ // Get the default locale name
+ [System.Security.SecurityCritical] // auto-generated
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool InternalGetDefaultLocaleName(int localetype, StringHandleOnStack localeString);
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ private static String GetUserDefaultUILanguage()
+ {
+ string userDefaultUiLanguage = null;
+ if(InternalGetUserDefaultUILanguage(JitHelpers.GetStringHandleOnStack(ref userDefaultUiLanguage)))
+ {
+ return userDefaultUiLanguage;
+ }
+ return String.Empty;
+ }
+
+ // Get the user's default UI language, return locale name
+ [System.Security.SecurityCritical] // auto-generated
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool InternalGetUserDefaultUILanguage(StringHandleOnStack userDefaultUiLanguage);
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ private static String GetSystemDefaultUILanguage()
+ {
+ string systemDefaultUiLanguage = null;
+ if(InternalGetSystemDefaultUILanguage(JitHelpers.GetStringHandleOnStack(ref systemDefaultUiLanguage)))
+ {
+ return systemDefaultUiLanguage;
+ }
+ return String.Empty;
+
+ }
+
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool InternalGetSystemDefaultUILanguage(StringHandleOnStack systemDefaultUiLanguage);
+
+// Added but disabled from desktop in .NET 4.0, stayed disabled in .NET 4.5
+#if FEATURE_CORECLR
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern String[] nativeGetResourceFallbackArray();
+#endif
+ }
+}
+
diff --git a/src/mscorlib/src/System/Globalization/CultureNotFoundException.cs b/src/mscorlib/src/System/Globalization/CultureNotFoundException.cs
new file mode 100644
index 0000000000..0486cc9d17
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CultureNotFoundException.cs
@@ -0,0 +1,131 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization {
+
+ using System;
+ using System.Runtime.Serialization;
+ using System.Threading;
+ using System.Diagnostics.Contracts;
+
+ [System.Runtime.InteropServices.ComVisible(true)]
+ [Serializable]
+ public partial class CultureNotFoundException : ArgumentException, ISerializable
+ {
+ private string m_invalidCultureName; // unrecognized culture name
+ private Nullable<int> m_invalidCultureId; // unrecognized culture Lcid
+
+ public CultureNotFoundException()
+ : base(DefaultMessage)
+ {
+ }
+
+ public CultureNotFoundException(String message)
+ : base(message)
+ {
+ }
+
+ public CultureNotFoundException(String paramName, String message)
+ : base(message, paramName)
+ {
+ }
+
+ public CultureNotFoundException(String message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ public CultureNotFoundException(String paramName, int invalidCultureId, String message)
+ : base(message, paramName)
+ {
+ m_invalidCultureId = invalidCultureId;
+ }
+
+ public CultureNotFoundException(String message, int invalidCultureId, Exception innerException)
+ : base(message, innerException)
+ {
+ m_invalidCultureId = invalidCultureId;
+ }
+
+ public CultureNotFoundException(String paramName, string invalidCultureName, String message)
+ : base(message, paramName)
+ {
+ m_invalidCultureName = invalidCultureName;
+ }
+
+ public CultureNotFoundException(String message, string invalidCultureName, Exception innerException)
+ : base(message, innerException)
+ {
+ m_invalidCultureName = invalidCultureName;
+ }
+
+ protected CultureNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) {
+ m_invalidCultureId = (Nullable<int>) info.GetValue("InvalidCultureId", typeof(Nullable<int>));
+ m_invalidCultureName = (string) info.GetValue("InvalidCultureName", typeof(string));
+ }
+
+ [System.Security.SecurityCritical] // auto-generated_required
+ public override void GetObjectData(SerializationInfo info, StreamingContext context) {
+ if (info==null) {
+ throw new ArgumentNullException("info");
+ }
+ Contract.EndContractBlock();
+ base.GetObjectData(info, context);
+ Nullable<int> invalidCultureId = null;
+ invalidCultureId = m_invalidCultureId;
+ info.AddValue("InvalidCultureId", invalidCultureId, typeof(Nullable<int>));
+ info.AddValue("InvalidCultureName", m_invalidCultureName, typeof(string));
+ }
+ public virtual Nullable<int> InvalidCultureId
+ {
+ get { return m_invalidCultureId; }
+ }
+
+ public virtual string InvalidCultureName
+ {
+ get { return m_invalidCultureName; }
+ }
+
+ private static String DefaultMessage
+ {
+ get
+ {
+ return Environment.GetResourceString("Argument_CultureNotSupported");
+ }
+ }
+
+ private String FormatedInvalidCultureId
+ {
+ get
+ {
+ if (InvalidCultureId != null)
+ {
+ return String.Format(CultureInfo.InvariantCulture,
+ "{0} (0x{0:x4})", (int)InvalidCultureId);
+ }
+ return InvalidCultureName;
+ }
+ }
+
+ public override String Message
+ {
+ get
+ {
+ String s = base.Message;
+ if (
+ m_invalidCultureId != null ||
+ m_invalidCultureName != null)
+ {
+ String valueMessage = Environment.GetResourceString("Argument_CultureInvalidIdentifier", FormatedInvalidCultureId);
+ if (s == null)
+ return valueMessage;
+ return s + Environment.NewLine + valueMessage;
+ }
+ return s;
+ }
+ }
+
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/CultureTypes.cs b/src/mscorlib/src/System/Globalization/CultureTypes.cs
new file mode 100644
index 0000000000..d506d50c9d
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/CultureTypes.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// The enumeration constants used in CultureInfo.GetCultures().
+//
+// Note that this isn't exposed in Silverlight
+namespace System.Globalization {
+
+
+[Serializable]
+ [Flags]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public enum CultureTypes
+ {
+ NeutralCultures = 0x0001, // Neutral cultures are cultures like "en", "de", "zh", etc, for enumeration this includes ALL neutrals regardless of other flags
+ SpecificCultures = 0x0002, // Non-netural cultuers. Examples are "en-us", "zh-tw", etc., for enumeration this includes ALL specifics regardless of other flags
+ InstalledWin32Cultures = 0x0004, // Win32 installed cultures in the system and exists in the framework too., this is effectively all cultures
+
+ AllCultures = NeutralCultures | SpecificCultures | InstalledWin32Cultures,
+
+ UserCustomCulture = 0x0008, // User defined custom culture
+ ReplacementCultures = 0x0010, // User defined replacement custom culture.
+ [Obsolete("This value has been deprecated. Please use other values in CultureTypes.")]
+ WindowsOnlyCultures = 0x0020, // this will always return empty list.
+ [Obsolete("This value has been deprecated. Please use other values in CultureTypes.")]
+ FrameworkCultures = 0x0040, // will return only the v2 cultures marked as Framework culture.
+ }
+
+}
diff --git a/src/mscorlib/src/System/Globalization/DateTimeFormat.cs b/src/mscorlib/src/System/Globalization/DateTimeFormat.cs
new file mode 100644
index 0000000000..228e5f56a2
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/DateTimeFormat.cs
@@ -0,0 +1,1054 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System {
+ using System.Text;
+ using System.Threading;
+ using System.Globalization;
+ using System.Collections.Generic;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
+ using System.Runtime.Versioning;
+ using System.Security;
+ using System.Diagnostics.Contracts;
+
+ /*
+ Customized format patterns:
+ P.S. Format in the table below is the internal number format used to display the pattern.
+
+ Patterns Format Description Example
+ ========= ========== ===================================== ========
+ "h" "0" hour (12-hour clock)w/o leading zero 3
+ "hh" "00" hour (12-hour clock)with leading zero 03
+ "hh*" "00" hour (12-hour clock)with leading zero 03
+
+ "H" "0" hour (24-hour clock)w/o leading zero 8
+ "HH" "00" hour (24-hour clock)with leading zero 08
+ "HH*" "00" hour (24-hour clock) 08
+
+ "m" "0" minute w/o leading zero
+ "mm" "00" minute with leading zero
+ "mm*" "00" minute with leading zero
+
+ "s" "0" second w/o leading zero
+ "ss" "00" second with leading zero
+ "ss*" "00" second with leading zero
+
+ "f" "0" second fraction (1 digit)
+ "ff" "00" second fraction (2 digit)
+ "fff" "000" second fraction (3 digit)
+ "ffff" "0000" second fraction (4 digit)
+ "fffff" "00000" second fraction (5 digit)
+ "ffffff" "000000" second fraction (6 digit)
+ "fffffff" "0000000" second fraction (7 digit)
+
+ "F" "0" second fraction (up to 1 digit)
+ "FF" "00" second fraction (up to 2 digit)
+ "FFF" "000" second fraction (up to 3 digit)
+ "FFFF" "0000" second fraction (up to 4 digit)
+ "FFFFF" "00000" second fraction (up to 5 digit)
+ "FFFFFF" "000000" second fraction (up to 6 digit)
+ "FFFFFFF" "0000000" second fraction (up to 7 digit)
+
+ "t" first character of AM/PM designator A
+ "tt" AM/PM designator AM
+ "tt*" AM/PM designator PM
+
+ "d" "0" day w/o leading zero 1
+ "dd" "00" day with leading zero 01
+ "ddd" short weekday name (abbreviation) Mon
+ "dddd" full weekday name Monday
+ "dddd*" full weekday name Monday
+
+
+ "M" "0" month w/o leading zero 2
+ "MM" "00" month with leading zero 02
+ "MMM" short month name (abbreviation) Feb
+ "MMMM" full month name Febuary
+ "MMMM*" full month name Febuary
+
+ "y" "0" two digit year (year % 100) w/o leading zero 0
+ "yy" "00" two digit year (year % 100) with leading zero 00
+ "yyy" "D3" year 2000
+ "yyyy" "D4" year 2000
+ "yyyyy" "D5" year 2000
+ ...
+
+ "z" "+0;-0" timezone offset w/o leading zero -8
+ "zz" "+00;-00" timezone offset with leading zero -08
+ "zzz" "+00;-00" for hour offset, "00" for minute offset full timezone offset -07:30
+ "zzz*" "+00;-00" for hour offset, "00" for minute offset full timezone offset -08:00
+
+ "K" -Local "zzz", e.g. -08:00
+ -Utc "'Z'", representing UTC
+ -Unspecified ""
+ -DateTimeOffset "zzzzz" e.g -07:30:15
+
+ "g*" the current era name A.D.
+
+ ":" time separator : -- DEPRECATED - Insert separator directly into pattern (eg: "H.mm.ss")
+ "/" date separator /-- DEPRECATED - Insert separator directly into pattern (eg: "M-dd-yyyy")
+ "'" quoted string 'ABC' will insert ABC into the formatted string.
+ '"' quoted string "ABC" will insert ABC into the formatted string.
+ "%" used to quote a single pattern characters E.g.The format character "%y" is to print two digit year.
+ "\" escaped character E.g. '\d' insert the character 'd' into the format string.
+ other characters insert the character into the format string.
+
+ Pre-defined format characters:
+ (U) to indicate Universal time is used.
+ (G) to indicate Gregorian calendar is used.
+
+ Format Description Real format Example
+ ========= ================================= ====================== =======================
+ "d" short date culture-specific 10/31/1999
+ "D" long data culture-specific Sunday, October 31, 1999
+ "f" full date (long date + short time) culture-specific Sunday, October 31, 1999 2:00 AM
+ "F" full date (long date + long time) culture-specific Sunday, October 31, 1999 2:00:00 AM
+ "g" general date (short date + short time) culture-specific 10/31/1999 2:00 AM
+ "G" general date (short date + long time) culture-specific 10/31/1999 2:00:00 AM
+ "m"/"M" Month/Day date culture-specific October 31
+(G) "o"/"O" Round Trip XML "yyyy-MM-ddTHH:mm:ss.fffffffK" 1999-10-31 02:00:00.0000000Z
+(G) "r"/"R" RFC 1123 date, "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'" Sun, 31 Oct 1999 10:00:00 GMT
+(G) "s" Sortable format, based on ISO 8601. "yyyy-MM-dd'T'HH:mm:ss" 1999-10-31T02:00:00
+ ('T' for local time)
+ "t" short time culture-specific 2:00 AM
+ "T" long time culture-specific 2:00:00 AM
+(G) "u" Universal time with sortable format, "yyyy'-'MM'-'dd HH':'mm':'ss'Z'" 1999-10-31 10:00:00Z
+ based on ISO 8601.
+(U) "U" Universal time with full culture-specific Sunday, October 31, 1999 10:00:00 AM
+ (long date + long time) format
+ "y"/"Y" Year/Month day culture-specific October, 1999
+
+ */
+
+ //This class contains only static members and does not require the serializable attribute.
+ internal static
+ class DateTimeFormat {
+
+ internal const int MaxSecondsFractionDigits = 7;
+ internal static readonly TimeSpan NullOffset = TimeSpan.MinValue;
+
+ internal static char[] allStandardFormats =
+ {
+ 'd', 'D', 'f', 'F', 'g', 'G',
+ 'm', 'M', 'o', 'O', 'r', 'R',
+ 's', 't', 'T', 'u', 'U', 'y', 'Y',
+ };
+
+ internal const String RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
+ internal const String RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz";
+
+ private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
+
+ internal static String[] fixedNumberFormats = new String[] {
+ "0",
+ "00",
+ "000",
+ "0000",
+ "00000",
+ "000000",
+ "0000000",
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Format the positive integer value to a string and perfix with assigned
+ // length of leading zero.
+ //
+ // Parameters:
+ // value: The value to format
+ // len: The maximum length for leading zero.
+ // If the digits of the value is greater than len, no leading zero is added.
+ //
+ // Notes:
+ // The function can format to Int32.MaxValue.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static void FormatDigits(StringBuilder outputBuffer, int value, int len) {
+ Contract.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0");
+ FormatDigits(outputBuffer, value, len, false);
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe static void FormatDigits(StringBuilder outputBuffer, int value, int len, bool overrideLengthLimit) {
+ Contract.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0");
+
+ // Limit the use of this function to be two-digits, so that we have the same behavior
+ // as RTM bits.
+ if (!overrideLengthLimit && len > 2)
+ {
+ len = 2;
+ }
+
+ char* buffer = stackalloc char[16];
+ char* p = buffer + 16;
+ int n = value;
+ do {
+ *--p = (char)(n % 10 + '0');
+ n /= 10;
+ } while ((n != 0)&&(p > buffer));
+
+ int digits = (int) (buffer + 16 - p);
+
+ //If the repeat count is greater than 0, we're trying
+ //to emulate the "00" format, so we have to prepend
+ //a zero if the string only has one character.
+ while ((digits < len) && (p > buffer)) {
+ *--p='0';
+ digits++;
+ }
+ outputBuffer.Append(p, digits);
+ }
+
+ private static void HebrewFormatDigits(StringBuilder outputBuffer, int digits) {
+ outputBuffer.Append(HebrewNumber.ToString(digits));
+ }
+
+ internal static int ParseRepeatPattern(String format, int pos, char patternChar)
+ {
+ int len = format.Length;
+ int index = pos + 1;
+ while ((index < len) && (format[index] == patternChar))
+ {
+ index++;
+ }
+ return (index - pos);
+ }
+
+ private static String FormatDayOfWeek(int dayOfWeek, int repeat, DateTimeFormatInfo dtfi)
+ {
+ Contract.Assert(dayOfWeek >= 0 && dayOfWeek <= 6, "dayOfWeek >= 0 && dayOfWeek <= 6");
+ if (repeat == 3)
+ {
+ return (dtfi.GetAbbreviatedDayName((DayOfWeek)dayOfWeek));
+ }
+ // Call dtfi.GetDayName() here, instead of accessing DayNames property, because we don't
+ // want a clone of DayNames, which will hurt perf.
+ return (dtfi.GetDayName((DayOfWeek)dayOfWeek));
+ }
+
+ private static String FormatMonth(int month, int repeatCount, DateTimeFormatInfo dtfi)
+ {
+ Contract.Assert(month >=1 && month <= 12, "month >=1 && month <= 12");
+ if (repeatCount == 3)
+ {
+ return (dtfi.GetAbbreviatedMonthName(month));
+ }
+ // Call GetMonthName() here, instead of accessing MonthNames property, because we don't
+ // want a clone of MonthNames, which will hurt perf.
+ return (dtfi.GetMonthName(month));
+ }
+
+ //
+ // FormatHebrewMonthName
+ //
+ // Action: Return the Hebrew month name for the specified DateTime.
+ // Returns: The month name string for the specified DateTime.
+ // Arguments:
+ // time the time to format
+ // month The month is the value of HebrewCalendar.GetMonth(time).
+ // repeat Return abbreviated month name if repeat=3, or full month name if repeat=4
+ // dtfi The DateTimeFormatInfo which uses the Hebrew calendars as its calendar.
+ // Exceptions: None.
+ //
+
+ /* Note:
+ If DTFI is using Hebrew calendar, GetMonthName()/GetAbbreviatedMonthName() will return month names like this:
+ 1 Hebrew 1st Month
+ 2 Hebrew 2nd Month
+ .. ...
+ 6 Hebrew 6th Month
+ 7 Hebrew 6th Month II (used only in a leap year)
+ 8 Hebrew 7th Month
+ 9 Hebrew 8th Month
+ 10 Hebrew 9th Month
+ 11 Hebrew 10th Month
+ 12 Hebrew 11th Month
+ 13 Hebrew 12th Month
+
+ Therefore, if we are in a regular year, we have to increment the month name if moth is greater or eqaul to 7.
+ */
+ private static String FormatHebrewMonthName(DateTime time, int month, int repeatCount, DateTimeFormatInfo dtfi)
+ {
+ Contract.Assert(repeatCount != 3 || repeatCount != 4, "repeateCount should be 3 or 4");
+ if (dtfi.Calendar.IsLeapYear(dtfi.Calendar.GetYear(time))) {
+ // This month is in a leap year
+ return (dtfi.internalGetMonthName(month, MonthNameStyles.LeapYear, (repeatCount == 3)));
+ }
+ // This is in a regular year.
+ if (month >= 7) {
+ month++;
+ }
+ if (repeatCount == 3) {
+ return (dtfi.GetAbbreviatedMonthName(month));
+ }
+ return (dtfi.GetMonthName(month));
+ }
+
+ //
+ // The pos should point to a quote character. This method will
+ // append to the result StringBuilder the string encloed by the quote character.
+ //
+ internal static int ParseQuoteString(String format, int pos, StringBuilder result)
+ {
+ //
+ // NOTE : pos will be the index of the quote character in the 'format' string.
+ //
+ int formatLen = format.Length;
+ int beginPos = pos;
+ char quoteChar = format[pos++]; // Get the character used to quote the following string.
+
+ bool foundQuote = false;
+ while (pos < formatLen)
+ {
+ char ch = format[pos++];
+ if (ch == quoteChar)
+ {
+ foundQuote = true;
+ break;
+ }
+ else if (ch == '\\') {
+ // The following are used to support escaped character.
+ // Escaped character is also supported in the quoted string.
+ // Therefore, someone can use a format like "'minute:' mm\"" to display:
+ // minute: 45"
+ // because the second double quote is escaped.
+ if (pos < formatLen) {
+ result.Append(format[pos++]);
+ } else {
+ //
+ // This means that '\' is at the end of the formatting string.
+ //
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ } else {
+ result.Append(ch);
+ }
+ }
+
+ if (!foundQuote)
+ {
+ // Here we can't find the matching quote.
+ throw new FormatException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("Format_BadQuote"), quoteChar));
+ }
+
+ //
+ // Return the character count including the begin/end quote characters and enclosed string.
+ //
+ return (pos - beginPos);
+ }
+
+ //
+ // Get the next character at the index of 'pos' in the 'format' string.
+ // Return value of -1 means 'pos' is already at the end of the 'format' string.
+ // Otherwise, return value is the int value of the next character.
+ //
+ internal static int ParseNextChar(String format, int pos)
+ {
+ if (pos >= format.Length - 1)
+ {
+ return (-1);
+ }
+ return ((int)format[pos+1]);
+ }
+
+ //
+ // IsUseGenitiveForm
+ //
+ // Actions: Check the format to see if we should use genitive month in the formatting.
+ // Starting at the position (index) in the (format) string, look back and look ahead to
+ // see if there is "d" or "dd". In the case like "d MMMM" or "MMMM dd", we can use
+ // genitive form. Genitive form is not used if there is more than two "d".
+ // Arguments:
+ // format The format string to be scanned.
+ // index Where we should start the scanning. This is generally where "M" starts.
+ // tokenLen The len of the current pattern character. This indicates how many "M" that we have.
+ // patternToMatch The pattern that we want to search. This generally uses "d"
+ //
+ private static bool IsUseGenitiveForm(String format, int index, int tokenLen, char patternToMatch) {
+ int i;
+ int repeat = 0;
+ //
+ // Look back to see if we can find "d" or "ddd"
+ //
+
+ // Find first "d".
+ for (i = index - 1; i >= 0 && format[i] != patternToMatch; i--) { /*Do nothing here */ };
+
+ if (i >= 0) {
+ // Find a "d", so look back to see how many "d" that we can find.
+ while (--i >= 0 && format[i] == patternToMatch) {
+ repeat++;
+ }
+ //
+ // repeat == 0 means that we have one (patternToMatch)
+ // repeat == 1 means that we have two (patternToMatch)
+ //
+ if (repeat <= 1) {
+ return (true);
+ }
+ // Note that we can't just stop here. We may find "ddd" while looking back, and we have to look
+ // ahead to see if there is "d" or "dd".
+ }
+
+ //
+ // If we can't find "d" or "dd" by looking back, try look ahead.
+ //
+
+ // Find first "d"
+ for (i = index + tokenLen; i < format.Length && format[i] != patternToMatch; i++) { /* Do nothing here */ };
+
+ if (i < format.Length) {
+ repeat = 0;
+ // Find a "d", so contine the walk to see how may "d" that we can find.
+ while (++i < format.Length && format[i] == patternToMatch) {
+ repeat++;
+ }
+ //
+ // repeat == 0 means that we have one (patternToMatch)
+ // repeat == 1 means that we have two (patternToMatch)
+ //
+ if (repeat <= 1) {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+
+ //
+ // FormatCustomized
+ //
+ // Actions: Format the DateTime instance using the specified format.
+ //
+ private static String FormatCustomized(DateTime dateTime, String format, DateTimeFormatInfo dtfi, TimeSpan offset) {
+
+
+ Calendar cal = dtfi.Calendar;
+ StringBuilder result = StringBuilderCache.Acquire();
+ // This is a flag to indicate if we are format the dates using Hebrew calendar.
+
+ bool isHebrewCalendar = (cal.ID == Calendar.CAL_HEBREW);
+ // This is a flag to indicate if we are formating hour/minute/second only.
+ bool bTimeOnly = true;
+
+ int i = 0;
+ int tokenLen, hour12;
+
+ while (i < format.Length) {
+ char ch = format[i];
+ int nextChar;
+ switch (ch) {
+ case 'g':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ result.Append(dtfi.GetEraName(cal.GetEra(dateTime)));
+ break;
+ case 'h':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ hour12 = dateTime.Hour % 12;
+ if (hour12 == 0)
+ {
+ hour12 = 12;
+ }
+ FormatDigits(result, hour12, tokenLen);
+ break;
+ case 'H':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ FormatDigits(result, dateTime.Hour, tokenLen);
+ break;
+ case 'm':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ FormatDigits(result, dateTime.Minute, tokenLen);
+ break;
+ case 's':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ FormatDigits(result, dateTime.Second, tokenLen);
+ break;
+ case 'f':
+ case 'F':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ if (tokenLen <= MaxSecondsFractionDigits) {
+ long fraction = (dateTime.Ticks % Calendar.TicksPerSecond);
+ fraction = fraction / (long)Math.Pow(10, 7 - tokenLen);
+ if (ch == 'f') {
+ result.Append(((int)fraction).ToString(fixedNumberFormats[tokenLen - 1], CultureInfo.InvariantCulture));
+ }
+ else {
+ int effectiveDigits = tokenLen;
+ while (effectiveDigits > 0) {
+ if (fraction % 10 == 0) {
+ fraction = fraction / 10;
+ effectiveDigits--;
+ }
+ else {
+ break;
+ }
+ }
+ if (effectiveDigits > 0) {
+ result.Append(((int)fraction).ToString(fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
+ }
+ else {
+ // No fraction to emit, so see if we should remove decimal also.
+ if (result.Length > 0 && result[result.Length - 1] == '.') {
+ result.Remove(result.Length - 1, 1);
+ }
+ }
+ }
+ } else {
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ break;
+ case 't':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ if (tokenLen == 1)
+ {
+ if (dateTime.Hour < 12)
+ {
+ if (dtfi.AMDesignator.Length >= 1)
+ {
+ result.Append(dtfi.AMDesignator[0]);
+ }
+ }
+ else
+ {
+ if (dtfi.PMDesignator.Length >= 1)
+ {
+ result.Append(dtfi.PMDesignator[0]);
+ }
+ }
+
+ }
+ else
+ {
+ result.Append((dateTime.Hour < 12 ? dtfi.AMDesignator : dtfi.PMDesignator));
+ }
+ break;
+ case 'd':
+ //
+ // tokenLen == 1 : Day of month as digits with no leading zero.
+ // tokenLen == 2 : Day of month as digits with leading zero for single-digit months.
+ // tokenLen == 3 : Day of week as a three-leter abbreviation.
+ // tokenLen >= 4 : Day of week as its full name.
+ //
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ if (tokenLen <= 2)
+ {
+ int day = cal.GetDayOfMonth(dateTime);
+ if (isHebrewCalendar) {
+ // For Hebrew calendar, we need to convert numbers to Hebrew text for yyyy, MM, and dd values.
+ HebrewFormatDigits(result, day);
+ } else {
+ FormatDigits(result, day, tokenLen);
+ }
+ }
+ else
+ {
+ int dayOfWeek = (int)cal.GetDayOfWeek(dateTime);
+ result.Append(FormatDayOfWeek(dayOfWeek, tokenLen, dtfi));
+ }
+ bTimeOnly = false;
+ break;
+ case 'M':
+ //
+ // tokenLen == 1 : Month as digits with no leading zero.
+ // tokenLen == 2 : Month as digits with leading zero for single-digit months.
+ // tokenLen == 3 : Month as a three-letter abbreviation.
+ // tokenLen >= 4 : Month as its full name.
+ //
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ int month = cal.GetMonth(dateTime);
+ if (tokenLen <= 2)
+ {
+ if (isHebrewCalendar) {
+ // For Hebrew calendar, we need to convert numbers to Hebrew text for yyyy, MM, and dd values.
+ HebrewFormatDigits(result, month);
+ } else {
+ FormatDigits(result, month, tokenLen);
+ }
+ }
+ else {
+ if (isHebrewCalendar) {
+ result.Append(FormatHebrewMonthName(dateTime, month, tokenLen, dtfi));
+ } else {
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0 && tokenLen >= 4) {
+ result.Append(
+ dtfi.internalGetMonthName(
+ month,
+ IsUseGenitiveForm(format, i, tokenLen, 'd')? MonthNameStyles.Genitive : MonthNameStyles.Regular,
+ false));
+ } else {
+ result.Append(FormatMonth(month, tokenLen, dtfi));
+ }
+ }
+ }
+ bTimeOnly = false;
+ break;
+ case 'y':
+ // Notes about OS behavior:
+ // y: Always print (year % 100). No leading zero.
+ // yy: Always print (year % 100) with leading zero.
+ // yyy/yyyy/yyyyy/... : Print year value. No leading zero.
+
+ int year = cal.GetYear(dateTime);
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ if (dtfi.HasForceTwoDigitYears) {
+ FormatDigits(result, year, tokenLen <= 2 ? tokenLen : 2);
+ }
+ else if (cal.ID == Calendar.CAL_HEBREW) {
+ HebrewFormatDigits(result, year);
+ }
+ else {
+ if (tokenLen <= 2) {
+ FormatDigits(result, year % 100, tokenLen);
+ }
+ else {
+ String fmtPattern = "D" + tokenLen.ToString();
+ result.Append(year.ToString(fmtPattern, CultureInfo.InvariantCulture));
+ }
+ }
+ bTimeOnly = false;
+ break;
+ case 'z':
+ tokenLen = ParseRepeatPattern(format, i, ch);
+ FormatCustomizedTimeZone(dateTime, offset, format, tokenLen, bTimeOnly, result);
+ break;
+ case 'K':
+ tokenLen = 1;
+ FormatCustomizedRoundripTimeZone(dateTime, offset, result);
+ break;
+ case ':':
+ result.Append(dtfi.TimeSeparator);
+ tokenLen = 1;
+ break;
+ case '/':
+ result.Append(dtfi.DateSeparator);
+ tokenLen = 1;
+ break;
+ case '\'':
+ case '\"':
+ tokenLen = ParseQuoteString(format, i, result);
+ break;
+ case '%':
+ // Optional format character.
+ // For example, format string "%d" will print day of month
+ // without leading zero. Most of the cases, "%" can be ignored.
+ nextChar = ParseNextChar(format, i);
+ // nextChar will be -1 if we already reach the end of the format string.
+ // Besides, we will not allow "%%" appear in the pattern.
+ if (nextChar >= 0 && nextChar != (int)'%') {
+ result.Append(FormatCustomized(dateTime, ((char)nextChar).ToString(), dtfi, offset));
+ tokenLen = 2;
+ }
+ else
+ {
+ //
+ // This means that '%' is at the end of the format string or
+ // "%%" appears in the format string.
+ //
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ break;
+ case '\\':
+ // Escaped character. Can be used to insert character into the format string.
+ // For exmple, "\d" will insert the character 'd' into the string.
+ //
+ // NOTENOTE : we can remove this format character if we enforce the enforced quote
+ // character rule.
+ // That is, we ask everyone to use single quote or double quote to insert characters,
+ // then we can remove this character.
+ //
+ nextChar = ParseNextChar(format, i);
+ if (nextChar >= 0)
+ {
+ result.Append(((char)nextChar));
+ tokenLen = 2;
+ }
+ else
+ {
+ //
+ // This means that '\' is at the end of the formatting string.
+ //
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ break;
+ default:
+ // NOTENOTE : we can remove this rule if we enforce the enforced quote
+ // character rule.
+ // That is, if we ask everyone to use single quote or double quote to insert characters,
+ // then we can remove this default block.
+ result.Append(ch);
+ tokenLen = 1;
+ break;
+ }
+ i += tokenLen;
+ }
+ return StringBuilderCache.GetStringAndRelease(result);
+
+ }
+
+
+ // output the 'z' famliy of formats, which output a the offset from UTC, e.g. "-07:30"
+ private static void FormatCustomizedTimeZone(DateTime dateTime, TimeSpan offset, String format, Int32 tokenLen, Boolean timeOnly, StringBuilder result) {
+ // See if the instance already has an offset
+ Boolean dateTimeFormat = (offset == NullOffset);
+ if (dateTimeFormat) {
+ // No offset. The instance is a DateTime and the output should be the local time zone
+
+ if (timeOnly && dateTime.Ticks < Calendar.TicksPerDay) {
+ // For time only format and a time only input, the time offset on 0001/01/01 is less
+ // accurate than the system's current offset because of daylight saving time.
+ offset = TimeZoneInfo.GetLocalUtcOffset(DateTime.Now, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ } else if (dateTime.Kind == DateTimeKind.Utc) {
+#if FEATURE_CORECLR
+ offset = TimeSpan.Zero;
+#else // FEATURE_CORECLR
+ // This code path points to a bug in user code. It would make sense to return a 0 offset in this case.
+ // However, because it was only possible to detect this in Whidbey, there is user code that takes a
+ // dependency on being serialize a UTC DateTime using the 'z' format, and it will work almost all the
+ // time if it is offset by an incorrect conversion to local time when parsed. Therefore, we need to
+ // explicitly emit the local time offset, which we can do by removing the UTC flag.
+ InvalidFormatForUtc(format, dateTime);
+ dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
+ offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+#endif // FEATURE_CORECLR
+ } else {
+ offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+ }
+ if (offset >= TimeSpan.Zero) {
+ result.Append('+');
+ }
+ else {
+ result.Append('-');
+ // get a positive offset, so that you don't need a separate code path for the negative numbers.
+ offset = offset.Negate();
+ }
+
+ if (tokenLen <= 1) {
+ // 'z' format e.g "-7"
+ result.AppendFormat(CultureInfo.InvariantCulture, "{0:0}", offset.Hours);
+ }
+ else {
+ // 'zz' or longer format e.g "-07"
+ result.AppendFormat(CultureInfo.InvariantCulture, "{0:00}", offset.Hours);
+ if (tokenLen >= 3) {
+ // 'zzz*' or longer format e.g "-07:30"
+ result.AppendFormat(CultureInfo.InvariantCulture, ":{0:00}", offset.Minutes);
+ }
+ }
+ }
+
+ // output the 'K' format, which is for round-tripping the data
+ private static void FormatCustomizedRoundripTimeZone(DateTime dateTime, TimeSpan offset, StringBuilder result) {
+
+ // The objective of this format is to round trip the data in the type
+ // For DateTime it should round-trip the Kind value and preserve the time zone.
+ // DateTimeOffset instance, it should do so by using the internal time zone.
+
+ if (offset == NullOffset) {
+ // source is a date time, so behavior depends on the kind.
+ switch (dateTime.Kind) {
+ case DateTimeKind.Local:
+ // This should output the local offset, e.g. "-07:30"
+ offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ // fall through to shared time zone output code
+ break;
+ case DateTimeKind.Utc:
+ // The 'Z' constant is a marker for a UTC date
+ result.Append("Z");
+ return;
+ default:
+ // If the kind is unspecified, we output nothing here
+ return;
+ }
+ }
+ if (offset >= TimeSpan.Zero) {
+ result.Append('+');
+ }
+ else {
+ result.Append('-');
+ // get a positive offset, so that you don't need a separate code path for the negative numbers.
+ offset = offset.Negate();
+ }
+
+ result.AppendFormat(CultureInfo.InvariantCulture, "{0:00}:{1:00}", offset.Hours, offset.Minutes);
+ }
+
+
+ internal static String GetRealFormat(String format, DateTimeFormatInfo dtfi)
+ {
+ String realFormat = null;
+
+ switch (format[0])
+ {
+ case 'd': // Short Date
+ realFormat = dtfi.ShortDatePattern;
+ break;
+ case 'D': // Long Date
+ realFormat = dtfi.LongDatePattern;
+ break;
+ case 'f': // Full (long date + short time)
+ realFormat = dtfi.LongDatePattern + " " + dtfi.ShortTimePattern;
+ break;
+ case 'F': // Full (long date + long time)
+ realFormat = dtfi.FullDateTimePattern;
+ break;
+ case 'g': // General (short date + short time)
+ realFormat = dtfi.GeneralShortTimePattern;
+ break;
+ case 'G': // General (short date + long time)
+ realFormat = dtfi.GeneralLongTimePattern;
+ break;
+ case 'm':
+ case 'M': // Month/Day Date
+ realFormat = dtfi.MonthDayPattern;
+ break;
+ case 'o':
+ case 'O':
+ realFormat = RoundtripFormat;
+ break;
+ case 'r':
+ case 'R': // RFC 1123 Standard
+ realFormat = dtfi.RFC1123Pattern;
+ break;
+ case 's': // Sortable without Time Zone Info
+ realFormat = dtfi.SortableDateTimePattern;
+ break;
+ case 't': // Short Time
+ realFormat = dtfi.ShortTimePattern;
+ break;
+ case 'T': // Long Time
+ realFormat = dtfi.LongTimePattern;
+ break;
+ case 'u': // Universal with Sortable format
+ realFormat = dtfi.UniversalSortableDateTimePattern;
+ break;
+ case 'U': // Universal with Full (long date + long time) format
+ realFormat = dtfi.FullDateTimePattern;
+ break;
+ case 'y':
+ case 'Y': // Year/Month Date
+ realFormat = dtfi.YearMonthPattern;
+ break;
+ default:
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ return (realFormat);
+ }
+
+
+ // Expand a pre-defined format string (like "D" for long date) to the real format that
+ // we are going to use in the date time parsing.
+ // This method also convert the dateTime if necessary (e.g. when the format is in Universal time),
+ // and change dtfi if necessary (e.g. when the format should use invariant culture).
+ //
+ private static String ExpandPredefinedFormat(String format, ref DateTime dateTime, ref DateTimeFormatInfo dtfi, ref TimeSpan offset) {
+ switch (format[0]) {
+ case 'o':
+ case 'O': // Round trip format
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 'r':
+ case 'R': // RFC 1123 Standard
+ if (offset != NullOffset) {
+ // Convert to UTC invariants mean this will be in range
+ dateTime = dateTime - offset;
+ }
+ else if (dateTime.Kind == DateTimeKind.Local) {
+ InvalidFormatForLocal(format, dateTime);
+ }
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 's': // Sortable without Time Zone Info
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 'u': // Universal time in sortable format.
+ if (offset != NullOffset) {
+ // Convert to UTC invariants mean this will be in range
+ dateTime = dateTime - offset;
+ }
+ else if (dateTime.Kind == DateTimeKind.Local) {
+
+ InvalidFormatForLocal(format, dateTime);
+ }
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 'U': // Universal time in culture dependent format.
+ if (offset != NullOffset) {
+ // This format is not supported by DateTimeOffset
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ // Universal time is always in Greogrian calendar.
+ //
+ // Change the Calendar to be Gregorian Calendar.
+ //
+ dtfi = (DateTimeFormatInfo)dtfi.Clone();
+ if (dtfi.Calendar.GetType() != typeof(GregorianCalendar)) {
+ dtfi.Calendar = GregorianCalendar.GetDefaultInstance();
+ }
+ dateTime = dateTime.ToUniversalTime();
+ break;
+ }
+ format = GetRealFormat(format, dtfi);
+ return (format);
+ }
+
+ internal static String Format(DateTime dateTime, String format, DateTimeFormatInfo dtfi) {
+ return Format(dateTime, format, dtfi, NullOffset);
+ }
+
+
+ internal static String Format(DateTime dateTime, String format, DateTimeFormatInfo dtfi, TimeSpan offset)
+ {
+ Contract.Requires(dtfi != null);
+ if (format==null || format.Length==0) {
+ Boolean timeOnlySpecialCase = false;
+ if (dateTime.Ticks < Calendar.TicksPerDay) {
+ // If the time is less than 1 day, consider it as time of day.
+ // Just print out the short time format.
+ //
+ // This is a workaround for VB, since they use ticks less then one day to be
+ // time of day. In cultures which use calendar other than Gregorian calendar, these
+ // alternative calendar may not support ticks less than a day.
+ // For example, Japanese calendar only supports date after 1868/9/8.
+ // This will pose a problem when people in VB get the time of day, and use it
+ // to call ToString(), which will use the general format (short date + long time).
+ // Since Japanese calendar does not support Gregorian year 0001, an exception will be
+ // thrown when we try to get the Japanese year for Gregorian year 0001.
+ // Therefore, the workaround allows them to call ToString() for time of day from a DateTime by
+ // formatting as ISO 8601 format.
+ switch (dtfi.Calendar.ID) {
+ case Calendar.CAL_JAPAN:
+ case Calendar.CAL_TAIWAN:
+ case Calendar.CAL_HIJRI:
+ case Calendar.CAL_HEBREW:
+ case Calendar.CAL_JULIAN:
+ case Calendar.CAL_UMALQURA:
+ case Calendar.CAL_PERSIAN:
+ timeOnlySpecialCase = true;
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ }
+ }
+ if (offset == NullOffset) {
+ // Default DateTime.ToString case.
+ if (timeOnlySpecialCase) {
+ format = "s";
+ }
+ else {
+ format = "G";
+ }
+ }
+ else {
+ // Default DateTimeOffset.ToString case.
+ if (timeOnlySpecialCase) {
+ format = RoundtripDateTimeUnfixed;
+ }
+ else {
+ format = dtfi.DateTimeOffsetPattern;
+ }
+
+ }
+
+ }
+
+ if (format.Length == 1) {
+ format = ExpandPredefinedFormat(format, ref dateTime, ref dtfi, ref offset);
+ }
+
+ return (FormatCustomized(dateTime, format, dtfi, offset));
+ }
+
+ internal static String[] GetAllDateTimes(DateTime dateTime, char format, DateTimeFormatInfo dtfi)
+ {
+ Contract.Requires(dtfi != null);
+ String [] allFormats = null;
+ String [] results = null;
+
+ switch (format)
+ {
+ case 'd':
+ case 'D':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ case 'm':
+ case 'M':
+ case 't':
+ case 'T':
+ case 'y':
+ case 'Y':
+ allFormats = dtfi.GetAllDateTimePatterns(format);
+ results = new String[allFormats.Length];
+ for (int i = 0; i < allFormats.Length; i++)
+ {
+ results[i] = Format(dateTime, allFormats[i], dtfi);
+ }
+ break;
+ case 'U':
+ DateTime universalTime = dateTime.ToUniversalTime();
+ allFormats = dtfi.GetAllDateTimePatterns(format);
+ results = new String[allFormats.Length];
+ for (int i = 0; i < allFormats.Length; i++)
+ {
+ results[i] = Format(universalTime, allFormats[i], dtfi);
+ }
+ break;
+ //
+ // The following ones are special cases because these patterns are read-only in
+ // DateTimeFormatInfo.
+ //
+ case 'r':
+ case 'R':
+ case 'o':
+ case 'O':
+ case 's':
+ case 'u':
+ results = new String[] {Format(dateTime, new String(format, 1), dtfi)};
+ break;
+ default:
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+
+ }
+ return (results);
+ }
+
+ internal static String[] GetAllDateTimes(DateTime dateTime, DateTimeFormatInfo dtfi)
+ {
+ List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE);
+
+ for (int i = 0; i < allStandardFormats.Length; i++)
+ {
+ String[] strings = GetAllDateTimes(dateTime, allStandardFormats[i], dtfi);
+ for (int j = 0; j < strings.Length; j++)
+ {
+ results.Add(strings[j]);
+ }
+ }
+ String[] value = new String[results.Count];
+ results.CopyTo(0, value, 0, results.Count);
+ return (value);
+ }
+
+ // This is a placeholder for an MDA to detect when the user is using a
+ // local DateTime with a format that will be interpreted as UTC.
+ internal static void InvalidFormatForLocal(String format, DateTime dateTime) {
+ }
+
+ // This is an MDA for cases when the user is using a local format with
+ // a Utc DateTime.
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal static void InvalidFormatForUtc(String format, DateTime dateTime) {
+#if MDA_SUPPORTED
+ Mda.DateTimeInvalidLocalFormat();
+#endif
+ }
+
+
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs b/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs
new file mode 100644
index 0000000000..00c2d1f439
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/DateTimeFormatInfo.cs
@@ -0,0 +1,2936 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization {
+ using System;
+ using System.Security;
+ using System.Threading;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Runtime.Serialization;
+ using System.Security.Permissions;
+ using System.Runtime.InteropServices;
+ using System.Runtime.Versioning;
+ using System.Text;
+ using System.Diagnostics.Contracts;
+
+ //
+ // Flags used to indicate different styles of month names.
+ // This is an internal flag used by internalGetMonthName().
+ // Use flag here in case that we need to provide a combination of these styles
+ // (such as month name of a leap year in genitive form. Not likely for now,
+ // but would like to keep the option open).
+ //
+
+ [Flags]
+ internal enum MonthNameStyles {
+ Regular = 0x00000000,
+ Genitive = 0x00000001,
+ LeapYear = 0x00000002,
+ }
+
+ //
+ // Flags used to indicate special rule used in parsing/formatting
+ // for a specific DateTimeFormatInfo instance.
+ // This is an internal flag.
+ //
+ // This flag is different from MonthNameStyles because this flag
+ // can be expanded to accomodate parsing behaviors like CJK month names
+ // or alternative month names, etc.
+
+ [Flags]
+ internal enum DateTimeFormatFlags {
+ None = 0x00000000,
+ UseGenitiveMonth = 0x00000001,
+ UseLeapYearMonth = 0x00000002,
+ UseSpacesInMonthNames = 0x00000004, // Has spaces or non-breaking space in the month names.
+ UseHebrewRule = 0x00000008, // Format/Parse using the Hebrew calendar rule.
+ UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names.
+ UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers.
+
+ NotInitialized = -1,
+ }
+
+
+ [Serializable]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public sealed class DateTimeFormatInfo : ICloneable, IFormatProvider
+ {
+ //
+ // Note, some fields are derived so don't really need to be serialized, but we can't mark as
+ // optional because Whidbey was expecting them. Post-Arrowhead we could fix that
+ // once Whidbey serialization is no longer necessary.
+ //
+
+ // cache for the invariant culture.
+ // invariantInfo is constant irrespective of your current culture.
+ private static volatile DateTimeFormatInfo invariantInfo;
+
+ // an index which points to a record in Culture Data Table.
+ [NonSerialized]private CultureData m_cultureData;
+
+ // The culture name used to create this DTFI.
+ [OptionalField(VersionAdded = 2)]
+ internal String m_name = null;
+
+ // The language name of the culture used to create this DTFI.
+ [NonSerialized]private String m_langName = null;
+
+ // CompareInfo usually used by the parser.
+ [NonSerialized]private CompareInfo m_compareInfo = null;
+
+ // Culture matches current DTFI. mainly used for string comparisons during parsing.
+ [NonSerialized]private CultureInfo m_cultureInfo = null;
+
+ //
+ // Caches for various properties.
+ //
+
+ internal String amDesignator = null;
+ internal String pmDesignator = null;
+ [OptionalField(VersionAdded = 1)]
+ internal String dateSeparator = null; // derived from short date (whidbey expects, arrowhead doesn't)
+ [OptionalField(VersionAdded = 1)]
+ internal String generalShortTimePattern = null; // short date + short time (whidbey expects, arrowhead doesn't)
+ [OptionalField(VersionAdded = 1)]
+ internal String generalLongTimePattern = null; // short date + long time (whidbey expects, arrowhead doesn't)
+ [OptionalField(VersionAdded = 1)]
+ internal String timeSeparator = null; // derived from long time (whidbey expects, arrowhead doesn't)
+ internal String monthDayPattern = null;
+ [OptionalField(VersionAdded = 2)] // added in .NET Framework Release {2.0SP1/3.0SP1/3.5RTM}
+ internal String dateTimeOffsetPattern = null;
+
+ //
+ // The following are constant values.
+ //
+ internal const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
+
+ // The sortable pattern is based on ISO 8601.
+ internal const String sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
+ internal const String universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
+
+ //
+ // The following are affected by calendar settings.
+ //
+ internal Calendar calendar = null;
+
+ internal int firstDayOfWeek = -1;
+ internal int calendarWeekRule = -1;
+
+ [OptionalField(VersionAdded = 1)]
+ internal String fullDateTimePattern = null; // long date + long time (whidbey expects, arrowhead doesn't)
+
+ internal String[] abbreviatedDayNames = null;
+
+ [OptionalField(VersionAdded = 2)]
+ internal String[] m_superShortDayNames = null;
+
+ internal String[] dayNames = null;
+ internal String[] abbreviatedMonthNames = null;
+ internal String[] monthNames = null;
+ // Cache the genitive month names that we retrieve from the data table.
+ [OptionalField(VersionAdded = 2)]
+ internal String[] genitiveMonthNames = null;
+
+ // Cache the abbreviated genitive month names that we retrieve from the data table.
+ [OptionalField(VersionAdded = 2)]
+ internal String[] m_genitiveAbbreviatedMonthNames = null;
+
+ // Cache the month names of a leap year that we retrieve from the data table.
+ [OptionalField(VersionAdded = 2)]
+ internal String[] leapYearMonthNames = null;
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+
+ // The "default" Date/time patterns
+ internal String longDatePattern = null;
+ internal String shortDatePattern = null;
+ internal String yearMonthPattern = null;
+ internal String longTimePattern = null;
+ internal String shortTimePattern = null;
+
+ // These are Whidbey-serialization compatable arrays (eg: default not included)
+ // "all" is a bit of a misnomer since the "default" pattern stored above isn't
+ // necessarily a member of the list
+ [OptionalField(VersionAdded = 3)]
+ private String[] allYearMonthPatterns = null; // This was wasn't serialized in Whidbey
+ internal String[] allShortDatePatterns = null;
+ internal String[] allLongDatePatterns = null;
+ internal String[] allShortTimePatterns = null;
+ internal String[] allLongTimePatterns = null;
+
+ // Cache the era names for this DateTimeFormatInfo instance.
+ internal String[] m_eraNames = null;
+ internal String[] m_abbrevEraNames = null;
+ internal String[] m_abbrevEnglishEraNames = null;
+
+ internal int[] optionalCalendars = null;
+
+ private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
+
+ // CultureInfo updates this
+ internal bool m_isReadOnly=false;
+
+ // This flag gives hints about if formatting/parsing should perform special code path for things like
+ // genitive form or leap year month names.
+ [OptionalField(VersionAdded = 2)]
+ internal DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized;
+ internal static bool preferExistingTokens = InitPreferExistingTokens();
+
+
+ [System.Security.SecuritySafeCritical]
+ static bool InitPreferExistingTokens()
+ {
+ bool ret = false;
+#if !FEATURE_CORECLR
+ ret = DateTime.LegacyParseMode();
+#endif
+ return ret;
+ }
+
+ private String CultureName
+ {
+ get
+ {
+ if (m_name == null)
+ {
+ m_name = this.m_cultureData.CultureName;
+ }
+ return (m_name);
+ }
+ }
+
+ private CultureInfo Culture
+ {
+ get
+ {
+ if (m_cultureInfo == null)
+ {
+ m_cultureInfo = CultureInfo.GetCultureInfo(this.CultureName);
+ }
+ return m_cultureInfo;
+ }
+ }
+
+ private String LanguageName
+ {
+ [System.Security.SecurityCritical] // auto-generated
+ get
+ {
+ if (m_langName == null)
+ {
+ m_langName = this.m_cultureData.SISO639LANGNAME;
+ }
+ return (m_langName);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Create an array of string which contains the abbreviated day names.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private String[] internalGetAbbreviatedDayOfWeekNames()
+ {
+ if (this.abbreviatedDayNames == null)
+ {
+ // Get the abbreviated day names for our current calendar
+ this.abbreviatedDayNames = this.m_cultureData.AbbreviatedDayNames(Calendar.ID);
+ Contract.Assert(this.abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 day names in a week");
+ }
+ return (this.abbreviatedDayNames);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Action: Returns the string array of the one-letter day of week names.
+ // Returns:
+ // an array of one-letter day of week names
+ // Arguments:
+ // None
+ // Exceptions:
+ // None
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ private String[] internalGetSuperShortDayNames()
+ {
+ if (this.m_superShortDayNames == null)
+ {
+ // Get the super short day names for our current calendar
+ this.m_superShortDayNames = this.m_cultureData.SuperShortDayNames(Calendar.ID);
+ Contract.Assert(this.m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.internalGetSuperShortDayNames] Expected 7 day names in a week");
+ }
+ return (this.m_superShortDayNames);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Create an array of string which contains the day names.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private String[] internalGetDayOfWeekNames()
+ {
+ if (this.dayNames == null)
+ {
+ // Get the day names for our current calendar
+ this.dayNames = this.m_cultureData.DayNames(Calendar.ID);
+ Contract.Assert(this.dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week");
+ }
+ return (this.dayNames);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Create an array of string which contains the abbreviated month names.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private String[] internalGetAbbreviatedMonthNames()
+ {
+ if (this.abbreviatedMonthNames == null)
+ {
+ // Get the month names for our current calendar
+ this.abbreviatedMonthNames = this.m_cultureData.AbbreviatedMonthNames(Calendar.ID);
+ Contract.Assert(this.abbreviatedMonthNames.Length == 12 || this.abbreviatedMonthNames.Length == 13,
+ "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year");
+ }
+ return (this.abbreviatedMonthNames);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Create an array of string which contains the month names.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private String[] internalGetMonthNames()
+ {
+ if (this.monthNames == null)
+ {
+ // Get the month names for our current calendar
+ this.monthNames = this.m_cultureData.MonthNames(Calendar.ID);
+ Contract.Assert(this.monthNames.Length == 12 || this.monthNames.Length == 13,
+ "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year");
+ }
+
+ return (this.monthNames);
+ }
+
+
+ //
+ // Invariant DateTimeFormatInfo doesn't have user-overriden values
+ // Default calendar is gregorian
+ public DateTimeFormatInfo()
+ : this(CultureInfo.InvariantCulture.m_cultureData,
+ GregorianCalendar.GetDefaultInstance())
+ {
+ }
+
+ internal DateTimeFormatInfo(CultureData cultureData, Calendar cal)
+ {
+ Contract.Requires(cultureData != null);
+ Contract.Requires(cal != null);
+
+ // Remember our culture
+ this.m_cultureData = cultureData;
+
+ // m_isDefaultCalendar is set in the setter of Calendar below.
+ this.Calendar = cal;
+ }
+
+#if !FEATURE_CORECLR
+ [System.Security.SecuritySafeCritical]
+#endif
+ private void InitializeOverridableProperties(CultureData cultureData, int calendarID)
+ {
+ // Silverlight 2.0 never took a snapshot of the user's overridable properties
+ // This has a substantial performance impact so skip when CoreCLR
+ Contract.Requires(cultureData != null);
+ Contract.Assert(calendarID > 0, "[DateTimeFormatInfo.Populate] Expected Calendar.ID > 0");
+
+ if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; }
+ if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; }
+
+ if (this.amDesignator == null) { this.amDesignator = cultureData.SAM1159; }
+ if (this.pmDesignator == null) { this.pmDesignator = cultureData.SPM2359; }
+ if (this.timeSeparator == null) { this.timeSeparator = cultureData.TimeSeparator; }
+ if (this.dateSeparator == null) { this.dateSeparator = cultureData.DateSeparator(calendarID); }
+
+ this.allLongTimePatterns = this.m_cultureData.LongTimes;
+ Contract.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns");
+
+ this.allShortTimePatterns = this.m_cultureData.ShortTimes;
+ Contract.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns");
+
+ this.allLongDatePatterns = cultureData.LongDates(calendarID);
+ Contract.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns");
+
+ this.allShortDatePatterns = cultureData.ShortDates(calendarID);
+ Contract.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns");
+
+ this.allYearMonthPatterns = cultureData.YearMonths(calendarID);
+ Contract.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns");
+ }
+
+#region Serialization
+ // The following fields are defined to keep the serialization compatibility with .NET V1.0/V1.1.
+ [OptionalField(VersionAdded = 1)]
+ private int CultureID;
+ [OptionalField(VersionAdded = 1)]
+ private bool m_useUserOverride;
+ [OptionalField(VersionAdded = 1)]
+ private bool bUseCalendarInfo;
+ [OptionalField(VersionAdded = 1)]
+ private int nDataItem;
+ [OptionalField(VersionAdded = 2)]
+ internal bool m_isDefaultCalendar; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
+ [OptionalField(VersionAdded = 2)]
+ private static volatile Hashtable s_calendarNativeNames; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey)
+
+ // This was synthesized by Whidbey so we knew what words might appear in the middle of a date string
+ // Now we always synthesize so its not helpful
+ [OptionalField(VersionAdded = 1)]
+ internal String[] m_dateWords = null; // calculated, no need to serialze (whidbey expects, arrowhead doesn't)
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ if (this.m_name != null)
+ {
+ m_cultureData = CultureData.GetCultureData(m_name, m_useUserOverride);
+
+ if (this.m_cultureData == null)
+ throw new CultureNotFoundException(
+ "m_name", m_name, Environment.GetResourceString("Argument_CultureNotSupported"));
+ }
+ // Note: This is for Everett compatibility
+
+#if FEATURE_USE_LCID
+ else
+ m_cultureData = CultureData.GetCultureData(CultureID, m_useUserOverride);
+#endif
+ if (calendar == null)
+ {
+ calendar = (Calendar) GregorianCalendar.GetDefaultInstance().Clone();
+ calendar.SetReadOnlyState(m_isReadOnly);
+ }
+ else
+ {
+ CultureInfo.CheckDomainSafetyObject(calendar, this);
+ }
+ InitializeOverridableProperties(m_cultureData, calendar.ID);
+
+ //
+ // turn off read only state till we finish initializing all fields and then store read only state after we are done.
+ //
+ bool isReadOnly = m_isReadOnly;
+ m_isReadOnly = false;
+
+ // If we deserialized defaults ala Whidbey, make sure they're still defaults
+ // Whidbey's arrays could get a bit mixed up.
+ if (longDatePattern != null) this.LongDatePattern = longDatePattern;
+ if (shortDatePattern != null) this.ShortDatePattern = shortDatePattern;
+ if (yearMonthPattern != null) this.YearMonthPattern = yearMonthPattern;
+ if (longTimePattern != null) this.LongTimePattern = longTimePattern;
+ if (shortTimePattern != null) this.ShortTimePattern = shortTimePattern;
+
+ m_isReadOnly = isReadOnly;
+ }
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+#if FEATURE_USE_LCID
+ CultureID = this.m_cultureData.ILANGUAGE; // Used for serialization compatibility with Whidbey which didn't always serialize the name
+#endif
+ m_useUserOverride = this.m_cultureData.UseUserOverride;
+
+ // make sure the m_name is initialized.
+ m_name = this.CultureName;
+
+#if !FEATURE_CORECLR
+ if (s_calendarNativeNames == null)
+ s_calendarNativeNames = new Hashtable();
+#endif // FEATURE_CORECLR
+
+ // Important to initialize these fields otherwise we may run into exception when deserializing on Whidbey
+ // because Whidbey try to initialize some of these fields using calendar data which could be null values
+ // and then we get exceptions. So we call the accessors to force the caches to get loaded.
+ Object o;
+ o = this.LongTimePattern;
+ o = this.LongDatePattern;
+ o = this.ShortTimePattern;
+ o = this.ShortDatePattern;
+ o = this.YearMonthPattern;
+ o = this.AllLongTimePatterns;
+ o = this.AllLongDatePatterns;
+ o = this.AllShortTimePatterns;
+ o = this.AllShortDatePatterns;
+ o = this.AllYearMonthPatterns;
+ }
+#endregion Serialization
+
+ // Returns a default DateTimeFormatInfo that will be universally
+ // supported and constant irrespective of the current culture.
+ // Used by FromString methods.
+ //
+
+ public static DateTimeFormatInfo InvariantInfo {
+ get {
+ Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null);
+ if (invariantInfo == null)
+ {
+ DateTimeFormatInfo info = new DateTimeFormatInfo();
+ info.Calendar.SetReadOnlyState(true);
+ info.m_isReadOnly = true;
+ invariantInfo = info;
+ }
+ return (invariantInfo);
+ }
+ }
+
+ // Returns the current culture's DateTimeFormatInfo. Used by Parse methods.
+ //
+
+ public static DateTimeFormatInfo CurrentInfo {
+ get {
+ Contract.Ensures(Contract.Result<DateTimeFormatInfo>() != null);
+ System.Globalization.CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture;
+ if (!culture.m_isInherited) {
+ DateTimeFormatInfo info = culture.dateTimeInfo;
+ if (info != null) {
+ return info;
+ }
+ }
+ return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo));
+ }
+ }
+
+
+ public static DateTimeFormatInfo GetInstance(IFormatProvider provider) {
+ // Fast case for a regular CultureInfo
+ DateTimeFormatInfo info;
+ CultureInfo cultureProvider = provider as CultureInfo;
+ if (cultureProvider != null && !cultureProvider.m_isInherited)
+ {
+ return cultureProvider.DateTimeFormat;
+ }
+ // Fast case for a DTFI;
+ info = provider as DateTimeFormatInfo;
+ if (info != null) {
+ return info;
+ }
+ // Wasn't cultureInfo or DTFI, do it the slower way
+ if (provider != null) {
+ info = provider.GetFormat(typeof(DateTimeFormatInfo)) as DateTimeFormatInfo;
+ if (info != null) {
+ return info;
+ }
+ }
+ // Couldn't get anything, just use currentInfo as fallback
+ return CurrentInfo;
+ }
+
+
+ public Object GetFormat(Type formatType)
+ {
+ return (formatType == typeof(DateTimeFormatInfo)? this: null);
+ }
+
+
+ public Object Clone()
+ {
+ DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone();
+ // We can use the data member calendar in the setter, instead of the property Calendar,
+ // since the cloned copy should have the same state as the original copy.
+ n.calendar = (Calendar) this.Calendar.Clone();
+ n.m_isReadOnly = false;
+ return n;
+ }
+
+
+ public String AMDesignator
+ {
+#if FEATURE_CORECLR
+ [System.Security.SecuritySafeCritical] // auto-generated
+#endif
+ get
+ {
+#if FEATURE_CORECLR
+ if (this.amDesignator == null)
+ {
+ this.amDesignator = this.m_cultureData.SAM1159;
+ }
+#endif
+ Contract.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
+ return (this.amDesignator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ ClearTokenHashTable();
+ amDesignator = value;
+ }
+ }
+
+
+ public Calendar Calendar {
+ get {
+ Contract.Ensures(Contract.Result<Calendar>() != null);
+
+ Contract.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
+ return (this.calendar);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Obj"));
+ }
+ Contract.EndContractBlock();
+ if (value == calendar) {
+ return;
+ }
+
+ //
+ // Because the culture is agile object which can be attached to a thread and then thread can travel
+ // to another app domain then we prevent attaching any customized object to culture that we cannot contol.
+ //
+ CultureInfo.CheckDomainSafetyObject(value, this);
+
+ for (int i = 0; i < this.OptionalCalendars.Length; i++)
+ {
+ if (this.OptionalCalendars[i] == value.ID)
+ {
+ // We can use this one, so do so.
+
+ // Clean related properties if we already had a calendar set
+ if (calendar != null)
+ {
+ // clean related properties which are affected by the calendar setting,
+ // so that they will be refreshed when they are accessed next time.
+ //
+
+ // These properites are in the order as appearing in calendar.xml.
+ m_eraNames = null;
+ m_abbrevEraNames = null;
+ m_abbrevEnglishEraNames = null;
+
+ monthDayPattern = null;
+
+ dayNames = null;
+ abbreviatedDayNames = null;
+ m_superShortDayNames = null;
+ monthNames = null;
+ abbreviatedMonthNames = null;
+ genitiveMonthNames = null;
+ m_genitiveAbbreviatedMonthNames = null;
+ leapYearMonthNames = null;
+ formatFlags = DateTimeFormatFlags.NotInitialized;
+
+ allShortDatePatterns = null;
+ allLongDatePatterns = null;
+ allYearMonthPatterns = null;
+ dateTimeOffsetPattern = null;
+
+ // The defaults need reset as well:
+ longDatePattern = null;
+ shortDatePattern = null;
+ yearMonthPattern = null;
+
+ // These properies are not in the OS data, but they are dependent on the values like shortDatePattern.
+ fullDateTimePattern = null; // Long date + long time
+ generalShortTimePattern = null; // short date + short time
+ generalLongTimePattern = null; // short date + long time
+
+ // Derived item that changes
+ dateSeparator = null;
+
+ // We don't need to do these because they are not changed by changing calendar
+ // amDesignator
+ // pmDesignator
+ // timeSeparator
+ // longTimePattern
+ // firstDayOfWeek
+ // calendarWeekRule
+
+ // We don't need to clear these because they're only used for whidbey compat serialization
+ // the only values we use are the all...Patterns[0]
+ // longDatePattern
+ // shortDatePattern
+ // yearMonthPattern
+
+ // remember to reload tokens
+ ClearTokenHashTable();
+ }
+
+ // Remember the new calendar
+ calendar = value;
+ InitializeOverridableProperties(m_cultureData, calendar.ID);
+
+ // We succeeded, return
+ return;
+ }
+ }
+
+ // The assigned calendar is not a valid calendar for this culture, throw
+ throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("Argument_InvalidCalendar"));
+ }
+ }
+
+ private int[] OptionalCalendars {
+ get {
+ if (this.optionalCalendars == null) {
+ this.optionalCalendars = this.m_cultureData.CalendarIds;
+ }
+ return (this.optionalCalendars);
+ }
+ }
+
+ /*=================================GetEra==========================
+ **Action: Get the era value by parsing the name of the era.
+ **Returns: The era value for the specified era name.
+ ** -1 if the name of the era is not valid or not supported.
+ **Arguments: eraName the name of the era.
+ **Exceptions: None.
+ ============================================================================*/
+
+
+ public int GetEra(String eraName) {
+ if (eraName == null) {
+ throw new ArgumentNullException("eraName",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ // For Geo-Political reasons, the Era Name and Abbreviated Era Name
+ // for Taiwan Calendar on non-Taiwan SKU returns empty string (which
+ // would be matched below) but we don't want the empty string to give
+ // us an Era number
+ // confer 85900 DTFI.GetEra("") should fail on all cultures
+ if (eraName.Length == 0) {
+ return (-1);
+ }
+
+ // The following is based on the assumption that the era value is starting from 1, and has a
+ // serial values.
+ // If that ever changes, the code has to be changed.
+
+ // The calls to String.Compare should use the current culture for the string comparisons, but the
+ // invariant culture when comparing against the english names.
+ for (int i = 0; i < EraNames.Length; i++) {
+ // Compare the era name in a case-insensitive way for the appropriate culture.
+ if (m_eraNames[i].Length > 0) {
+ if (String.Compare(eraName, m_eraNames[i], this.Culture, CompareOptions.IgnoreCase)==0) {
+ return (i+1);
+ }
+ }
+ }
+ for (int i = 0; i < AbbreviatedEraNames.Length; i++) {
+ // Compare the abbreviated era name in a case-insensitive way for the appropriate culture.
+ if (String.Compare(eraName, m_abbrevEraNames[i], this.Culture, CompareOptions.IgnoreCase)==0) {
+ return (i+1);
+ }
+ }
+ for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) {
+ // this comparison should use the InvariantCulture. The English name could have linguistically
+ // interesting characters.
+ if (String.Compare(eraName, m_abbrevEnglishEraNames[i], StringComparison.InvariantCultureIgnoreCase)==0) {
+ return (i+1);
+ }
+ }
+ return (-1);
+ }
+
+ internal String[] EraNames
+ {
+ get
+ {
+ if (this.m_eraNames == null)
+ {
+ this.m_eraNames = this.m_cultureData.EraNames(Calendar.ID);;
+ }
+ return (this.m_eraNames);
+ }
+ }
+
+ /*=================================GetEraName==========================
+ **Action: Get the name of the era for the specified era value.
+ **Returns: The name of the specified era.
+ **Arguments:
+ ** era the era value.
+ **Exceptions:
+ ** ArguementException if the era valie is invalid.
+ ============================================================================*/
+
+ // Era names are 1 indexed
+ public String GetEraName(int era) {
+ if (era == Calendar.CurrentEra) {
+ era = Calendar.CurrentEraValue;
+ }
+
+ // The following is based on the assumption that the era value is starting from 1, and has a
+ // serial values.
+ // If that ever changes, the code has to be changed.
+ if ((--era) < EraNames.Length && (era >= 0)) {
+ return (m_eraNames[era]);
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ internal String[] AbbreviatedEraNames
+ {
+ get
+ {
+ if (this.m_abbrevEraNames == null)
+ {
+ this.m_abbrevEraNames = this.m_cultureData.AbbrevEraNames(Calendar.ID);
+ }
+ return (this.m_abbrevEraNames);
+ }
+ }
+
+ // Era names are 1 indexed
+ public String GetAbbreviatedEraName(int era) {
+ if (AbbreviatedEraNames.Length == 0) {
+ // If abbreviation era name is not used in this culture,
+ // return the full era name.
+ return (GetEraName(era));
+ }
+ if (era == Calendar.CurrentEra) {
+ era = Calendar.CurrentEraValue;
+ }
+ if ((--era) < m_abbrevEraNames.Length && (era >= 0)) {
+ return (m_abbrevEraNames[era]);
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ internal String[] AbbreviatedEnglishEraNames
+ {
+ get
+ {
+ if (this.m_abbrevEnglishEraNames == null)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0");
+ this.m_abbrevEnglishEraNames = this.m_cultureData.AbbreviatedEnglishEraNames(Calendar.ID);
+ }
+ return (this.m_abbrevEnglishEraNames);
+ }
+ }
+
+
+ // Note that cultureData derives this from the short date format (unless someone's set this previously)
+ // Note that this property is quite undesirable.
+ public String DateSeparator
+ {
+ get
+ {
+#if FEATURE_CORECLR
+ if (this.dateSeparator == null)
+ {
+ this.dateSeparator = this.m_cultureData.DateSeparator(Calendar.ID);
+ }
+#endif
+ Contract.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
+ return (this.dateSeparator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ ClearTokenHashTable();
+ this.dateSeparator = value;
+ }
+ }
+
+
+ public DayOfWeek FirstDayOfWeek
+ {
+ get
+ {
+#if FEATURE_CORECLR
+ if (this.firstDayOfWeek == -1)
+ {
+ this.firstDayOfWeek = this.m_cultureData.IFIRSTDAYOFWEEK;
+ }
+#endif
+ Contract.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
+
+ return ((DayOfWeek)this.firstDayOfWeek);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday) {
+ firstDayOfWeek = (int)value;
+ } else {
+ throw new ArgumentOutOfRangeException(
+ "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ }
+ }
+
+
+ public CalendarWeekRule CalendarWeekRule
+ {
+ get
+ {
+#if FEATURE_CORECLR
+ if (this.calendarWeekRule == -1)
+ {
+ this.calendarWeekRule = this.m_cultureData.IFIRSTWEEKOFYEAR;
+ }
+#endif
+ Contract.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
+ return ((CalendarWeekRule)this.calendarWeekRule);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek) {
+ calendarWeekRule = (int)value;
+ } else {
+ throw new ArgumentOutOfRangeException(
+ "value", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
+ }
+ }
+ }
+
+
+
+ public String FullDateTimePattern
+ {
+ get
+ {
+ if (fullDateTimePattern == null)
+ {
+ fullDateTimePattern = LongDatePattern + " " + LongTimePattern;
+ }
+ return (fullDateTimePattern);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ fullDateTimePattern = value;
+ }
+ }
+
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String LongDatePattern
+ {
+ get
+ {
+ // Initialize our long date pattern from the 1st array value if not set
+ if (this.longDatePattern == null)
+ {
+ // Initialize our data
+ this.longDatePattern = this.UnclonedLongDatePatterns[0];
+ }
+
+ return this.longDatePattern;
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ // Remember the new string
+ this.longDatePattern = value;
+
+ // Clear the token hash table
+ ClearTokenHashTable();
+
+ // Clean up cached values that will be affected by this property.
+ this.fullDateTimePattern = null;
+ }
+ }
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String LongTimePattern
+ {
+ get
+ {
+ // Initialize our long time pattern from the 1st array value if not set
+ if (this.longTimePattern == null)
+ {
+ // Initialize our data
+ this.longTimePattern = this.UnclonedLongTimePatterns[0];
+ }
+
+ return this.longTimePattern;
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ // Remember the new string
+ this.longTimePattern = value;
+
+ // Clear the token hash table
+ ClearTokenHashTable();
+
+ // Clean up cached values that will be affected by this property.
+ this.fullDateTimePattern = null; // Full date = long date + long Time
+ this.generalLongTimePattern = null; // General long date = short date + long Time
+ this.dateTimeOffsetPattern = null;
+ }
+ }
+
+
+ // Note: just to be confusing there's only 1 month day pattern, not a whole list
+ public String MonthDayPattern
+ {
+ get
+ {
+ if (this.monthDayPattern == null)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
+ this.monthDayPattern = this.m_cultureData.MonthDay(Calendar.ID);
+ }
+ Contract.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
+ return (this.monthDayPattern);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ this.monthDayPattern = value;
+ }
+ }
+
+
+ public String PMDesignator
+ {
+#if FEATURE_CORECLR
+ [System.Security.SecuritySafeCritical] // auto-generated
+#endif
+ get
+ {
+#if FEATURE_CORECLR
+ if (this.pmDesignator == null)
+ {
+ this.pmDesignator = this.m_cultureData.SPM2359;
+ }
+#endif
+ Contract.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
+ return (this.pmDesignator);
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ ClearTokenHashTable();
+
+ pmDesignator = value;
+ }
+
+ }
+
+
+ public String RFC1123Pattern
+ {
+ get
+ {
+ return (rfc1123Pattern);
+ }
+ }
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String ShortDatePattern
+ {
+ get
+ {
+ // Initialize our short date pattern from the 1st array value if not set
+ if (this.shortDatePattern == null)
+ {
+ // Initialize our data
+ this.shortDatePattern = this.UnclonedShortDatePatterns[0];
+ }
+
+ return this.shortDatePattern;
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ Contract.EndContractBlock();
+
+ // Remember the new string
+ this.shortDatePattern = value;
+
+ // Clear the token hash table, note that even short dates could require this
+ ClearTokenHashTable();
+
+ // Clean up cached values that will be affected by this property.
+ generalLongTimePattern = null; // General long time = short date + long time
+ generalShortTimePattern = null; // General short time = short date + short Time
+ dateTimeOffsetPattern = null;
+ }
+ }
+
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String ShortTimePattern
+ {
+ get
+ {
+ // Initialize our short time pattern from the 1st array value if not set
+ if (this.shortTimePattern == null)
+ {
+ // Initialize our data
+ this.shortTimePattern = this.UnclonedShortTimePatterns[0];
+ }
+ return this.shortTimePattern;
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ // Remember the new string
+ this.shortTimePattern= value;
+
+ // Clear the token hash table, note that even short times could require this
+ ClearTokenHashTable();
+
+ // Clean up cached values that will be affected by this property.
+ generalShortTimePattern = null; // General short date = short date + short time.
+ }
+ }
+
+
+ public String SortableDateTimePattern {
+ get {
+ return (sortableDateTimePattern);
+ }
+ }
+
+ /*=================================GeneralShortTimePattern=====================
+ **Property: Return the pattern for 'g' general format: shortDate + short time
+ **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
+ ** We put this internal property here so that we can avoid doing the
+ ** concatation every time somebody asks for the general format.
+ ==============================================================================*/
+
+ internal String GeneralShortTimePattern {
+ get {
+ if (generalShortTimePattern == null) {
+ generalShortTimePattern = ShortDatePattern + " " + ShortTimePattern;
+ }
+ return (generalShortTimePattern);
+ }
+ }
+
+ /*=================================GeneralLongTimePattern=====================
+ **Property: Return the pattern for 'g' general format: shortDate + Long time
+ **Note: This is used by DateTimeFormat.cs to get the pattern for 'g'
+ ** We put this internal property here so that we can avoid doing the
+ ** concatation every time somebody asks for the general format.
+ ==============================================================================*/
+
+ internal String GeneralLongTimePattern {
+ get {
+ if (generalLongTimePattern == null) {
+ generalLongTimePattern = ShortDatePattern + " " + LongTimePattern;
+ }
+ return (generalLongTimePattern);
+ }
+ }
+
+ /*=================================DateTimeOffsetPattern==========================
+ **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
+ **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset
+ ** We put this internal property here so that we can avoid doing the
+ ** concatation every time somebody uses this form
+ ==============================================================================*/
+
+ /*=================================DateTimeOffsetPattern==========================
+ **Property: Return the default pattern DateTimeOffset : shortDate + long time + time zone offset
+ **Note: This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset
+ ** We put this internal property here so that we can avoid doing the
+ ** concatation every time somebody uses this form
+ ==============================================================================*/
+
+ internal String DateTimeOffsetPattern {
+ get {
+ if (dateTimeOffsetPattern == null) {
+
+ string dateTimePattern = ShortDatePattern + " " + LongTimePattern;
+
+ /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to append a time zone offset */
+
+ bool foundZ = false;
+ bool inQuote = false;
+ char quote = '\'';
+ for (int i = 0; !foundZ && i < LongTimePattern.Length; i++) {
+ switch (LongTimePattern[i]) {
+ case 'z':
+ /* if we aren't in a quote, we've found a z */
+ foundZ = !inQuote;
+ /* we'll fall out of the loop now because the test includes !foundZ */
+ break;
+ case '\'':
+ case '\"':
+ if (inQuote && (quote == LongTimePattern[i])) {
+ /* we were in a quote and found a matching exit quote, so we are outside a quote now */
+ inQuote = false;
+ } else if (!inQuote) {
+ quote = LongTimePattern[i];
+ inQuote = true;
+ } else {
+ /* we were in a quote and saw the other type of quote character, so we are still in a quote */
+ }
+ break;
+ case '%':
+ case '\\':
+ i++; /* skip next character that is escaped by this backslash */
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!foundZ) {
+ dateTimePattern = dateTimePattern + " zzz";
+ }
+
+ dateTimeOffsetPattern = dateTimePattern;
+ }
+ return (dateTimeOffsetPattern);
+ }
+ }
+
+ // Note that cultureData derives this from the long time format (unless someone's set this previously)
+ // Note that this property is quite undesirable.
+ public String TimeSeparator
+ {
+ get
+ {
+#if FEATURE_CORECLR
+ if (timeSeparator == null)
+ {
+ timeSeparator = this.m_cultureData.TimeSeparator;
+ }
+#endif
+ Contract.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
+ return (timeSeparator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ ClearTokenHashTable();
+
+ timeSeparator = value;
+ }
+ }
+
+
+ public String UniversalSortableDateTimePattern
+ {
+ get
+ {
+ return (universalSortableDateTimePattern);
+ }
+ }
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ public String YearMonthPattern
+ {
+ get
+ {
+ // Initialize our year/month pattern from the 1st array value if not set
+ if (this.yearMonthPattern == null)
+ {
+ // Initialize our data
+ this.yearMonthPattern = this.UnclonedYearMonthPatterns[0];
+ }
+ return this.yearMonthPattern;
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ // Remember the new string
+ this.yearMonthPattern = value;
+
+ // Clear the token hash table, note that even short times could require this
+ ClearTokenHashTable();
+ }
+ }
+
+ //
+ // Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value"
+ //
+ static private void CheckNullValue(String[] values, int length) {
+ Contract.Requires(values != null, "value != null");
+ Contract.Requires(values.Length >= length);
+ for (int i = 0; i < length; i++) {
+ if (values[i] == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_ArrayValue"));
+ }
+ }
+ }
+
+
+ public String[] AbbreviatedDayNames
+ {
+ get
+ {
+ return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone());
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 7) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length);
+ ClearTokenHashTable();
+
+ abbreviatedDayNames = value;
+ }
+ }
+
+
+ // Returns the string array of the one-letter day of week names.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String[] ShortestDayNames
+ {
+ get
+ {
+ return ((String[])internalGetSuperShortDayNames().Clone());
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length);
+ this.m_superShortDayNames = value;
+ }
+ }
+
+
+ public String[] DayNames
+ {
+ get
+ {
+ return ((String[])internalGetDayOfWeekNames().Clone());
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 7), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length);
+ ClearTokenHashTable();
+
+ dayNames = value;
+ }
+ }
+
+
+ public String[] AbbreviatedMonthNames {
+ get {
+ return ((String[])internalGetAbbreviatedMonthNames().Clone());
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length - 1);
+ ClearTokenHashTable();
+ abbreviatedMonthNames = value;
+ }
+ }
+
+
+ public String[] MonthNames
+ {
+ get
+ {
+ return ((String[])internalGetMonthNames().Clone());
+ }
+
+ set {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null) {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length - 1);
+ monthNames = value;
+ ClearTokenHashTable();
+ }
+ }
+
+ // Whitespaces that we allow in the month names.
+ // U+00a0 is non-breaking space.
+ static char[] MonthSpaces = {' ', '\u00a0'};
+
+ internal bool HasSpacesInMonthNames {
+ get {
+ return (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0;
+ }
+ }
+
+ internal bool HasSpacesInDayNames {
+ get {
+ return (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0;
+ }
+ }
+
+
+ //
+ // internalGetMonthName
+ //
+ // Actions: Return the month name using the specified MonthNameStyles in either abbreviated form
+ // or full form.
+ // Arguments:
+ // month
+ // style To indicate a form like regular/genitive/month name in a leap year.
+ // abbreviated When true, return abbreviated form. Otherwise, return a full form.
+ // Exceptions:
+ // ArgumentOutOfRangeException When month name is invalid.
+ //
+ internal String internalGetMonthName(int month, MonthNameStyles style, bool abbreviated) {
+ //
+ // Right now, style is mutual exclusive, but I make the style to be flag so that
+ // maybe we can combine flag if there is such a need.
+ //
+ String[] monthNamesArray = null;
+ switch (style) {
+ case MonthNameStyles.Genitive:
+ monthNamesArray = internalGetGenitiveMonthNames(abbreviated);
+ break;
+ case MonthNameStyles.LeapYear:
+ monthNamesArray = internalGetLeapYearMonthNames(/*abbreviated*/);
+ break;
+ default:
+ monthNamesArray = (abbreviated ? internalGetAbbreviatedMonthNames(): internalGetMonthNames());
+ break;
+ }
+ // The month range is from 1 ~ this.m_monthNames.Length
+ // (actually is 13 right now for all cases)
+ if ((month < 1) || (month > monthNamesArray.Length)) {
+ throw new ArgumentOutOfRangeException(
+ "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ 1, monthNamesArray.Length));
+ }
+ return (monthNamesArray[month-1]);
+ }
+
+ //
+ // internalGetGenitiveMonthNames
+ //
+ // Action: Retrieve the array which contains the month names in genitive form.
+ // If this culture does not use the gentive form, the normal month name is returned.
+ // Arguments:
+ // abbreviated When true, return abbreviated form. Otherwise, return a full form.
+ //
+ private String[] internalGetGenitiveMonthNames(bool abbreviated) {
+ if (abbreviated) {
+ if (this.m_genitiveAbbreviatedMonthNames == null)
+ {
+ this.m_genitiveAbbreviatedMonthNames = this.m_cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID);
+ Contract.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13,
+ "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a year");
+ }
+ return (this.m_genitiveAbbreviatedMonthNames);
+ }
+
+ if (this.genitiveMonthNames == null)
+ {
+ this.genitiveMonthNames = this.m_cultureData.GenitiveMonthNames(this.Calendar.ID);
+ Contract.Assert(this.genitiveMonthNames.Length == 13,
+ "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year");
+ }
+ return (this.genitiveMonthNames);
+ }
+
+ //
+ // internalGetLeapYearMonthNames
+ //
+ // Actions: Retrieve the month names used in a leap year.
+ // If this culture does not have different month names in a leap year, the normal month name is returned.
+ // Agruments: None. (can use abbreviated later if needed)
+ //
+ internal String[] internalGetLeapYearMonthNames(/*bool abbreviated*/) {
+ if (this.leapYearMonthNames == null)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0");
+ this.leapYearMonthNames = this.m_cultureData.LeapYearMonthNames(Calendar.ID);
+ Contract.Assert(this.leapYearMonthNames.Length == 13,
+ "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expepcted 13 leap year month names");
+ }
+ return (leapYearMonthNames);
+ }
+
+
+ public String GetAbbreviatedDayName(DayOfWeek dayofweek)
+ {
+
+ if ((int)dayofweek < 0 || (int)dayofweek > 6) {
+ throw new ArgumentOutOfRangeException(
+ "dayofweek", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ Contract.EndContractBlock();
+ //
+ // Don't call the public property AbbreviatedDayNames here since a clone is needed in that
+ // property, so it will be slower. Instead, use GetAbbreviatedDayOfWeekNames() directly.
+ //
+ return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]);
+ }
+
+
+ // Returns the super short day of week names for the specified day of week.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String GetShortestDayName(DayOfWeek dayOfWeek)
+ {
+
+ if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6) {
+ throw new ArgumentOutOfRangeException(
+ "dayOfWeek", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ Contract.EndContractBlock();
+ //
+ // Don't call the public property SuperShortDayNames here since a clone is needed in that
+ // property, so it will be slower. Instead, use internalGetSuperShortDayNames() directly.
+ //
+ return (internalGetSuperShortDayNames()[(int)dayOfWeek]);
+ }
+
+ // Get all possible combination of inputs
+ static private String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString)
+ {
+ Contract.Requires(patterns1 != null);
+ Contract.Requires(patterns2 != null);
+
+ // Get array size
+ String[] result = new String[patterns1.Length * patterns2.Length];
+
+ // Counter of actual results
+ int k = 0;
+ for (int i = 0; i < patterns1.Length; i++)
+ {
+ for (int j = 0; j < patterns2.Length; j++)
+ {
+ // Can't combine if null or empty
+ result[k++] = patterns1[i] + connectString + patterns2[j];
+ }
+ }
+
+ // Return the combinations
+ return (result);
+ }
+
+
+ public String[] GetAllDateTimePatterns()
+ {
+ List<String> results = new List<String>(DEFAULT_ALL_DATETIMES_SIZE);
+
+ for (int i = 0; i < DateTimeFormat.allStandardFormats.Length; i++)
+ {
+ String[] strings = GetAllDateTimePatterns(DateTimeFormat.allStandardFormats[i]);
+ for (int j = 0; j < strings.Length; j++)
+ {
+ results.Add(strings[j]);
+ }
+ }
+ return results.ToArray();
+ }
+
+
+ public String[] GetAllDateTimePatterns(char format)
+ {
+ Contract.Ensures(Contract.Result<String[]>() != null);
+ String [] result = null;
+
+ switch (format)
+ {
+ case 'd':
+ result = this.AllShortDatePatterns;
+ break;
+ case 'D':
+ result = this.AllLongDatePatterns;
+ break;
+ case 'f':
+ result = GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " ");
+ break;
+ case 'F':
+ case 'U':
+ result = GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " ");
+ break;
+ case 'g':
+ result = GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " ");
+ break;
+ case 'G':
+ result = GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " ");
+ break;
+ case 'm':
+ case 'M':
+ result = new String[] {MonthDayPattern};
+ break;
+ case 'o':
+ case 'O':
+ result = new String[] {DateTimeFormat.RoundtripFormat};
+ break;
+ case 'r':
+ case 'R':
+ result = new String[] {rfc1123Pattern};
+ break;
+ case 's':
+ result = new String[] {sortableDateTimePattern};
+ break;
+ case 't':
+ result = this.AllShortTimePatterns;
+ break;
+ case 'T':
+ result = this.AllLongTimePatterns;
+ break;
+ case 'u':
+ result = new String[] {UniversalSortableDateTimePattern};
+ break;
+ case 'y':
+ case 'Y':
+ result = this.AllYearMonthPatterns;
+ break;
+ default:
+ throw new ArgumentException(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
+ }
+ return (result);
+ }
+
+
+ public String GetDayName(DayOfWeek dayofweek)
+ {
+ if ((int)dayofweek < 0 || (int)dayofweek > 6) {
+ throw new ArgumentOutOfRangeException(
+ "dayofweek", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ Contract.EndContractBlock();
+
+ // Use the internal one so that we don't clone the array unnecessarily
+ return (internalGetDayOfWeekNames()[(int)dayofweek]);
+ }
+
+
+
+ public String GetAbbreviatedMonthName(int month)
+ {
+ if (month < 1 || month > 13) {
+ throw new ArgumentOutOfRangeException(
+ "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ 1, 13));
+ }
+ Contract.EndContractBlock();
+ // Use the internal one so we don't clone the array unnecessarily
+ return (internalGetAbbreviatedMonthNames()[month-1]);
+ }
+
+
+ public String GetMonthName(int month)
+ {
+ if (month < 1 || month > 13) {
+ throw new ArgumentOutOfRangeException(
+ "month", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ 1, 13));
+ }
+ Contract.EndContractBlock();
+ // Use the internal one so we don't clone the array unnecessarily
+ return (internalGetMonthNames()[month-1]);
+ }
+
+ // For our "patterns" arrays we have 2 variables, a string and a string[]
+ //
+ // The string[] contains the list of patterns, EXCEPT the default may not be included.
+ // The string contains the default pattern.
+ // When we initially construct our string[], we set the string to string[0]
+ //
+ // The resulting [] can get returned to the calling app, so clone it.
+ private static string[] GetMergedPatterns(string [] patterns, string defaultPattern)
+ {
+ Contract.Assert(patterns != null && patterns.Length > 0,
+ "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern");
+ Contract.Assert(defaultPattern != null,
+ "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string");
+
+ // If the default happens to be the first in the list just return (a cloned) copy
+ if (defaultPattern == patterns[0])
+ {
+ return (string[])patterns.Clone();
+ }
+
+ // We either need a bigger list, or the pattern from the list.
+ int i;
+ for (i = 0; i < patterns.Length; i++)
+ {
+ // Stop if we found it
+ if (defaultPattern == patterns[i])
+ break;
+ }
+
+ // Either way we're going to need a new array
+ string[] newPatterns;
+
+ // Did we find it
+ if (i < patterns.Length)
+ {
+ // Found it, output will be same size
+ newPatterns = (string[])patterns.Clone();
+
+ // Have to move [0] item to [i] so we can re-write default at [0]
+ // (remember defaultPattern == [i] so this is OK)
+ newPatterns[i] = newPatterns[0];
+ }
+ else
+ {
+ // Not found, make room for it
+ newPatterns = new String[patterns.Length + 1];
+
+ // Copy existing array
+ Array.Copy(patterns, 0, newPatterns, 1, patterns.Length);
+ }
+
+ // Remember the default
+ newPatterns[0] = defaultPattern;
+
+ // Return the reconstructed list
+ return newPatterns;
+ }
+
+ // Default string isn't necessarily in our string array, so get the
+ // merged patterns of both
+ private String[] AllYearMonthPatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedYearMonthPatterns, this.YearMonthPattern);
+ }
+ }
+
+ private String[] AllShortDatePatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedShortDatePatterns, this.ShortDatePattern);
+ }
+ }
+
+ private String[] AllShortTimePatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedShortTimePatterns, this.ShortTimePattern);
+ }
+ }
+
+ private String[] AllLongDatePatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedLongDatePatterns, this.LongDatePattern);
+ }
+ }
+
+ private String[] AllLongTimePatterns
+ {
+ get
+ {
+ return GetMergedPatterns(this.UnclonedLongTimePatterns, this.LongTimePattern);
+ }
+ }
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllYearMonthPatterns
+ private String[] UnclonedYearMonthPatterns
+ {
+ get
+ {
+ if (this.allYearMonthPatterns == null)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0");
+ this.allYearMonthPatterns = this.m_cultureData.YearMonths(this.Calendar.ID);
+ Contract.Assert(this.allYearMonthPatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns");
+ }
+
+ return this.allYearMonthPatterns;
+ }
+ }
+
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllShortDatePatterns
+ private String [] UnclonedShortDatePatterns
+ {
+ get
+ {
+ if (allShortDatePatterns == null)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0");
+ this.allShortDatePatterns = this.m_cultureData.ShortDates(this.Calendar.ID);
+ Contract.Assert(this.allShortDatePatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns");
+ }
+
+ return this.allShortDatePatterns;
+ }
+ }
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllLongDatePatterns
+ private String[] UnclonedLongDatePatterns
+ {
+ get
+ {
+ if (allLongDatePatterns == null)
+ {
+ Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0");
+ this.allLongDatePatterns = this.m_cultureData.LongDates(this.Calendar.ID);
+ Contract.Assert(this.allLongDatePatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns");
+ }
+
+ return this.allLongDatePatterns;
+ }
+ }
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllShortTimePatterns
+ private String[] UnclonedShortTimePatterns
+ {
+ get
+ {
+ if (this.allShortTimePatterns == null)
+ {
+ this.allShortTimePatterns = this.m_cultureData.ShortTimes;
+ Contract.Assert(this.allShortTimePatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns");
+ }
+
+ return this.allShortTimePatterns;
+ }
+ }
+
+ // NOTE: Clone this string array if you want to return it to user. Otherwise, you are returning a writable cache copy.
+ // This won't include default, call AllLongTimePatterns
+ private String[] UnclonedLongTimePatterns
+ {
+ get
+ {
+ if (this.allLongTimePatterns == null)
+ {
+ this.allLongTimePatterns = this.m_cultureData.LongTimes;
+ Contract.Assert(this.allLongTimePatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns");
+ }
+
+ return this.allLongTimePatterns;
+ }
+ }
+
+ public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi) {
+ if (dtfi == null) {
+ throw new ArgumentNullException("dtfi",
+ Environment.GetResourceString("ArgumentNull_Obj"));
+ }
+ Contract.EndContractBlock();
+ if (dtfi.IsReadOnly) {
+ return (dtfi);
+ }
+ DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone());
+ // We can use the data member calendar in the setter, instead of the property Calendar,
+ // since the cloned copy should have the same state as the original copy.
+ newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar);
+ newInfo.m_isReadOnly = true;
+ return (newInfo);
+ }
+
+
+ public bool IsReadOnly {
+ get {
+ return (m_isReadOnly);
+ }
+ }
+
+ // Return the native name for the calendar in DTFI.Calendar. The native name is referred to
+ // the culture used to create the DTFI. E.g. in the following example, the native language is Japanese.
+ // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar();
+ // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar.
+ // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(GregorianCalendarTypes.Localized);
+ // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String NativeCalendarName
+ {
+ get
+ {
+ return m_cultureData.CalendarName(Calendar.ID);
+ }
+ }
+
+ //
+ // Used by custom cultures and others to set the list of available formats. Note that none of them are
+ // explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items
+ // from the list.
+ //
+ // Most of the format characters that can be used in GetAllDateTimePatterns are
+ // not really needed since they are one of the following:
+ //
+ // r/R/s/u locale-independent constants -- cannot be changed!
+ // m/M/y/Y fields with a single string in them -- that can be set through props directly
+ // f/F/g/G/U derived fields based on combinations of various of the below formats
+ //
+ // NOTE: No special validation is done here beyond what is done when the actual respective fields
+ // are used (what would be the point of disallowing here what we allow in the appropriate property?)
+ //
+ // WARNING: If more validation is ever done in one place, it should be done in the other.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public void SetAllDateTimePatterns(String[] patterns, char format)
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (patterns == null) {
+ throw new ArgumentNullException("patterns",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+
+ if (patterns.Length == 0)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Arg_ArrayZeroError"), "patterns");
+ }
+ Contract.EndContractBlock();
+
+ for (int i=0; i<patterns.Length; i++)
+ {
+ if (patterns[i] == null)
+ {
+ throw new ArgumentNullException("patterns[" + i + "]", Environment.GetResourceString("ArgumentNull_ArrayValue"));
+ }
+ }
+
+ // Remember the patterns, and use the 1st as default
+ switch (format)
+ {
+ case 'd':
+ this.allShortDatePatterns = patterns;
+ this.shortDatePattern = this.allShortDatePatterns[0];
+ break;
+
+ case 'D':
+ this.allLongDatePatterns = patterns;
+ this.longDatePattern = this.allLongDatePatterns[0];
+ break;
+
+ case 't':
+ this.allShortTimePatterns = patterns;
+ this.shortTimePattern = this.allShortTimePatterns[0];
+ break;
+
+ case 'T':
+ this.allLongTimePatterns = patterns;
+ this.longTimePattern = this.allLongTimePatterns[0];
+ break;
+
+ case 'y':
+ case 'Y':
+ this.allYearMonthPatterns = patterns;
+ this.yearMonthPattern = this.allYearMonthPatterns[0];
+ break;
+
+ default:
+ throw new ArgumentException(Environment.GetResourceString("Format_BadFormatSpecifier"), "format");
+ }
+
+ // Clear the token hash table, note that even short dates could require this
+ ClearTokenHashTable();
+
+ return;
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String[] AbbreviatedMonthGenitiveNames
+ {
+ get
+ {
+ return ((String[])internalGetGenitiveMonthNames(true).Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length - 1);
+ ClearTokenHashTable();
+ this.m_genitiveAbbreviatedMonthNames= value;
+ }
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String[] MonthGenitiveNames
+ {
+ get
+ {
+ return ((String[])internalGetGenitiveMonthNames(false).Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ if (value == null)
+ {
+ throw new ArgumentNullException("value",
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidArrayLength", 13), "value");
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length - 1);
+ genitiveMonthNames= value;
+ ClearTokenHashTable();
+ }
+ }
+
+ //
+ // Positive TimeSpan Pattern
+ //
+ [NonSerialized]
+ private string m_fullTimeSpanPositivePattern;
+ internal String FullTimeSpanPositivePattern
+ {
+ get
+ {
+ if (m_fullTimeSpanPositivePattern == null)
+ {
+ CultureData cultureDataWithoutUserOverrides;
+ if (m_cultureData.UseUserOverride)
+ cultureDataWithoutUserOverrides = CultureData.GetCultureData(m_cultureData.CultureName, false);
+ else
+ cultureDataWithoutUserOverrides = m_cultureData;
+ String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator;
+
+ m_fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF";
+ }
+ return m_fullTimeSpanPositivePattern;
+ }
+ }
+
+ //
+ // Negative TimeSpan Pattern
+ //
+ [NonSerialized]
+ private string m_fullTimeSpanNegativePattern;
+ internal String FullTimeSpanNegativePattern
+ {
+ get
+ {
+ if (m_fullTimeSpanNegativePattern == null)
+ m_fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern;
+ return m_fullTimeSpanNegativePattern;
+ }
+ }
+
+ //
+ // Get suitable CompareInfo from current DTFI object.
+ //
+ internal CompareInfo CompareInfo
+ {
+ get
+ {
+ if (m_compareInfo == null)
+ {
+ // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the
+ // CompareInfo cache. otherwise we would just create CompareInfo using m_cultureData.
+ m_compareInfo = CompareInfo.GetCompareInfo(m_cultureData.SCOMPAREINFO);
+ }
+
+ return m_compareInfo;
+ }
+ }
+
+
+ internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite
+ | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurrentDateDefault
+ | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal
+ | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind);
+
+ internal static void ValidateStyles(DateTimeStyles style, String parameterName) {
+ if ((style & InvalidDateTimeStyles) != 0) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidDateTimeStyles"), parameterName);
+ }
+ if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0)) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_ConflictingDateTimeStyles"), parameterName);
+ }
+ Contract.EndContractBlock();
+ if (((style & DateTimeStyles.RoundtripKind) != 0)
+ && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0)) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_ConflictingDateTimeRoundtripStyles"), parameterName);
+ }
+ }
+
+ //
+ // Actions: Return the internal flag used in formatting and parsing.
+ // The flag can be used to indicate things like if genitive forms is used in this DTFi, or if leap year gets different month names.
+ //
+ internal DateTimeFormatFlags FormatFlags
+ {
+ get
+ {
+ if (formatFlags == DateTimeFormatFlags.NotInitialized)
+ {
+ // Build the format flags from the data in this DTFI
+ formatFlags = DateTimeFormatFlags.None;
+ formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth(
+ MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true));
+ formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames(
+ MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true));
+ formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayNames);
+ formatFlags |= (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID);
+ }
+ return (formatFlags);
+ }
+ }
+
+ internal Boolean HasForceTwoDigitYears {
+ get {
+ switch (calendar.ID)
+ {
+ // If is y/yy, do not get (year % 100). "y" will print
+ // year without leading zero. "yy" will print year with two-digit in leading zero.
+ // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero.
+ // So year 5 is "05", and year 125 is "125".
+ // The reason for not doing (year % 100) is for Taiwan calendar.
+ // If year 125, then output 125 and not 25.
+ // Note: OS uses "yyyy" for Taiwan calendar by default.
+ case (Calendar.CAL_JAPAN):
+ case (Calendar.CAL_TAIWAN):
+ return true;
+ }
+ return false;
+ }
+ }
+
+ // Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar.
+ internal Boolean HasYearMonthAdjustment {
+ get {
+ return ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0);
+ }
+ }
+
+ // This is a callback that the parser can make back into the DTFI to let it fiddle with special
+ // cases associated with that culture or calendar. Currently this only has special cases for
+ // the Hebrew calendar, but this could be extended to other cultures.
+ //
+ // The return value is whether the year and month are actually valid for this calendar.
+ internal Boolean YearMonthAdjustment(ref int year, ref int month, Boolean parsedMonthName) {
+ if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) {
+
+ // Special rules to fix up the Hebrew year/month
+
+ // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000.
+ // E.g. if the year is 5763, we only format as 763.
+ if (year < 1000) {
+ year += 5000;
+ }
+
+ // Because we need to calculate leap year, we should fall out now for an invalid year.
+ if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSupportedDateTime)) {
+ return false;
+ }
+
+ // To handle leap months, the set of month names in the symbol table does not always correspond to the numbers.
+ // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid and
+ // shuffle the other months down.
+ if (parsedMonthName) {
+ if (!Calendar.IsLeapYear(year)) {
+ if (month >= 8) {
+ month--;
+ }
+ else if (month == 7) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ //
+ // DateTimeFormatInfo tokenizer. This is used by DateTime.Parse() to break input string into tokens.
+ //
+ [NonSerialized]
+ TokenHashValue[] m_dtfiTokenHash;
+
+ private const int TOKEN_HASH_SIZE = 199;
+ private const int SECOND_PRIME = 197;
+ private const String dateSeparatorOrTimeZoneOffset = "-";
+ private const String invariantDateSeparator = "/";
+ private const String invariantTimeSeparator = ":";
+
+ //
+ // Common Ignorable Symbols
+ //
+ internal const String IgnorablePeriod = ".";
+ internal const String IgnorableComma = ",";
+
+ //
+ // Year/Month/Day suffixes
+ //
+ internal const String CJKYearSuff = "\u5e74";
+ internal const String CJKMonthSuff = "\u6708";
+ internal const String CJKDaySuff = "\u65e5";
+
+ internal const String KoreanYearSuff = "\ub144";
+ internal const String KoreanMonthSuff = "\uc6d4";
+ internal const String KoreanDaySuff = "\uc77c";
+
+ internal const String KoreanHourSuff = "\uc2dc";
+ internal const String KoreanMinuteSuff = "\ubd84";
+ internal const String KoreanSecondSuff = "\ucd08";
+
+ internal const String CJKHourSuff = "\u6642";
+ internal const String ChineseHourSuff = "\u65f6";
+
+ internal const String CJKMinuteSuff = "\u5206";
+ internal const String CJKSecondSuff = "\u79d2";
+
+ internal const String LocalTimeMark = "T";
+
+ internal const String KoreanLangName = "ko";
+ internal const String JapaneseLangName = "ja";
+ internal const String EnglishLangName = "en";
+
+ private static volatile DateTimeFormatInfo s_jajpDTFI;
+ private static volatile DateTimeFormatInfo s_zhtwDTFI;
+
+ //
+ // Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse
+ // date string with Japanese era name correctly even when the supplied DTFI
+ // does not use Japanese calendar.
+ // The created instance is stored in global s_jajpDTFI.
+ //
+ internal static DateTimeFormatInfo GetJapaneseCalendarDTFI() {
+ DateTimeFormatInfo temp = s_jajpDTFI;
+ if (temp == null) {
+ temp = new CultureInfo("ja-JP", false).DateTimeFormat;
+ temp.Calendar = JapaneseCalendar.GetDefaultInstance();
+ s_jajpDTFI = temp;
+ }
+ return (temp);
+ }
+ internal static DateTimeFormatInfo GetTaiwanCalendarDTFI() {
+ DateTimeFormatInfo temp = s_zhtwDTFI;
+ if (temp == null) {
+ temp = new CultureInfo("zh-TW", false).DateTimeFormat;
+ temp.Calendar = TaiwanCalendar.GetDefaultInstance();
+ s_zhtwDTFI = temp;
+ }
+ return (temp);
+ }
+
+
+ // DTFI properties should call this when the setter are called.
+ private void ClearTokenHashTable()
+ {
+ m_dtfiTokenHash = null;
+ formatFlags = DateTimeFormatFlags.NotInitialized;
+ }
+
+ [System.Security.SecurityCritical] // auto-generated
+ internal TokenHashValue[] CreateTokenHashTable() {
+ TokenHashValue[] temp = m_dtfiTokenHash;
+ if (temp == null) {
+ temp = new TokenHashValue[TOKEN_HASH_SIZE];
+
+ bool koreanLanguage = LanguageName.Equals(KoreanLangName);
+
+ string sep = this.TimeSeparator.Trim();
+ if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0);
+ if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0);
+
+ if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep) {
+ //
+ // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or ChineseHourSuff for some cultures like
+ // ja-JP and ko-KR. In these cases having the same symbol inserted into the hash table with multiple TokenTypes causes undesirable
+ // DateTime.Parse behavior. For instance, the DateTimeFormatInfo.Tokenize() method might return SEP_DateOrOffset for KoreanHourSuff
+ // instead of SEP_HourSuff.
+ //
+ InsertHash(temp, this.TimeSeparator, TokenType.SEP_Time, 0);
+ }
+
+ InsertHash(temp, this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
+ InsertHash(temp, this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
+
+ if (LanguageName.Equals("sq")) {
+ // Albanian allows time formats like "12:00.PD"
+ InsertHash(temp, IgnorablePeriod + this.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
+ InsertHash(temp, IgnorablePeriod + this.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
+ }
+
+ // CJK suffix
+ InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0);
+ InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0);
+ InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0);
+ InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0);
+ InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0);
+ InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0);
+
+ InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0);
+ InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0);
+ InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0);
+ InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0);
+
+ if (koreanLanguage) {
+ // Korean suffix
+ InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0);
+ InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0);
+ InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0);
+ }
+
+ if ( LanguageName.Equals("ky")) {
+ // For some cultures, the date separator works more like a comma, being allowed before or after any date part
+ InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0);
+ }
+ else {
+ InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0);
+ }
+
+ String[] dateWords = null;
+ DateTimeFormatInfoScanner scanner = null;
+
+ // We need to rescan the date words since we're always synthetic
+ scanner = new DateTimeFormatInfoScanner();
+ // Enumarate all LongDatePatterns, and get the DateWords and scan for month postfix.
+ // The only reason they're being assigned to m_dateWords is for Whidbey Deserialization
+ m_dateWords = dateWords = scanner.GetDateWordsOfDTFI(this);
+ // Ensure the formatflags is initialized.
+ DateTimeFormatFlags flag = FormatFlags;
+
+ // For some cultures, the date separator works more like a comma, being allowed before or after any date part.
+ // In these cultures, we do not use normal date separator since we disallow date separator after a date terminal state.
+ // This is determined in DateTimeFormatInfoScanner. Use this flag to determine if we should treat date separator as ignorable symbol.
+ bool useDateSepAsIgnorableSymbol = false;
+
+ String monthPostfix = null;
+ if (dateWords != null)
+ {
+ // There are DateWords. It could be a real date word (such as "de"), or a monthPostfix.
+ // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix.
+ for (int i = 0; i < dateWords.Length; i++)
+ {
+ switch (dateWords[i][0])
+ {
+ // This is a month postfix
+ case DateTimeFormatInfoScanner.MonthPostfixChar:
+ // Get the real month postfix.
+ monthPostfix = dateWords[i].Substring(1);
+ // Add the month name + postfix into the token.
+ AddMonthNames(temp, monthPostfix);
+ break;
+ case DateTimeFormatInfoScanner.IgnorableSymbolChar:
+ String symbol = dateWords[i].Substring(1);
+ InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0);
+ if (this.DateSeparator.Trim(null).Equals(symbol))
+ {
+ // The date separator is the same as the ingorable symbol.
+ useDateSepAsIgnorableSymbol = true;
+ }
+ break;
+ default:
+ InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
+ if (LanguageName.Equals("eu")) {
+ // Basque has date words with leading dots
+ InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0);
+ }
+ break;
+ }
+ }
+ }
+
+ if (!useDateSepAsIgnorableSymbol)
+ {
+ // Use the normal date separator.
+ InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0);
+ }
+ // Add the regular month names.
+ AddMonthNames(temp, null);
+
+ // Add the abbreviated month names.
+ for (int i = 1; i <= 13; i++) {
+ InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i);
+ }
+
+
+ if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0) {
+ for (int i = 1; i <= 13; i++) {
+ String str;
+ str = internalGetMonthName(i, MonthNameStyles.Genitive, false);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+ }
+
+ if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) {
+ for (int i = 1; i <= 13; i++) {
+ String str;
+ str = internalGetMonthName(i, MonthNameStyles.LeapYear, false);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+ }
+
+ for (int i = 0; i < 7; i++) {
+ //String str = GetDayOfWeekNames()[i];
+ // We have to call public methods here to work with inherited DTFI.
+ String str = GetDayName((DayOfWeek)i);
+ InsertHash(temp, str, TokenType.DayOfWeekToken, i);
+
+ str = GetAbbreviatedDayName((DayOfWeek)i);
+ InsertHash(temp, str, TokenType.DayOfWeekToken, i);
+
+ }
+
+ int[] eras = calendar.Eras;
+ for (int i = 1; i <= eras.Length; i++) {
+ InsertHash(temp, GetEraName(i), TokenType.EraToken, i);
+ InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i);
+ }
+
+ if (LanguageName.Equals(JapaneseLangName)) {
+ // Japanese allows day of week forms like: "(Tue)"
+ for (int i = 0; i < 7; i++) {
+ String specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")";
+ InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i);
+ }
+ if (this.Calendar.GetType() != typeof(JapaneseCalendar)) {
+ // Special case for Japanese. If this is a Japanese DTFI, and the calendar is not Japanese calendar,
+ // we will check Japanese Era name as well when the calendar is Gregorian.
+ DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI();
+ for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++) {
+ InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i);
+ InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i);
+ // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
+ InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i-1], TokenType.JapaneseEraToken, i);
+ }
+ }
+ }
+ else if (CultureName.Equals("zh-TW")) {
+ DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI();
+ for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++) {
+ if (twDtfi.GetEraName(i).Length > 0) {
+ InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i);
+ }
+ }
+ }
+
+ InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0);
+ InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1);
+
+ // Add invariant month names and day names.
+ for (int i = 1; i <= 12; i++) {
+ String str;
+ // We have to call public methods here to work with inherited DTFI.
+ // Insert the month name first, so that they are at the front of abbrevaited
+ // month names.
+ str = InvariantInfo.GetMonthName(i);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ str = InvariantInfo.GetAbbreviatedMonthName(i);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+
+ for (int i = 0; i < 7; i++) {
+ // We have to call public methods here to work with inherited DTFI.
+ String str = InvariantInfo.GetDayName((DayOfWeek)i);
+ InsertHash(temp, str, TokenType.DayOfWeekToken, i);
+
+ str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i);
+ InsertHash(temp, str, TokenType.DayOfWeekToken, i);
+
+ }
+
+ for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) {
+ // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1.
+ InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1);
+ }
+
+ InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0);
+ InsertHash(temp, DateTimeParse.GMTName, TokenType.TimeZoneToken, 0);
+ InsertHash(temp, DateTimeParse.ZuluName, TokenType.TimeZoneToken, 0);
+
+ InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
+ InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
+
+ m_dtfiTokenHash = temp;
+ }
+ return (temp);
+ }
+
+ private void AddMonthNames(TokenHashValue[] temp, String monthPostfix)
+ {
+ for (int i = 1; i <= 13; i++) {
+ String str;
+ //str = internalGetMonthName(i, MonthNameStyles.Regular, false);
+ // We have to call public methods here to work with inherited DTFI.
+ // Insert the month name first, so that they are at the front of abbrevaited
+ // month names.
+ str = GetMonthName(i);
+ if (str.Length > 0) {
+ if (monthPostfix != null) {
+ // Insert the month name with the postfix first, so it can be matched first.
+ InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i);
+ } else
+ {
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+ }
+ str = GetAbbreviatedMonthName(i);
+ InsertHash(temp, str, TokenType.MonthToken, i);
+ }
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Try to parse the current word to see if it is a Hebrew number.
+ // Tokens will be updated accordingly.
+ // This is called by the Lexer of DateTime.Parse().
+ //
+ // Unlike most of the functions in this class, the return value indicates
+ // whether or not it started to parse. The badFormat parameter indicates
+ // if parsing began, but the format was bad.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ private static bool TryParseHebrewNumber(
+ ref __DTString str,
+ out Boolean badFormat,
+ out int number) {
+
+ number = -1;
+ badFormat = false;
+
+ int i = str.Index;
+ if (!HebrewNumber.IsDigit(str.Value[i])) {
+ // If the current character is not a Hebrew digit, just return false.
+ // There is no chance that we can parse a valid Hebrew number from here.
+ return (false);
+ }
+ // The current character is a Hebrew digit. Try to parse this word as a Hebrew number.
+ HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
+ HebrewNumberParsingState state;
+
+ do {
+ state = HebrewNumber.ParseByChar(str.Value[i++], ref context);
+ switch (state) {
+ case HebrewNumberParsingState.InvalidHebrewNumber: // Not a valid Hebrew number.
+ case HebrewNumberParsingState.NotHebrewDigit: // The current character is not a Hebrew digit character.
+ // Break out so that we don't continue to try parse this as a Hebrew number.
+ return (false);
+ }
+ } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber));
+
+ // When we are here, we are either at the end of the string, or we find a valid Hebrew number.
+ Contract.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEndOfHebrewNumber,
+ "Invalid returned state from HebrewNumber.ParseByChar()");
+
+ if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber) {
+ // We reach end of the string but we can't find a terminal state in parsing Hebrew number.
+ return (false);
+ }
+
+ // We have found a valid Hebrew number. Update the index.
+ str.Advance(i - str.Index);
+
+ // Get the final Hebrew number value from the HebrewNumberParsingContext.
+ number = context.result;
+
+ return (true);
+ }
+
+ private static bool IsHebrewChar(char ch) {
+ return (ch >= '\x0590' && ch <= '\x05ff');
+ }
+
+ [System.Security.SecurityCritical] // auto-generated
+ internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue, ref __DTString str) {
+ tokenType = TokenType.UnknownToken;
+ tokenValue = 0;
+
+ TokenHashValue value;
+ Contract.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
+
+ char ch = str.m_current;
+ bool isLetter = Char.IsLetter(ch);
+ if (isLetter) {
+ ch = Char.ToLower(ch, this.Culture);
+ if (IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask) {
+ bool badFormat;
+ if (TryParseHebrewNumber(ref str, out badFormat, out tokenValue)) {
+ if (badFormat) {
+ tokenType = TokenType.UnknownToken;
+ return (false);
+ }
+ // This is a Hebrew number.
+ // Do nothing here. TryParseHebrewNumber() will update token accordingly.
+ tokenType = TokenType.HebrewNumber;
+ return (true);
+ }
+ }
+ }
+
+
+ int hashcode = ch % TOKEN_HASH_SIZE;
+ int hashProbe = 1 + ch % SECOND_PRIME;
+ int remaining = str.len - str.Index;
+ int i = 0;
+
+ TokenHashValue[] hashTable = m_dtfiTokenHash;
+ if (hashTable == null) {
+ hashTable = CreateTokenHashTable();
+ }
+ do {
+ value = hashTable[hashcode];
+ if (value == null) {
+ // Not found.
+ break;
+ }
+ // Check this value has the right category (regular token or separator token) that we are looking for.
+ if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining) {
+ if (String.Compare(str.Value, str.Index, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase)==0) {
+ if (isLetter) {
+ // If this token starts with a letter, make sure that we won't allow partial match. So you can't tokenize "MarchWed" separately.
+ int nextCharIndex;
+ if ((nextCharIndex = str.Index + value.tokenString.Length) < str.len) {
+ // Check word boundary. The next character should NOT be a letter.
+ char nextCh = str.Value[nextCharIndex];
+ if (Char.IsLetter(nextCh)) {
+ return (false);
+ }
+ }
+ }
+ tokenType = value.tokenType & TokenMask;
+ tokenValue = value.tokenValue;
+ str.Advance(value.tokenString.Length);
+ return (true);
+ } else if (value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) {
+ // For month token, we will match the month names which have spaces.
+ int matchStrLen = 0;
+ if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) {
+ tokenType = value.tokenType & TokenMask;
+ tokenValue = value.tokenValue;
+ str.Advance(matchStrLen);
+ return (true);
+ }
+ } else if (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames) {
+ // For month token, we will match the month names which have spaces.
+ int matchStrLen = 0;
+ if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) {
+ tokenType = value.tokenType & TokenMask;
+ tokenValue = value.tokenValue;
+ str.Advance(matchStrLen);
+ return (true);
+ }
+ }
+ }
+ i++;
+ hashcode += hashProbe;
+ if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
+ }while (i < TOKEN_HASH_SIZE);
+
+ return (false);
+ }
+
+ void InsertAtCurrentHashNode(TokenHashValue[] hashTable, String str, char ch, TokenType tokenType, int tokenValue, int pos, int hashcode, int hashProbe) {
+ // Remember the current slot.
+ TokenHashValue previousNode = hashTable[hashcode];
+
+ //// Console.WriteLine(" Insert Key: {0} in {1}", str, slotToInsert);
+ // Insert the new node into the current slot.
+ hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);;
+
+ while (++pos < TOKEN_HASH_SIZE) {
+ hashcode += hashProbe;
+ if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
+ // Remember this slot
+ TokenHashValue temp = hashTable[hashcode];
+
+ if (temp != null && Char.ToLower(temp.tokenString[0], this.Culture) != ch) {
+ continue;
+ }
+ // Put the previous slot into this slot.
+ hashTable[hashcode] = previousNode;
+ //// Console.WriteLine(" Move {0} to slot {1}", previousNode.tokenString, hashcode);
+ if (temp == null) {
+ // Done
+ return;
+ }
+ previousNode = temp;
+ } ;
+ Contract.Assert(false, "The hashtable is full. This should not happen.");
+ }
+
+ void InsertHash(TokenHashValue[] hashTable, String str, TokenType tokenType, int tokenValue) {
+ // The month of the 13th month is allowed to be null, so make sure that we ignore null value here.
+ if (str == null || str.Length == 0) {
+ return;
+ }
+ TokenHashValue value;
+ int i = 0;
+ // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are skipped by
+ // DateTime.Parse().
+ if (Char.IsWhiteSpace(str[0]) || Char.IsWhiteSpace(str[str.Length - 1])) {
+ str = str.Trim(null); // Trim white space characters.
+ // Could have space for separators
+ if (str.Length == 0)
+ return;
+ }
+ char ch = Char.ToLower(str[0], this.Culture);
+ int hashcode = ch % TOKEN_HASH_SIZE;
+ int hashProbe = 1 + ch % SECOND_PRIME;
+ do {
+ value = hashTable[hashcode];
+ if (value == null) {
+ //// Console.WriteLine(" Put Key: {0} in {1}", str, hashcode);
+ hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue);
+ return;
+ } else {
+ // Collision happens. Find another slot.
+ if (str.Length >= value.tokenString.Length) {
+ // If there are two tokens with the same prefix, we have to make sure that the longer token should be at the front of
+ // the shorter ones.
+ if (String.Compare(str, 0, value.tokenString, 0, value.tokenString.Length, this.Culture, CompareOptions.IgnoreCase) == 0) {
+ if (str.Length > value.tokenString.Length) {
+ // The str to be inserted has the same prefix as the current token, and str is longer.
+ // Insert str into this node, and shift every node behind it.
+ InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProbe);
+ return;
+ } else {
+ // Same token. If they have different types (regular token vs separator token). Add them.
+ // If we have the same regular token or separator token in the hash already, do NOT update the hash.
+ // Therefore, the order of inserting token is significant here regarding what tokenType will be kept in the hash.
+
+
+ //
+ // Check the current value of RegularToken (stored in the lower 8-bit of tokenType) , and insert the tokenType into the hash ONLY when we don't have a RegularToken yet.
+ // Also check the current value of SeparatorToken (stored in the upper 8-bit of token), and insert the tokenType into the hash ONLY when we don't have the SeparatorToken yet.
+ //
+
+ int nTokenType = (int)tokenType;
+ int nCurrentTokenTypeInHash = (int)value.tokenType;
+
+ // The idea behind this check is:
+ // - if the app is targetting 4.5.1 or above OR the compat flag is set, use the correct behavior by default.
+ // - if the app is targetting 4.5 or below AND the compat switch is set, use the correct behavior
+ // - if the app is targetting 4.5 or below AND the compat switch is NOT set, use the incorrect behavior
+ if (preferExistingTokens || BinaryCompatibility.TargetsAtLeast_Desktop_V4_5_1)
+ {
+ if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & (int)TokenType.RegularTokenMask) != 0) ||
+ ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType & (int)TokenType.SeparatorTokenMask) != 0))
+ {
+ value.tokenType |= tokenType;
+ if (tokenValue != 0)
+ {
+ value.tokenValue = tokenValue;
+ }
+ }
+ }
+ else
+ {
+ // The following logic is incorrect and causes updates to happen depending on the bitwise relationship between the existing token type and the
+ // the stored token type. It was this way in .NET 4 RTM. The behavior above is correct and will be adopted going forward.
+
+ if ((((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.RegularTokenMask) == nTokenType) ||
+ (((nTokenType | nCurrentTokenTypeInHash) & (int)TokenType.SeparatorTokenMask) == nTokenType))
+ {
+ value.tokenType |= tokenType;
+ if (tokenValue != 0)
+ {
+ value.tokenValue = tokenValue;
+ }
+ }
+ }
+ // The token to be inserted is already in the table. Skip it.
+ }
+ }
+ }
+ }
+ //// Console.WriteLine(" COLLISION. Old Key: {0}, New Key: {1}", hashTable[hashcode].tokenString, str);
+ i++;
+ hashcode += hashProbe;
+ if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE;
+ } while (i < TOKEN_HASH_SIZE);
+ Contract.Assert(false, "The hashtable is full. This should not happen.");
+ }
+ } // class DateTimeFormatInfo
+
+ internal class TokenHashValue {
+ internal String tokenString;
+ internal TokenType tokenType;
+ internal int tokenValue;
+
+ internal TokenHashValue(String tokenString, TokenType tokenType, int tokenValue) {
+ this.tokenString = tokenString;
+ this.tokenType = tokenType;
+ this.tokenValue = tokenValue;
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/DateTimeFormatInfoScanner.cs b/src/mscorlib/src/System/Globalization/DateTimeFormatInfoScanner.cs
new file mode 100644
index 0000000000..40cbabc096
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/DateTimeFormatInfoScanner.cs
@@ -0,0 +1,750 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+////////////////////////////////////////////////////////////////////////////
+//
+// DateTimeFormatInfoScanner
+//
+// Scan a specified DateTimeFormatInfo to search for data used in DateTime.Parse()
+//
+// The data includes:
+//
+// DateWords: such as "de" used in es-ES (Spanish) LongDatePattern.
+// Postfix: such as "ta" used in fi-FI after the month name.
+//
+// This class is shared among mscorlib.dll and sysglobl.dll.
+// Use conditional CULTURE_AND_REGIONINFO_BUILDER_ONLY to differentiate between
+// methods for mscorlib.dll and sysglobl.dll.
+//
+////////////////////////////////////////////////////////////////////////////
+
+namespace System.Globalization
+{
+ using System;
+ using System.Globalization;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Text;
+
+ //
+ // from LocaleEx.txt header
+ //
+ //; IFORMATFLAGS
+ //; Parsing/formatting flags.
+ internal enum FORMATFLAGS {
+ None = 0x00000000,
+ UseGenitiveMonth = 0x00000001,
+ UseLeapYearMonth = 0x00000002,
+ UseSpacesInMonthNames = 0x00000004,
+ UseHebrewParsing = 0x00000008,
+ UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names.
+ UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers.
+ }
+
+ //
+ // To change in CalendarId you have to do the same change in Calendar.cs
+ // To do: make the definintion shared between these two files.
+ //
+
+ internal enum CalendarId : ushort
+ {
+ GREGORIAN = 1 , // Gregorian (localized) calendar
+ GREGORIAN_US = 2 , // Gregorian (U.S.) calendar
+ JAPAN = 3 , // Japanese Emperor Era calendar
+/* SSS_WARNINGS_OFF */ TAIWAN = 4 , // Taiwan Era calendar /* SSS_WARNINGS_ON */
+ KOREA = 5 , // Korean Tangun Era calendar
+ HIJRI = 6 , // Hijri (Arabic Lunar) calendar
+ THAI = 7 , // Thai calendar
+ HEBREW = 8 , // Hebrew (Lunar) calendar
+ GREGORIAN_ME_FRENCH = 9 , // Gregorian Middle East French calendar
+ GREGORIAN_ARABIC = 10, // Gregorian Arabic calendar
+ GREGORIAN_XLIT_ENGLISH = 11, // Gregorian Transliterated English calendar
+ GREGORIAN_XLIT_FRENCH = 12,
+// Note that all calendars after this point are MANAGED ONLY for now.
+ JULIAN = 13,
+ JAPANESELUNISOLAR = 14,
+ CHINESELUNISOLAR = 15,
+ SAKA = 16, // reserved to match Office but not implemented in our code
+ LUNAR_ETO_CHN = 17, // reserved to match Office but not implemented in our code
+ LUNAR_ETO_KOR = 18, // reserved to match Office but not implemented in our code
+ LUNAR_ETO_ROKUYOU = 19, // reserved to match Office but not implemented in our code
+ KOREANLUNISOLAR = 20,
+ TAIWANLUNISOLAR = 21,
+ PERSIAN = 22,
+ UMALQURA = 23,
+ LAST_CALENDAR = 23 // Last calendar ID
+ }
+
+ internal class DateTimeFormatInfoScanner
+ {
+ // Special prefix-like flag char in DateWord array.
+
+ // Use char in PUA area since we won't be using them in real data.
+ // The char used to tell a read date word or a month postfix. A month postfix
+ // is "ta" in the long date pattern like "d. MMMM'ta 'yyyy" for fi-FI.
+ // In this case, it will be stored as "\xfffeta" in the date word array.
+ internal const char MonthPostfixChar = '\xe000';
+
+ // Add ignorable symbol in a DateWord array.
+
+ // hu-HU has:
+ // shrot date pattern: yyyy. MM. dd.;yyyy-MM-dd;yy-MM-dd
+ // long date pattern: yyyy. MMMM d.
+ // Here, "." is the date separator (derived from short date pattern). However,
+ // "." also appear at the end of long date pattern. In this case, we just
+ // "." as ignorable symbol so that the DateTime.Parse() state machine will not
+ // treat the additional date separator at the end of y,m,d pattern as an error
+ // condition.
+ internal const char IgnorableSymbolChar = '\xe001';
+
+ // Known CJK suffix
+ internal const String CJKYearSuff = "\u5e74";
+ internal const String CJKMonthSuff = "\u6708";
+ internal const String CJKDaySuff = "\u65e5";
+
+ internal const String KoreanYearSuff = "\ub144";
+ internal const String KoreanMonthSuff = "\uc6d4";
+ internal const String KoreanDaySuff = "\uc77c";
+
+ internal const String KoreanHourSuff = "\uc2dc";
+ internal const String KoreanMinuteSuff = "\ubd84";
+ internal const String KoreanSecondSuff = "\ucd08";
+
+ internal const String CJKHourSuff = "\u6642";
+ internal const String ChineseHourSuff = "\u65f6";
+
+ internal const String CJKMinuteSuff = "\u5206";
+ internal const String CJKSecondSuff = "\u79d2";
+
+ // The collection fo date words & postfix.
+ internal List<String> m_dateWords = new List<String>();
+ // Hashtable for the known words.
+ private static volatile Dictionary<String, String> s_knownWords;
+
+ static Dictionary<String, String> KnownWords
+ {
+ get
+ {
+ if (s_knownWords == null)
+ {
+ Dictionary<String, String> temp = new Dictionary<String, String>();
+ // Add known words into the hash table.
+
+ // Skip these special symbols.
+ temp.Add("/", String.Empty);
+ temp.Add("-", String.Empty);
+ temp.Add(".", String.Empty);
+ // Skip known CJK suffixes.
+ temp.Add(CJKYearSuff, String.Empty);
+ temp.Add(CJKMonthSuff, String.Empty);
+ temp.Add(CJKDaySuff, String.Empty);
+ temp.Add(KoreanYearSuff, String.Empty);
+ temp.Add(KoreanMonthSuff, String.Empty);
+ temp.Add(KoreanDaySuff, String.Empty);
+ temp.Add(KoreanHourSuff, String.Empty);
+ temp.Add(KoreanMinuteSuff, String.Empty);
+ temp.Add(KoreanSecondSuff, String.Empty);
+ temp.Add(CJKHourSuff, String.Empty);
+ temp.Add(ChineseHourSuff, String.Empty);
+ temp.Add(CJKMinuteSuff, String.Empty);
+ temp.Add(CJKSecondSuff, String.Empty);
+
+ s_knownWords = temp;
+ }
+ return (s_knownWords);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Parameters:
+ // pattern: The pattern to be scanned.
+ // currentIndex: the current index to start the scan.
+ //
+ // Returns:
+ // Return the index with the first character that is a letter, which will
+ // be the start of a date word.
+ // Note that the index can be pattern.Length if we reach the end of the string.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static int SkipWhiteSpacesAndNonLetter(String pattern, int currentIndex)
+ {
+ while (currentIndex < pattern.Length)
+ {
+ char ch = pattern[currentIndex];
+ if (ch == '\\')
+ {
+ // Escaped character. Look ahead one character.
+ currentIndex++;
+ if (currentIndex < pattern.Length)
+ {
+ ch = pattern[currentIndex];
+ if (ch == '\'')
+ {
+ // Skip the leading single quote. We will
+ // stop at the first letter.
+ continue;
+ }
+ // Fall thru to check if this is a letter.
+ } else
+ {
+ // End of string
+ break;
+ }
+ }
+ if (Char.IsLetter(ch) || ch == '\'' || ch == '.')
+ {
+ break;
+ }
+ // Skip the current char since it is not a letter.
+ currentIndex++;
+ }
+ return (currentIndex);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // A helper to add the found date word or month postfix into ArrayList for date words.
+ //
+ // Parameters:
+ // formatPostfix: What kind of postfix this is.
+ // Possible values:
+ // null: This is a regular date word
+ // "MMMM": month postfix
+ // word: The date word or postfix to be added.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal void AddDateWordOrPostfix(String formatPostfix, String str)
+ {
+ if (str.Length > 0)
+ {
+ // Some cultures use . like an abbreviation
+ if (str.Equals("."))
+ {
+ AddIgnorableSymbols(".");
+ return;
+ }
+ String words;
+ if (KnownWords.TryGetValue(str, out words) == false)
+ {
+ if (m_dateWords == null)
+ {
+ m_dateWords = new List<String>();
+ }
+ if (formatPostfix == "MMMM")
+ {
+ // Add the word into the ArrayList as "\xfffe" + real month postfix.
+ String temp = MonthPostfixChar + str;
+ if (!m_dateWords.Contains(temp))
+ {
+ m_dateWords.Add(temp);
+ }
+ } else
+ {
+ if (!m_dateWords.Contains(str))
+ {
+ m_dateWords.Add(str);
+ }
+ if (str[str.Length - 1] == '.')
+ {
+ // Old version ignore the trialing dot in the date words. Support this as well.
+ String strWithoutDot = str.Substring(0, str.Length - 1);
+ if (!m_dateWords.Contains(strWithoutDot))
+ {
+ m_dateWords.Add(strWithoutDot);
+ }
+
+ }
+ }
+ }
+ }
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Scan the pattern from the specified index and add the date word/postfix
+ // when appropriate.
+ //
+ // Parameters:
+ // pattern: The pattern to be scanned.
+ // index: The starting index to be scanned.
+ // formatPostfix: The kind of postfix to be scanned.
+ // Possible values:
+ // null: This is a regular date word
+ // "MMMM": month postfix
+ //
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal int AddDateWords(String pattern, int index, String formatPostfix)
+ {
+ // Skip any whitespaces so we will start from a letter.
+ int newIndex = SkipWhiteSpacesAndNonLetter(pattern, index);
+ if (newIndex != index && formatPostfix != null)
+ {
+ // There are whitespaces. This will not be a postfix.
+ formatPostfix = null;
+ }
+ index = newIndex;
+
+ // This is the first char added into dateWord.
+ // Skip all non-letter character. We will add the first letter into DateWord.
+ StringBuilder dateWord = new StringBuilder();
+ // We assume that date words should start with a letter.
+ // Skip anything until we see a letter.
+
+ while (index < pattern.Length)
+ {
+ char ch = pattern[index];
+ if (ch == '\'')
+ {
+ // We have seen the end of quote. Add the word if we do not see it before,
+ // and break the while loop.
+ AddDateWordOrPostfix(formatPostfix, dateWord.ToString());
+ index++;
+ break;
+ } else if (ch == '\\')
+ {
+ //
+ // Escaped character. Look ahead one character
+ //
+
+ // Skip escaped backslash.
+ index++;
+ if (index < pattern.Length)
+ {
+ dateWord.Append(pattern[index]);
+ index++;
+ }
+ } else if (Char.IsWhiteSpace(ch))
+ {
+ // Found a whitespace. We have to add the current date word/postfix.
+ AddDateWordOrPostfix(formatPostfix, dateWord.ToString());
+ if (formatPostfix != null)
+ {
+ // Done with postfix. The rest will be regular date word.
+ formatPostfix = null;
+ }
+ // Reset the dateWord.
+ dateWord.Length = 0;
+ index++;
+ } else
+ {
+ dateWord.Append(ch);
+ index++;
+ }
+ }
+ return (index);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // A simple helper to find the repeat count for a specified char.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static int ScanRepeatChar(String pattern, char ch, int index, out int count)
+ {
+ count = 1;
+ while (++index < pattern.Length && pattern[index] == ch) {
+ count++;
+ }
+ // Return the updated position.
+ return (index);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Add the text that is a date separator but is treated like ignroable symbol.
+ // E.g.
+ // hu-HU has:
+ // shrot date pattern: yyyy. MM. dd.;yyyy-MM-dd;yy-MM-dd
+ // long date pattern: yyyy. MMMM d.
+ // Here, "." is the date separator (derived from short date pattern). However,
+ // "." also appear at the end of long date pattern. In this case, we just
+ // "." as ignorable symbol so that the DateTime.Parse() state machine will not
+ // treat the additional date separator at the end of y,m,d pattern as an error
+ // condition.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal void AddIgnorableSymbols(String text)
+ {
+ if (m_dateWords == null)
+ {
+ // Create the date word array.
+ m_dateWords = new List<String>();
+ }
+ // Add the ingorable symbol into the ArrayList.
+ String temp = IgnorableSymbolChar + text;
+ if (!m_dateWords.Contains(temp))
+ {
+ m_dateWords.Add(temp);
+ }
+ }
+
+
+ //
+ // Flag used to trace the date patterns (yy/yyyyy/M/MM/MMM/MMM/d/dd) that we have seen.
+ //
+ enum FoundDatePattern
+ {
+ None = 0x0000,
+ FoundYearPatternFlag = 0x0001,
+ FoundMonthPatternFlag = 0x0002,
+ FoundDayPatternFlag = 0x0004,
+ FoundYMDPatternFlag = 0x0007, // FoundYearPatternFlag | FoundMonthPatternFlag | FoundDayPatternFlag;
+ }
+
+ // Check if we have found all of the year/month/day pattern.
+ FoundDatePattern m_ymdFlags = FoundDatePattern.None;
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Given a date format pattern, scan for date word or postfix.
+ //
+ // A date word should be always put in a single quoted string. And it will
+ // start from a letter, so whitespace and symbols will be ignored before
+ // the first letter.
+ //
+ // Examples of date word:
+ // 'de' in es-SP: dddd, dd' de 'MMMM' de 'yyyy
+ // "\x0443." in bg-BG: dd.M.yyyy '\x0433.'
+ //
+ // Example of postfix:
+ // month postfix:
+ // "ta" in fi-FI: d. MMMM'ta 'yyyy
+ // Currently, only month postfix is supported.
+ //
+ // Usage:
+ // Always call this with Framework-style pattern, instead of Windows style pattern.
+ // Windows style pattern uses '' for single quote, while .NET uses \'
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal void ScanDateWord(String pattern)
+ {
+
+ // Check if we have found all of the year/month/day pattern.
+ m_ymdFlags = FoundDatePattern.None;
+
+ int i = 0;
+ while (i < pattern.Length)
+ {
+ char ch = pattern[i];
+ int chCount;
+
+ switch (ch)
+ {
+ case '\'':
+ // Find a beginning quote. Search until the end quote.
+ i = AddDateWords(pattern, i+1, null);
+ break;
+ case 'M':
+ i = ScanRepeatChar(pattern, 'M', i, out chCount);
+ if (chCount >= 4)
+ {
+ if (i < pattern.Length && pattern[i] == '\'')
+ {
+ i = AddDateWords(pattern, i+1, "MMMM");
+ }
+ }
+ m_ymdFlags |= FoundDatePattern.FoundMonthPatternFlag;
+ break;
+ case 'y':
+ i = ScanRepeatChar(pattern, 'y', i, out chCount);
+ m_ymdFlags |= FoundDatePattern.FoundYearPatternFlag;
+ break;
+ case 'd':
+ i = ScanRepeatChar(pattern, 'd', i, out chCount);
+ if (chCount <= 2)
+ {
+ // Only count "d" & "dd".
+ // ddd, dddd are day names. Do not count them.
+ m_ymdFlags |= FoundDatePattern.FoundDayPatternFlag;
+ }
+ break;
+ case '\\':
+ // Found a escaped char not in a quoted string. Skip the current backslash
+ // and its next character.
+ i += 2;
+ break;
+ case '.':
+ if (m_ymdFlags == FoundDatePattern.FoundYMDPatternFlag)
+ {
+ // If we find a dot immediately after the we have seen all of the y, m, d pattern.
+ // treat it as a ignroable symbol. Check for comments in AddIgnorableSymbols for
+ // more details.
+ AddIgnorableSymbols(".");
+ m_ymdFlags = FoundDatePattern.None;
+ }
+ i++;
+ break;
+ default:
+ if (m_ymdFlags == FoundDatePattern.FoundYMDPatternFlag && !Char.IsWhiteSpace(ch))
+ {
+ // We are not seeing "." after YMD. Clear the flag.
+ m_ymdFlags = FoundDatePattern.None;
+ }
+ // We are not in quote. Skip the current character.
+ i++;
+ break;
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Given a DTFI, get all of the date words from date patterns and time patterns.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ #if FEATURE_CORECLR
+ [System.Security.SecurityCritical] // auto-generated
+ #endif
+ internal String[] GetDateWordsOfDTFI(DateTimeFormatInfo dtfi) {
+ // Enumarate all LongDatePatterns, and get the DateWords and scan for month postfix.
+ String[] datePatterns = dtfi.GetAllDateTimePatterns('D');
+ int i;
+
+ // Scan the long date patterns
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+
+ // Scan the short date patterns
+ datePatterns = dtfi.GetAllDateTimePatterns('d');
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+ // Scan the YearMonth patterns.
+ datePatterns = dtfi.GetAllDateTimePatterns('y');
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+
+ // Scan the month/day pattern
+ ScanDateWord(dtfi.MonthDayPattern);
+
+ // Scan the long time patterns.
+ datePatterns = dtfi.GetAllDateTimePatterns('T');
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+
+ // Scan the short time patterns.
+ datePatterns = dtfi.GetAllDateTimePatterns('t');
+ for (i = 0; i < datePatterns.Length; i++)
+ {
+ ScanDateWord(datePatterns[i]);
+ }
+
+ String[] result = null;
+ if (m_dateWords != null && m_dateWords.Count > 0)
+ {
+ result = new String[m_dateWords.Count];
+ for (i = 0; i < m_dateWords.Count; i++)
+ {
+ result[i] = m_dateWords[i];
+ }
+ }
+ return (result);
+ }
+
+#if ADDITIONAL_DTFI_SCANNER_METHODS
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Reset the date word ArrayList
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal void Reset()
+ {
+ m_dateWords.RemoveRange(0, m_dateWords.Count);
+ }
+#endif
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Scan the month names to see if genitive month names are used, and return
+ // the format flag.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static FORMATFLAGS GetFormatFlagGenitiveMonth(String[] monthNames, String[] genitveMonthNames, String[] abbrevMonthNames, String[] genetiveAbbrevMonthNames)
+ {
+ // If we have different names in regular and genitive month names, use genitive month flag.
+ return ((!EqualStringArrays(monthNames, genitveMonthNames) || !EqualStringArrays(abbrevMonthNames, genetiveAbbrevMonthNames))
+ ? FORMATFLAGS.UseGenitiveMonth: 0);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Scan the month names to see if spaces are used or start with a digit, and return the format flag
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static FORMATFLAGS GetFormatFlagUseSpaceInMonthNames(String[] monthNames, String[] genitveMonthNames, String[] abbrevMonthNames, String[] genetiveAbbrevMonthNames)
+ {
+ FORMATFLAGS formatFlags = 0;
+ formatFlags |= (ArrayElementsBeginWithDigit(monthNames) ||
+ ArrayElementsBeginWithDigit(genitveMonthNames) ||
+ ArrayElementsBeginWithDigit(abbrevMonthNames) ||
+ ArrayElementsBeginWithDigit(genetiveAbbrevMonthNames)
+ ? FORMATFLAGS.UseDigitPrefixInTokens : 0);
+
+ formatFlags |= (ArrayElementsHaveSpace(monthNames) ||
+ ArrayElementsHaveSpace(genitveMonthNames) ||
+ ArrayElementsHaveSpace(abbrevMonthNames) ||
+ ArrayElementsHaveSpace(genetiveAbbrevMonthNames)
+ ? FORMATFLAGS.UseSpacesInMonthNames : 0);
+ return (formatFlags);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Scan the day names and set the correct format flag.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static FORMATFLAGS GetFormatFlagUseSpaceInDayNames(String[] dayNames, String[] abbrevDayNames)
+ {
+ return ((ArrayElementsHaveSpace(dayNames) ||
+ ArrayElementsHaveSpace(abbrevDayNames))
+ ? FORMATFLAGS.UseSpacesInDayNames : 0);
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Check the calendar to see if it is HebrewCalendar and set the Hebrew format flag if necessary.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ internal static FORMATFLAGS GetFormatFlagUseHebrewCalendar(int calID)
+ {
+ return (calID == (int)CalendarId.HEBREW ?
+ FORMATFLAGS.UseHebrewParsing | FORMATFLAGS.UseLeapYearMonth : 0);
+ }
+
+
+ //-----------------------------------------------------------------------------
+ // EqualStringArrays
+ // compares two string arrays and return true if all elements of the first
+ // array equals to all elmentsof the second array.
+ // otherwise it returns false.
+ //-----------------------------------------------------------------------------
+
+ private static bool EqualStringArrays(string [] array1, string [] array2)
+ {
+ // Shortcut if they're the same array
+ if (array1 == array2)
+ {
+ return true;
+ }
+
+ // This is effectively impossible
+ if (array1.Length != array2.Length)
+ {
+ return false;
+ }
+
+ // Check each string
+ for (int i=0; i<array1.Length; i++)
+ {
+ if (!array1[i].Equals(array2[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ //-----------------------------------------------------------------------------
+ // ArrayElementsHaveSpace
+ // It checks all input array elements if any of them has space character
+ // returns true if found space character in one of the array elements.
+ // otherwise returns false.
+ //-----------------------------------------------------------------------------
+
+ private static bool ArrayElementsHaveSpace(string [] array)
+ {
+
+ for (int i=0; i<array.Length; i++)
+ {
+ // it is faster to check for space character manually instead of calling IndexOf
+ // so we don't have to go to native code side.
+ for (int j=0; j<array[i].Length; j++)
+ {
+ if ( Char.IsWhiteSpace(array[i][j]) )
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Check if any element of the array start with a digit.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private static bool ArrayElementsBeginWithDigit(string [] array)
+ {
+
+ for (int i=0; i<array.Length; i++)
+ {
+ // it is faster to check for space character manually instead of calling IndexOf
+ // so we don't have to go to native code side.
+ if (array[i].Length > 0 &&
+ array[i][0] >= '0' && array[i][0] <= '9')
+ {
+ int index = 1;
+ while (index < array[i].Length && array[i][index] >= '0' && array[i][index] <= '9')
+ {
+ // Skip other digits.
+ index++;
+ }
+ if (index == array[i].Length)
+ {
+ return (false);
+ }
+
+ if (index == array[i].Length - 1)
+ {
+ // Skip known CJK month suffix.
+ // CJK uses month name like "1\x6708", since \x6708 is a known month suffix,
+ // we don't need the UseDigitPrefixInTokens since it is slower.
+ switch (array[i][index])
+ {
+ case '\x6708': // CJKMonthSuff
+ case '\xc6d4': // KoreanMonthSuff
+ return (false);
+ }
+ }
+
+ if (index == array[i].Length - 4)
+ {
+ // Skip known CJK month suffix.
+ // Starting with Windows 8, the CJK months for some cultures looks like: "1' \x6708'"
+ // instead of just "1\x6708"
+ if(array[i][index] == '\'' && array[i][index + 1] == ' ' &&
+ array[i][index + 2] == '\x6708' && array[i][index + 3] == '\'')
+ {
+ return (false);
+ }
+
+ }
+ return (true);
+ }
+ }
+
+ return false;
+ }
+
+ }
+}
+
diff --git a/src/mscorlib/src/System/Globalization/DateTimeParse.cs b/src/mscorlib/src/System/Globalization/DateTimeParse.cs
new file mode 100644
index 0000000000..8de3242f30
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/DateTimeParse.cs
@@ -0,0 +1,5069 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class is called by DateTime to parse a date/time string.
+//
+////////////////////////////////////////////////////////////////////////////
+
+namespace System {
+ using System;
+ using System.Text;
+ using System.Globalization;
+ using System.Threading;
+ using System.Collections;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
+ using System.Runtime.Versioning;
+ using System.Security;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+
+ ////////////////////////////////////////////////////////////////////////
+
+ //This class contains only static members
+
+ internal static
+ class DateTimeParse {
+
+ internal const Int32 MaxDateTimeNumberDigits = 8;
+
+ internal delegate bool MatchNumberDelegate(ref __DTString str, int digitLen, out int result);
+
+ internal static MatchNumberDelegate m_hebrewNumberParser = new MatchNumberDelegate(DateTimeParse.MatchHebrewDigits);
+
+#if !FEATURE_CORECLR
+ [SecuritySafeCritical]
+ internal static bool GetAmPmParseFlag()
+ {
+ return DateTime.EnableAmPmParseAdjustment();
+ }
+
+ internal static bool enableAmPmParseAdjustment = GetAmPmParseFlag();
+#endif
+
+ internal static DateTime ParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style) {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ result.Init();
+ if (TryParseExact(s, format, dtfi, style, ref result)) {
+ return result.parsedDate;
+ }
+ else {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+ internal static DateTime ParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, out TimeSpan offset) {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ offset = TimeSpan.Zero;
+ result.Init();
+ result.flags |= ParseFlags.CaptureOffset;
+ if (TryParseExact(s, format, dtfi, style, ref result)) {
+ offset = result.timeZoneOffset;
+ return result.parsedDate;
+ }
+ else {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+ internal static bool TryParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result) {
+ result = DateTime.MinValue;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init();
+ if (TryParseExact(s, format, dtfi, style, ref resultData)) {
+ result = resultData.parsedDate;
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool TryParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result, out TimeSpan offset) {
+ result = DateTime.MinValue;
+ offset = TimeSpan.Zero;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init();
+ resultData.flags |= ParseFlags.CaptureOffset;
+ if (TryParseExact(s, format, dtfi, style, ref resultData)) {
+ result = resultData.parsedDate;
+ offset = resultData.timeZoneOffset;
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool TryParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result) {
+ if (s == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "s");
+ return false;
+ }
+ if (format == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "format");
+ return false;
+ }
+ if (s.Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ if (format.Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
+ return false;
+ }
+
+ Contract.Assert(dtfi != null, "dtfi == null");
+
+ return DoStrictParse(s, format, style, dtfi, ref result);
+ }
+
+ internal static DateTime ParseExactMultiple(String s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style) {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ result.Init();
+ if (TryParseExactMultiple(s, formats, dtfi, style, ref result)) {
+ return result.parsedDate;
+ }
+ else {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+
+ internal static DateTime ParseExactMultiple(String s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style, out TimeSpan offset) {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ offset = TimeSpan.Zero;
+ result.Init();
+ result.flags |= ParseFlags.CaptureOffset;
+ if (TryParseExactMultiple(s, formats, dtfi, style, ref result)) {
+ offset = result.timeZoneOffset;
+ return result.parsedDate;
+ }
+ else {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+ internal static bool TryParseExactMultiple(String s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result, out TimeSpan offset) {
+ result = DateTime.MinValue;
+ offset = TimeSpan.Zero;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init();
+ resultData.flags |= ParseFlags.CaptureOffset;
+ if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData)) {
+ result = resultData.parsedDate;
+ offset = resultData.timeZoneOffset;
+ return true;
+ }
+ return false;
+ }
+
+
+ internal static bool TryParseExactMultiple(String s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result) {
+ result = DateTime.MinValue;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init();
+ if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData)) {
+ result = resultData.parsedDate;
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool TryParseExactMultiple(String s, String[] formats,
+ DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result) {
+ if (s == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "s");
+ return false;
+ }
+ if (formats == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "formats");
+ return false;
+ }
+
+ if (s.Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ if (formats.Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
+ return false;
+ }
+
+ Contract.Assert(dtfi != null, "dtfi == null");
+
+ //
+ // Do a loop through the provided formats and see if we can parse succesfully in
+ // one of the formats.
+ //
+ for (int i = 0; i < formats.Length; i++) {
+ if (formats[i] == null || formats[i].Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
+ return false;
+ }
+ // Create a new result each time to ensure the runs are independent. Carry through
+ // flags from the caller and return the result.
+ DateTimeResult innerResult = new DateTimeResult(); // The buffer to store the parsing result.
+ innerResult.Init();
+ innerResult.flags = result.flags;
+ if (TryParseExact(s, formats[i], dtfi, style, ref innerResult)) {
+ result.parsedDate = innerResult.parsedDate;
+ result.timeZoneOffset = innerResult.timeZoneOffset;
+ return (true);
+ }
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Date Token Types
+ //
+ // Following is the set of tokens that can be generated from a date
+ // string. Notice that the legal set of trailing separators have been
+ // folded in with the date number, and month name tokens. This set
+ // of tokens is chosen to reduce the number of date parse states.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal enum DTT: int {
+
+ End = 0, // '\0'
+ NumEnd = 1, // Num[ ]*[\0]
+ NumAmpm = 2, // Num[ ]+AmPm
+ NumSpace = 3, // Num[ ]+^[Dsep|Tsep|'0\']
+ NumDatesep = 4, // Num[ ]*Dsep
+ NumTimesep = 5, // Num[ ]*Tsep
+ MonthEnd = 6, // Month[ ]*'\0'
+ MonthSpace = 7, // Month[ ]+^[Dsep|Tsep|'\0']
+ MonthDatesep = 8, // Month[ ]*Dsep
+ NumDatesuff = 9, // Month[ ]*DSuff
+ NumTimesuff = 10, // Month[ ]*TSuff
+ DayOfWeek = 11, // Day of week name
+ YearSpace = 12, // Year+^[Dsep|Tsep|'0\']
+ YearDateSep = 13, // Year+Dsep
+ YearEnd = 14, // Year+['\0']
+ TimeZone = 15, // timezone name
+ Era = 16, // era name
+ NumUTCTimeMark = 17, // Num + 'Z'
+ // When you add a new token which will be in the
+ // state table, add it after NumLocalTimeMark.
+ Unk = 18, // unknown
+ NumLocalTimeMark = 19, // Num + 'T'
+ Max = 20, // marker
+ }
+
+ internal enum TM {
+ NotSet = -1,
+ AM = 0,
+ PM = 1,
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // DateTime parsing state enumeration (DS.*)
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal enum DS {
+ BEGIN = 0,
+ N = 1, // have one number
+ NN = 2, // have two numbers
+
+ // The following are known to be part of a date
+
+ D_Nd = 3, // date string: have number followed by date separator
+ D_NN = 4, // date string: have two numbers
+ D_NNd = 5, // date string: have two numbers followed by date separator
+
+ D_M = 6, // date string: have a month
+ D_MN = 7, // date string: have a month and a number
+ D_NM = 8, // date string: have a number and a month
+ D_MNd = 9, // date string: have a month and number followed by date separator
+ D_NDS = 10, // date string: have one number followed a date suffix.
+
+ D_Y = 11, // date string: have a year.
+ D_YN = 12, // date string: have a year and a number
+ D_YNd = 13, // date string: have a year and a number and a date separator
+ D_YM = 14, // date string: have a year and a month
+ D_YMd = 15, // date string: have a year and a month and a date separator
+ D_S = 16, // have numbers followed by a date suffix.
+ T_S = 17, // have numbers followed by a time suffix.
+
+ // The following are known to be part of a time
+
+ T_Nt = 18, // have num followed by time separator
+ T_NNt = 19, // have two numbers followed by time separator
+
+
+ ERROR = 20,
+
+ // The following are terminal states. These all have an action
+ // associated with them; and transition back to BEGIN.
+
+ DX_NN = 21, // day from two numbers
+ DX_NNN = 22, // day from three numbers
+ DX_MN = 23, // day from month and one number
+ DX_NM = 24, // day from month and one number
+ DX_MNN = 25, // day from month and two numbers
+ DX_DS = 26, // a set of date suffixed numbers.
+ DX_DSN = 27, // day from date suffixes and one number.
+ DX_NDS = 28, // day from one number and date suffixes .
+ DX_NNDS = 29, // day from one number and date suffixes .
+
+ DX_YNN = 30, // date string: have a year and two number
+ DX_YMN = 31, // date string: have a year, a month, and a number.
+ DX_YN = 32, // date string: have a year and one number
+ DX_YM = 33, // date string: have a year, a month.
+ TX_N = 34, // time from one number (must have ampm)
+ TX_NN = 35, // time from two numbers
+ TX_NNN = 36, // time from three numbers
+ TX_TS = 37, // a set of time suffixed numbers.
+ DX_NNY = 38,
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // NOTE: The following state machine table is dependent on the order of the
+ // DS and DTT enumerations.
+ //
+ // For each non terminal state, the following table defines the next state
+ // for each given date token type.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+// End NumEnd NumAmPm NumSpace NumDaySep NumTimesep MonthEnd MonthSpace MonthDSep NumDateSuff NumTimeSuff DayOfWeek YearSpace YearDateSep YearEnd TimeZone Era UTCTimeMark
+private static DS[][] dateParsingStates = {
+// DS.BEGIN // DS.BEGIN
+new DS[] { DS.BEGIN, DS.ERROR, DS.TX_N, DS.N, DS.D_Nd, DS.T_Nt, DS.ERROR, DS.D_M, DS.D_M, DS.D_S, DS.T_S, DS.BEGIN, DS.D_Y, DS.D_Y, DS.ERROR, DS.BEGIN, DS.BEGIN, DS.ERROR},
+
+// DS.N // DS.N
+new DS[] { DS.ERROR, DS.DX_NN, DS.ERROR, DS.NN, DS.D_NNd, DS.ERROR, DS.DX_NM, DS.D_NM, DS.D_MNd, DS.D_NDS, DS.ERROR, DS.N, DS.D_YN, DS.D_YNd, DS.DX_YN, DS.N, DS.N, DS.ERROR},
+
+// DS.NN // DS.NN
+new DS[] { DS.DX_NN, DS.DX_NNN, DS.TX_N, DS.DX_NNN, DS.ERROR, DS.T_Nt, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.ERROR, DS.T_S, DS.NN, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.NN, DS.NN, DS.ERROR},
+
+// DS.D_Nd // DS.D_Nd
+new DS[] { DS.ERROR, DS.DX_NN, DS.ERROR, DS.D_NN, DS.D_NNd, DS.ERROR, DS.DX_NM, DS.D_MN, DS.D_MNd, DS.ERROR, DS.ERROR, DS.D_Nd, DS.D_YN, DS.D_YNd, DS.DX_YN, DS.ERROR, DS.D_Nd, DS.ERROR},
+
+// DS.D_NN // DS.D_NN
+new DS[] { DS.DX_NN, DS.DX_NNN, DS.TX_N, DS.DX_NNN, DS.ERROR, DS.T_Nt, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.DX_DS, DS.T_S, DS.D_NN, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.ERROR, DS.D_NN, DS.ERROR},
+
+// DS.D_NNd // DS.D_NNd
+new DS[] { DS.ERROR, DS.DX_NNN, DS.DX_NNN, DS.DX_NNN, DS.ERROR, DS.ERROR, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.DX_DS, DS.ERROR, DS.D_NNd, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.ERROR, DS.D_NNd, DS.ERROR},
+
+// DS.D_M // DS.D_M
+new DS[] { DS.ERROR, DS.DX_MN, DS.ERROR, DS.D_MN, DS.D_MNd, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_M, DS.D_YM, DS.D_YMd, DS.DX_YM, DS.ERROR, DS.D_M, DS.ERROR},
+
+// DS.D_MN // DS.D_MN
+new DS[] { DS.DX_MN, DS.DX_MNN, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.DX_DS, DS.T_S, DS.D_MN, DS.DX_YMN, DS.ERROR, DS.DX_YMN, DS.ERROR, DS.D_MN, DS.ERROR},
+
+// DS.D_NM // DS.D_NM
+new DS[] { DS.DX_NM, DS.DX_MNN, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.DX_DS, DS.T_S, DS.D_NM, DS.DX_YMN, DS.ERROR, DS.DX_YMN, DS.ERROR, DS.D_NM, DS.ERROR},
+
+// DS.D_MNd // DS.D_MNd
+new DS[] { DS.ERROR, DS.DX_MNN, DS.ERROR, DS.DX_MNN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_MNd, DS.DX_YMN, DS.ERROR, DS.DX_YMN, DS.ERROR, DS.D_MNd, DS.ERROR},
+
+// DS.D_NDS, // DS.D_NDS,
+new DS[] { DS.DX_NDS,DS.DX_NNDS, DS.DX_NNDS, DS.DX_NNDS, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_NDS, DS.T_S, DS.D_NDS, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_NDS, DS.ERROR},
+
+// DS.D_Y // DS.D_Y
+new DS[] { DS.ERROR, DS.DX_YN, DS.ERROR, DS.D_YN, DS.D_YNd, DS.ERROR, DS.DX_YM, DS.D_YM, DS.D_YMd, DS.D_YM, DS.ERROR, DS.D_Y, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_Y, DS.ERROR},
+
+// DS.D_YN // DS.D_YN
+new DS[] { DS.DX_YN, DS.DX_YNN, DS.DX_YNN, DS.DX_YNN, DS.ERROR, DS.ERROR, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR},
+
+// DS.D_YNd // DS.D_YNd
+new DS[] { DS.ERROR, DS.DX_YNN, DS.DX_YNN, DS.DX_YNN, DS.ERROR, DS.ERROR, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YN, DS.ERROR},
+
+// DS.D_YM // DS.D_YM
+new DS[] { DS.DX_YM, DS.DX_YMN, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR},
+
+// DS.D_YMd // DS.D_YMd
+new DS[] { DS.ERROR, DS.DX_YMN, DS.DX_YMN, DS.DX_YMN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_YM, DS.ERROR},
+
+// DS.D_S // DS.D_S
+new DS[] { DS.DX_DS, DS.DX_DSN, DS.TX_N, DS.T_Nt, DS.ERROR, DS.T_Nt, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_S, DS.T_S, DS.D_S, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_S, DS.ERROR},
+
+// DS.T_S // DS.T_S
+new DS[] { DS.TX_TS, DS.TX_TS, DS.TX_TS, DS.T_Nt, DS.D_Nd, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.D_S, DS.T_S, DS.T_S, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_S, DS.T_S, DS.ERROR},
+
+// DS.T_Nt // DS.T_Nt
+new DS[] { DS.ERROR, DS.TX_NN, DS.TX_NN, DS.TX_NN, DS.ERROR, DS.T_NNt, DS.DX_NM, DS.D_NM, DS.ERROR, DS.ERROR, DS.T_S, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_Nt, DS.T_Nt, DS.TX_NN},
+
+// DS.T_NNt // DS.T_NNt
+new DS[] { DS.ERROR, DS.TX_NNN, DS.TX_NNN, DS.TX_NNN, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_S, DS.T_NNt, DS.ERROR, DS.ERROR, DS.ERROR, DS.T_NNt, DS.T_NNt, DS.TX_NNN},
+
+};
+// End NumEnd NumAmPm NumSpace NumDaySep NumTimesep MonthEnd MonthSpace MonthDSep NumDateSuff NumTimeSuff DayOfWeek YearSpace YearDateSep YearEnd TimeZone Era UTCMark
+
+ internal const String GMTName = "GMT";
+ internal const String ZuluName = "Z";
+
+ //
+ // Search from the index of str at str.Index to see if the target string exists in the str.
+ //
+ private static bool MatchWord(ref __DTString str, String target)
+ {
+ int length = target.Length;
+ if (length > (str.Value.Length - str.Index)) {
+ return false;
+ }
+
+ if (str.CompareInfo.Compare(str.Value, str.Index, length,
+ target, 0, length, CompareOptions.IgnoreCase)!=0) {
+ return (false);
+ }
+
+ int nextCharIndex = str.Index + target.Length;
+
+ if (nextCharIndex < str.Value.Length) {
+ char nextCh = str.Value[nextCharIndex];
+ if (Char.IsLetter(nextCh)) {
+ return (false);
+ }
+ }
+ str.Index = nextCharIndex;
+ if (str.Index < str.len) {
+ str.m_current = str.Value[str.Index];
+ }
+
+ return (true);
+ }
+
+
+ //
+ // Check the word at the current index to see if it matches GMT name or Zulu name.
+ //
+ private static bool GetTimeZoneName(ref __DTString str)
+ {
+ if (MatchWord(ref str, GMTName)) {
+ return (true);
+ }
+
+ if (MatchWord(ref str, ZuluName)) {
+ return (true);
+ }
+
+ return (false);
+ }
+
+ internal static bool IsDigit(char ch) {
+ return (ch >= '0' && ch <= '9');
+ }
+
+
+ /*=================================ParseFraction==========================
+ **Action: Starting at the str.Index, which should be a decimal symbol.
+ ** if the current character is a digit, parse the remaining
+ ** numbers as fraction. For example, if the sub-string starting at str.Index is "123", then
+ ** the method will return 0.123
+ **Returns: The fraction number.
+ **Arguments:
+ ** str the parsing string
+ **Exceptions:
+ ============================================================================*/
+
+ private static bool ParseFraction(ref __DTString str, out double result) {
+ result = 0;
+ double decimalBase = 0.1;
+ int digits = 0;
+ char ch;
+ while (str.GetNext()
+ && IsDigit(ch = str.m_current)) {
+ result += (ch - '0') * decimalBase;
+ decimalBase *= 0.1;
+ digits++;
+ }
+ return (digits > 0);
+ }
+
+ /*=================================ParseTimeZone==========================
+ **Action: Parse the timezone offset in the following format:
+ ** "+8", "+08", "+0800", "+0800"
+ ** This method is used by DateTime.Parse().
+ **Returns: The TimeZone offset.
+ **Arguments:
+ ** str the parsing string
+ **Exceptions:
+ ** FormatException if invalid timezone format is found.
+ ============================================================================*/
+
+ private static bool ParseTimeZone(ref __DTString str, ref TimeSpan result) {
+ // The hour/minute offset for timezone.
+ int hourOffset = 0;
+ int minuteOffset = 0;
+ DTSubString sub;
+
+ // Consume the +/- character that has already been read
+ sub = str.GetSubString();
+ if (sub.length != 1) {
+ return false;
+ }
+ char offsetChar = sub[0];
+ if (offsetChar != '+' && offsetChar != '-') {
+ return false;
+ }
+ str.ConsumeSubString(sub);
+
+ sub = str.GetSubString();
+ if (sub.type != DTSubStringType.Number) {
+ return false;
+ }
+ int value = sub.value;
+ int length = sub.length;
+ if (length == 1 || length == 2) {
+ // Parsing "+8" or "+08"
+ hourOffset = value;
+ str.ConsumeSubString(sub);
+ // See if we have minutes
+ sub = str.GetSubString();
+ if (sub.length == 1 && sub[0] == ':') {
+ // Parsing "+8:00" or "+08:00"
+ str.ConsumeSubString(sub);
+ sub = str.GetSubString();
+ if (sub.type != DTSubStringType.Number || sub.length < 1 || sub.length > 2) {
+ return false;
+ }
+ minuteOffset = sub.value;
+ str.ConsumeSubString(sub);
+ }
+ }
+ else if (length == 3 || length == 4) {
+ // Parsing "+800" or "+0800"
+ hourOffset = value / 100;
+ minuteOffset = value % 100;
+ str.ConsumeSubString(sub);
+ }
+ else {
+ // Wrong number of digits
+ return false;
+ }
+ Contract.Assert(hourOffset >= 0 && hourOffset <= 99, "hourOffset >= 0 && hourOffset <= 99");
+ Contract.Assert(minuteOffset >= 0 && minuteOffset <= 99, "minuteOffset >= 0 && minuteOffset <= 99");
+ if (minuteOffset < 0 || minuteOffset >= 60) {
+ return false;
+ }
+
+ result = new TimeSpan(hourOffset, minuteOffset, 0);
+ if (offsetChar == '-') {
+ result = result.Negate();
+ }
+ return true;
+ }
+
+ // This is the helper function to handle timezone in string in the format like +/-0800
+ private static bool HandleTimeZone(ref __DTString str, ref DateTimeResult result)
+ {
+ if ((str.Index < str.len - 1)) {
+ char nextCh = str.Value[str.Index];
+ // Skip whitespace, but don't update the index unless we find a time zone marker
+ int whitespaceCount = 0;
+ while (Char.IsWhiteSpace(nextCh) && str.Index + whitespaceCount < str.len - 1) {
+ whitespaceCount++;
+ nextCh = str.Value[str.Index + whitespaceCount];
+ }
+ if (nextCh == '+' || nextCh == '-') {
+ str.Index += whitespaceCount;
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0) {
+ // Should not have two timezone offsets.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ result.flags |= ParseFlags.TimeZoneUsed;
+ if (!ParseTimeZone(ref str, ref result.timeZoneOffset)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ //
+ // This is the lexer. Check the character at the current index, and put the found token in dtok and
+ // some raw date/time information in raw.
+ //
+ [System.Security.SecuritySafeCritical] // auto-generated
+ private static Boolean Lex(DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi, DateTimeStyles styles)
+ {
+
+ TokenType tokenType;
+ int tokenValue;
+ int indexBeforeSeparator;
+ char charBeforeSeparator;
+
+ TokenType sep;
+ dtok.dtt = DTT.Unk; // Assume the token is unkown.
+
+ str.GetRegularToken(out tokenType, out tokenValue, dtfi);
+
+#if _LOGGING
+ // Builds with _LOGGING defined (x86dbg, amd64chk, etc) support tracing
+ // Set the following internal-only/unsupported environment variables to enable DateTime tracing to the console:
+ //
+ // COMPlus_LogEnable=1
+ // COMPlus_LogToConsole=1
+ // COMPlus_LogLevel=9
+ // COMPlus_ManagedLogFacility=0x00001000
+ if (_tracingEnabled) {
+ BCLDebug.Trace("DATETIME", "[DATETIME] Lex({0})\tpos:{1}({2}), {3}, DS.{4}", Hex(str.Value),
+ str.Index, Hex(str.m_current), tokenType, dps);
+ }
+#endif // _LOGGING
+
+ // Look at the regular token.
+ switch (tokenType) {
+ case TokenType.NumberToken:
+ case TokenType.YearNumberToken:
+ if (raw.numCount == 3 || tokenValue == -1) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0010", dps);
+ return false;
+ }
+ //
+ // This is a digit.
+ //
+ // If the previous parsing state is DS.T_NNt (like 12:01), and we got another number,
+ // so we will have a terminal state DS.TX_NNN (like 12:01:02).
+ // If the previous parsing state is DS.T_Nt (like 12:), and we got another number,
+ // so we will have a terminal state DS.TX_NN (like 12:01).
+ //
+ // Look ahead to see if the following character is a decimal point or timezone offset.
+ // This enables us to parse time in the forms of:
+ // "11:22:33.1234" or "11:22:33-08".
+ if (dps == DS.T_NNt) {
+ if ((str.Index < str.len - 1)) {
+ char nextCh = str.Value[str.Index];
+ if (nextCh == '.') {
+ // While ParseFraction can fail, it just means that there were no digits after
+ // the dot. In this case ParseFraction just removes the dot. This is actually
+ // valid for cultures like Albanian, that join the time marker to the time with
+ // with a dot: e.g. "9:03.MD"
+ ParseFraction(ref str, out raw.fraction);
+ }
+ }
+ }
+ if (dps == DS.T_NNt || dps == DS.T_Nt) {
+ if ((str.Index < str.len - 1)) {
+ if (false == HandleTimeZone(ref str, ref result))
+ {
+ LexTraceExit("0020 (value like \"12:01\" or \"12:\" followed by a non-TZ number", dps);
+ return false;
+ }
+ }
+ }
+
+ dtok.num = tokenValue;
+ if (tokenType == TokenType.YearNumberToken)
+ {
+ if (raw.year == -1)
+ {
+ raw.year = tokenValue;
+ //
+ // If we have number which has 3 or more digits (like "001" or "0001"),
+ // we assume this number is a year. Save the currnet raw.numCount in
+ // raw.year.
+ //
+ switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) {
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.YearEnd;
+ break;
+ case TokenType.SEP_Am:
+ case TokenType.SEP_Pm:
+ if (raw.timeMark == TM.NotSet) {
+ raw.timeMark = (sep == TokenType.SEP_Am ? TM.AM : TM.PM);
+ dtok.dtt = DTT.YearSpace;
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0030 (TM.AM/TM.PM Happened more than 1x)", dps);
+ }
+ break;
+ case TokenType.SEP_Space:
+ dtok.dtt = DTT.YearSpace;
+ break;
+ case TokenType.SEP_Date:
+ dtok.dtt = DTT.YearDateSep;
+ break;
+ case TokenType.SEP_Time:
+ if (!raw.hasSameDateAndTimeSeparators)
+ {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0040 (Invalid separator after number)", dps);
+ return false;
+ }
+
+ // we have the date and time separators are same and getting a year number, then change the token to YearDateSep as
+ // we are sure we are not parsing time.
+ dtok.dtt = DTT.YearDateSep;
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if ((dateParsingStates[(int)dps][(int) DTT.YearDateSep] == DS.ERROR)
+ && (dateParsingStates[(int)dps][(int) DTT.YearSpace] > DS.ERROR)) {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.YearSpace;
+ }
+ else {
+ dtok.dtt = DTT.YearDateSep;
+ }
+ break;
+ case TokenType.SEP_YearSuff:
+ case TokenType.SEP_MonthSuff:
+ case TokenType.SEP_DaySuff:
+ dtok.dtt = DTT.NumDatesuff;
+ dtok.suffix = sep;
+ break;
+ case TokenType.SEP_HourSuff:
+ case TokenType.SEP_MinuteSuff:
+ case TokenType.SEP_SecondSuff:
+ dtok.dtt = DTT.NumTimesuff;
+ dtok.suffix = sep;
+ break;
+ default:
+ // Invalid separator after number number.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0040 (Invalid separator after number)", dps);
+ return false;
+ }
+ //
+ // Found the token already. Return now.
+ //
+ LexTraceExit("0050 (success)", dps);
+ return true;
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0060", dps);
+ return false;
+ }
+ switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
+ {
+ //
+ // Note here we check if the numCount is less than three.
+ // When we have more than three numbers, it will be caught as error in the state machine.
+ //
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.NumEnd;
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_Am:
+ case TokenType.SEP_Pm:
+ if (raw.timeMark == TM.NotSet) {
+ raw.timeMark = (sep == TokenType.SEP_Am ? TM.AM : TM.PM);
+ dtok.dtt = DTT.NumAmpm;
+ // Fix AM/PM parsing case, e.g. "1/10 5 AM"
+ if (dps == DS.D_NN
+#if !FEATURE_CORECLR
+ && enableAmPmParseAdjustment
+#endif
+ )
+ {
+ if (!ProcessTerminaltState(DS.DX_NN, ref result, ref styles, ref raw, dtfi))
+ {
+ return false;
+ }
+ }
+
+ raw.AddNumber(dtok.num);
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ break;
+ }
+ if (dps == DS.T_NNt || dps == DS.T_Nt) {
+ if (false == HandleTimeZone(ref str, ref result))
+ {
+ LexTraceExit("0070 (HandleTimeZone returned false)", dps);
+ return false;
+ }
+ }
+ break;
+ case TokenType.SEP_Space:
+ dtok.dtt = DTT.NumSpace;
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_Date:
+ dtok.dtt = DTT.NumDatesep;
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if ((dateParsingStates[(int)dps][(int) DTT.NumDatesep] == DS.ERROR)
+ && (dateParsingStates[(int)dps][(int) DTT.NumSpace] > DS.ERROR)) {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.NumSpace;
+ }
+ else {
+ dtok.dtt = DTT.NumDatesep;
+ }
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_Time:
+ if (raw.hasSameDateAndTimeSeparators &&
+ (dps == DS.D_Y || dps == DS.D_YN || dps == DS.D_YNd || dps == DS.D_YM || dps == DS.D_YMd))
+ {
+ // we are parsing a date and we have the time separator same as date separator, so we mark the token as date separator
+ dtok.dtt = DTT.NumDatesep;
+ raw.AddNumber(dtok.num);
+ break;
+ }
+ dtok.dtt = DTT.NumTimesep;
+ raw.AddNumber(dtok.num);
+ break;
+ case TokenType.SEP_YearSuff:
+ try {
+ dtok.num = dtfi.Calendar.ToFourDigitYear(tokenValue);
+ }
+ catch (ArgumentOutOfRangeException e) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", e);
+ LexTraceExit("0075 (Calendar.ToFourDigitYear failed)", dps);
+ return false;
+ }
+ dtok.dtt = DTT.NumDatesuff;
+ dtok.suffix = sep;
+ break;
+ case TokenType.SEP_MonthSuff:
+ case TokenType.SEP_DaySuff:
+ dtok.dtt = DTT.NumDatesuff;
+ dtok.suffix = sep;
+ break;
+ case TokenType.SEP_HourSuff:
+ case TokenType.SEP_MinuteSuff:
+ case TokenType.SEP_SecondSuff:
+ dtok.dtt = DTT.NumTimesuff;
+ dtok.suffix = sep;
+ break;
+ case TokenType.SEP_LocalTimeMark:
+ dtok.dtt = DTT.NumLocalTimeMark;
+ raw.AddNumber(dtok.num);
+ break;
+ default:
+ // Invalid separator after number number.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0080", dps);
+ return false;
+ }
+ break;
+ case TokenType.HebrewNumber:
+ if (tokenValue >= 100) {
+ // This is a year number
+ if (raw.year == -1) {
+ raw.year = tokenValue;
+ //
+ // If we have number which has 3 or more digits (like "001" or "0001"),
+ // we assume this number is a year. Save the currnet raw.numCount in
+ // raw.year.
+ //
+ switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) {
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.YearEnd;
+ break;
+ case TokenType.SEP_Space:
+ dtok.dtt = DTT.YearSpace;
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if (dateParsingStates[(int)dps][(int) DTT.YearSpace] > DS.ERROR) {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.YearSpace;
+ break;
+ }
+ goto default;
+ default:
+ // Invalid separator after number number.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0090", dps);
+ return false;
+ }
+ } else {
+ // Invalid separator after number number.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0100", dps);
+ return false;
+ }
+ } else {
+ // This is a day number
+ dtok.num = tokenValue;
+ raw.AddNumber(dtok.num);
+
+ switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) {
+ //
+ // Note here we check if the numCount is less than three.
+ // When we have more than three numbers, it will be caught as error in the state machine.
+ //
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.NumEnd;
+ break;
+ case TokenType.SEP_Space:
+ case TokenType.SEP_Date:
+ dtok.dtt = DTT.NumDatesep;
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if ((dateParsingStates[(int)dps][(int) DTT.NumDatesep] == DS.ERROR)
+ && (dateParsingStates[(int)dps][(int) DTT.NumSpace] > DS.ERROR)) {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.NumSpace;
+ }
+ else {
+ dtok.dtt = DTT.NumDatesep;
+ }
+ break;
+ default:
+ // Invalid separator after number number.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0110", dps);
+ return false;
+ }
+ }
+ break;
+ case TokenType.DayOfWeekToken:
+ if (raw.dayOfWeek == -1)
+ {
+ //
+ // This is a day of week name.
+ //
+ raw.dayOfWeek = tokenValue;
+ dtok.dtt = DTT.DayOfWeek;
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0120 (DayOfWeek seen more than 1x)", dps);
+ return false;
+ }
+ break;
+ case TokenType.MonthToken:
+ if (raw.month == -1)
+ {
+ //
+ // This is a month name
+ //
+ switch(sep=str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
+ {
+ case TokenType.SEP_End:
+ dtok.dtt = DTT.MonthEnd;
+ break;
+ case TokenType.SEP_Space:
+ dtok.dtt = DTT.MonthSpace;
+ break;
+ case TokenType.SEP_Date:
+ dtok.dtt = DTT.MonthDatesep;
+ break;
+ case TokenType.SEP_Time:
+ if (!raw.hasSameDateAndTimeSeparators)
+ {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0130 (Invalid separator after month name)", dps);
+ return false;
+ }
+
+ // we have the date and time separators are same and getting a Month name, then change the token to MonthDatesep as
+ // we are sure we are not parsing time.
+ dtok.dtt = DTT.MonthDatesep;
+ break;
+ case TokenType.SEP_DateOrOffset:
+ // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
+ // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
+ if ((dateParsingStates[(int)dps][(int) DTT.MonthDatesep] == DS.ERROR)
+ && (dateParsingStates[(int)dps][(int) DTT.MonthSpace] > DS.ERROR)) {
+ str.Index = indexBeforeSeparator;
+ str.m_current = charBeforeSeparator;
+ dtok.dtt = DTT.MonthSpace;
+ }
+ else {
+ dtok.dtt = DTT.MonthDatesep;
+ }
+ break;
+ default:
+ //Invalid separator after month name
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0130 (Invalid separator after month name)", dps);
+ return false;
+ }
+ raw.month = tokenValue;
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0140 (MonthToken seen more than 1x)", dps);
+ return false;
+ }
+ break;
+ case TokenType.EraToken:
+ if (result.era != -1) {
+ result.era = tokenValue;
+ dtok.dtt = DTT.Era;
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0150 (EraToken seen when result.era already set)", dps);
+ return false;
+ }
+ break;
+ case TokenType.JapaneseEraToken:
+ // Special case for Japanese. We allow Japanese era name to be used even if the calendar is not Japanese Calendar.
+ result.calendar = JapaneseCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.GetJapaneseCalendarDTFI();
+ if (result.era != -1) {
+ result.era = tokenValue;
+ dtok.dtt = DTT.Era;
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0160 (JapaneseEraToken seen when result.era already set)", dps);
+ return false;
+ }
+ break;
+ case TokenType.TEraToken:
+ result.calendar = TaiwanCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.GetTaiwanCalendarDTFI();
+ if (result.era != -1) {
+ result.era = tokenValue;
+ dtok.dtt = DTT.Era;
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0170 (TEraToken seen when result.era already set)", dps);
+ return false;
+ }
+ break;
+ case TokenType.TimeZoneToken:
+ //
+ // This is a timezone designator
+ //
+ // NOTENOTE : for now, we only support "GMT" and "Z" (for Zulu time).
+ //
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0) {
+ // Should not have two timezone offsets.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0180 (seen GMT or Z more than 1x)", dps);
+ return false;
+ }
+ dtok.dtt = DTT.TimeZone;
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = new TimeSpan(0);
+ result.flags |= ParseFlags.TimeZoneUtc;
+ break;
+ case TokenType.EndOfString:
+ dtok.dtt = DTT.End;
+ break;
+ case TokenType.DateWordToken:
+ case TokenType.IgnorableSymbol:
+ // Date words and ignorable symbols can just be skipped over
+ break;
+ case TokenType.Am:
+ case TokenType.Pm:
+ if (raw.timeMark == TM.NotSet) {
+ raw.timeMark = (TM)tokenValue;
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0190 (AM/PM timeMark already set)", dps);
+ return false;
+ }
+ break;
+ case TokenType.UnknownToken:
+ if (Char.IsLetter(str.m_current)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_UnknowDateTimeWord", str.Index);
+ LexTraceExit("0200", dps);
+ return (false);
+ }
+
+#if !FEATURE_CORECLR
+ // If DateTimeParseIgnorePunctuation is defined, we want to have the V1.1 behavior of just
+ // ignoring any unrecognized punctuation and moving on to the next character
+ if (Environment.GetCompatibilityFlag(CompatibilityFlag.DateTimeParseIgnorePunctuation) && ((result.flags & ParseFlags.CaptureOffset) == 0)) {
+ str.GetNext();
+ LexTraceExit("0210 (success)", dps);
+ return true;
+ }
+#endif // FEATURE_CORECLR
+
+ if ((str.m_current == '-' || str.m_current == '+') && ((result.flags & ParseFlags.TimeZoneUsed) == 0)) {
+ Int32 originalIndex = str.Index;
+ if (ParseTimeZone(ref str, ref result.timeZoneOffset)) {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ LexTraceExit("0220 (success)", dps);
+ return true;
+ }
+ else {
+ // Time zone parse attempt failed. Fall through to punctuation handling.
+ str.Index = originalIndex;
+ }
+ }
+
+ // Visual Basic implements string to date conversions on top of DateTime.Parse:
+ // CDate("#10/10/95#")
+ //
+ if (VerifyValidPunctuation(ref str)) {
+ LexTraceExit("0230 (success)", dps);
+ return true;
+ }
+
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ LexTraceExit("0240", dps);
+ return false;
+ }
+
+ LexTraceExit("0250 (success)", dps);
+ return true;
+ }
+
+ private static Boolean VerifyValidPunctuation(ref __DTString str) {
+ // Compatability Behavior. Allow trailing nulls and surrounding hashes
+ Char ch = str.Value[str.Index];
+ if (ch == '#') {
+ bool foundStart = false;
+ bool foundEnd = false;
+ for (int i = 0; i < str.len; i++) {
+ ch = str.Value[i];
+ if (ch == '#') {
+ if (foundStart) {
+ if (foundEnd) {
+ // Having more than two hashes is invalid
+ return false;
+ }
+ else {
+ foundEnd = true;
+ }
+ }
+ else {
+ foundStart = true;
+ }
+ }
+ else if (ch == '\0') {
+ // Allow nulls only at the end
+ if (!foundEnd) {
+ return false;
+ }
+ }
+ else if ((!Char.IsWhiteSpace(ch))) {
+ // Anthyhing other than whitespace outside hashes is invalid
+ if (!foundStart || foundEnd) {
+ return false;
+ }
+ }
+ }
+ if (!foundEnd) {
+ // The has was un-paired
+ return false;
+ }
+ // Valid Hash usage: eat the hash and continue.
+ str.GetNext();
+ return true;
+ }
+ else if (ch == '\0') {
+ for (int i = str.Index; i < str.len; i++) {
+ if (str.Value[i] != '\0') {
+ // Nulls are only valid if they are the only trailing character
+ return false;
+ }
+ }
+ // Move to the end of the string
+ str.Index = str.len;
+ return true;
+ }
+ return false;
+ }
+
+ private const int ORDER_YMD = 0; // The order of date is Year/Month/Day.
+ private const int ORDER_MDY = 1; // The order of date is Month/Day/Year.
+ private const int ORDER_DMY = 2; // The order of date is Day/Month/Year.
+ private const int ORDER_YDM = 3; // The order of date is Year/Day/Month
+ private const int ORDER_YM = 4; // Year/Month order.
+ private const int ORDER_MY = 5; // Month/Year order.
+ private const int ORDER_MD = 6; // Month/Day order.
+ private const int ORDER_DM = 7; // Day/Month order.
+
+ //
+ // Decide the year/month/day order from the datePattern.
+ //
+ // Return 0 for YMD, 1 for MDY, 2 for DMY, otherwise -1.
+ //
+ private static Boolean GetYearMonthDayOrder(String datePattern, DateTimeFormatInfo dtfi, out int order)
+ {
+ int yearOrder = -1;
+ int monthOrder = -1;
+ int dayOrder = -1;
+ int orderCount = 0;
+
+ bool inQuote = false;
+
+ for (int i = 0; i < datePattern.Length && orderCount < 3; i++)
+ {
+ char ch = datePattern[i];
+ if (ch == '\\' || ch == '%')
+ {
+ i++;
+ continue; // Skip next character that is escaped by this backslash
+ }
+
+ if (ch == '\'' || ch == '"')
+ {
+ inQuote = !inQuote;
+ }
+
+ if (!inQuote)
+ {
+ if (ch == 'y')
+ {
+ yearOrder = orderCount++;
+
+ //
+ // Skip all year pattern charaters.
+ //
+ for(; i+1 < datePattern.Length && datePattern[i+1] == 'y'; i++)
+ {
+ // Do nothing here.
+ }
+ }
+ else if (ch == 'M')
+ {
+ monthOrder = orderCount++;
+ //
+ // Skip all month pattern characters.
+ //
+ for(; i+1 < datePattern.Length && datePattern[i+1] == 'M'; i++)
+ {
+ // Do nothing here.
+ }
+ }
+ else if (ch == 'd')
+ {
+
+ int patternCount = 1;
+ //
+ // Skip all day pattern characters.
+ //
+ for(; i+1 < datePattern.Length && datePattern[i+1] == 'd'; i++)
+ {
+ patternCount++;
+ }
+ //
+ // Make sure this is not "ddd" or "dddd", which means day of week.
+ //
+ if (patternCount <= 2)
+ {
+ dayOrder = orderCount++;
+ }
+ }
+ }
+ }
+
+ if (yearOrder == 0 && monthOrder == 1 && dayOrder == 2)
+ {
+ order = ORDER_YMD;
+ return true;
+ }
+ if (monthOrder == 0 && dayOrder == 1 && yearOrder == 2)
+ {
+ order = ORDER_MDY;
+ return true;
+ }
+ if (dayOrder == 0 && monthOrder == 1 && yearOrder == 2)
+ {
+ order = ORDER_DMY;
+ return true;
+ }
+ if (yearOrder == 0 && dayOrder == 1 && monthOrder == 2)
+ {
+ order = ORDER_YDM;
+ return true;
+ }
+ order = -1;
+ return false;
+ }
+
+ //
+ // Decide the year/month order from the pattern.
+ //
+ // Return 0 for YM, 1 for MY, otherwise -1.
+ //
+ private static Boolean GetYearMonthOrder(String pattern, DateTimeFormatInfo dtfi, out int order)
+ {
+ int yearOrder = -1;
+ int monthOrder = -1;
+ int orderCount = 0;
+
+ bool inQuote = false;
+ for (int i = 0; i < pattern.Length && orderCount < 2; i++)
+ {
+ char ch = pattern[i];
+ if (ch == '\\' || ch == '%')
+ {
+ i++;
+ continue; // Skip next character that is escaped by this backslash
+ }
+
+ if (ch == '\'' || ch == '"')
+ {
+ inQuote = !inQuote;
+ }
+
+ if (!inQuote)
+ {
+ if (ch == 'y')
+ {
+ yearOrder = orderCount++;
+
+ //
+ // Skip all year pattern charaters.
+ //
+ for(; i+1 < pattern.Length && pattern[i+1] == 'y'; i++)
+ {
+ }
+ }
+ else if (ch == 'M')
+ {
+ monthOrder = orderCount++;
+ //
+ // Skip all month pattern characters.
+ //
+ for(; i+1 < pattern.Length && pattern[i+1] == 'M'; i++)
+ {
+ }
+ }
+ }
+ }
+
+ if (yearOrder == 0 && monthOrder == 1)
+ {
+ order = ORDER_YM;
+ return true;
+ }
+ if (monthOrder == 0 && yearOrder == 1)
+ {
+ order = ORDER_MY;
+ return true;
+ }
+ order = -1;
+ return false;
+ }
+
+ //
+ // Decide the month/day order from the pattern.
+ //
+ // Return 0 for MD, 1 for DM, otherwise -1.
+ //
+ private static Boolean GetMonthDayOrder(String pattern, DateTimeFormatInfo dtfi, out int order)
+ {
+ int monthOrder = -1;
+ int dayOrder = -1;
+ int orderCount = 0;
+
+ bool inQuote = false;
+ for (int i = 0; i < pattern.Length && orderCount < 2; i++)
+ {
+ char ch = pattern[i];
+ if (ch == '\\' || ch == '%')
+ {
+ i++;
+ continue; // Skip next character that is escaped by this backslash
+ }
+
+ if (ch == '\'' || ch == '"')
+ {
+ inQuote = !inQuote;
+ }
+
+ if (!inQuote)
+ {
+ if (ch == 'd')
+ {
+ int patternCount = 1;
+ //
+ // Skip all day pattern charaters.
+ //
+ for(; i+1 < pattern.Length && pattern[i+1] == 'd'; i++)
+ {
+ patternCount++;
+ }
+
+ //
+ // Make sure this is not "ddd" or "dddd", which means day of week.
+ //
+ if (patternCount <= 2)
+ {
+ dayOrder = orderCount++;
+ }
+
+ }
+ else if (ch == 'M')
+ {
+ monthOrder = orderCount++;
+ //
+ // Skip all month pattern characters.
+ //
+ for(; i+1 < pattern.Length && pattern[i+1] == 'M'; i++)
+ {
+ }
+ }
+ }
+ }
+
+ if (monthOrder == 0 && dayOrder == 1)
+ {
+ order = ORDER_MD;
+ return true;
+ }
+ if (dayOrder == 0 && monthOrder == 1)
+ {
+ order = ORDER_DM;
+ return true;
+ }
+ order = -1;
+ return false;
+ }
+
+ //
+ // Adjust the two-digit year if necessary.
+ //
+ private static bool TryAdjustYear(ref DateTimeResult result, int year, out int adjustedYear)
+ {
+ if (year < 100)
+ {
+ try {
+ // the Calendar classes need some real work. Many of the calendars that throw
+ // don't implement a fast/non-allocating (and non-throwing) IsValid{Year|Day|Month} method.
+ // we are making a targeted try/catch fix in the in-place release but will revisit this code
+ // in the next side-by-side release.
+ year = result.calendar.ToFourDigitYear(year);
+ }
+ catch (ArgumentOutOfRangeException) {
+ adjustedYear = -1;
+ return false;
+ }
+ }
+ adjustedYear = year;
+ return true;
+ }
+
+ private static bool SetDateYMD(ref DateTimeResult result, int year, int month, int day)
+ {
+ // Note, longer term these checks should be done at the end of the parse. This current
+ // way of checking creates order dependence with parsing the era name.
+ if (result.calendar.IsValidDay(year, month, day, result.era))
+ {
+ result.SetDate(year, month, day); // YMD
+ return (true);
+ }
+ return (false);
+ }
+
+ private static bool SetDateMDY(ref DateTimeResult result, int month, int day, int year)
+ {
+ return (SetDateYMD(ref result, year, month, day));
+ }
+
+ private static bool SetDateDMY(ref DateTimeResult result, int day, int month, int year)
+ {
+ return (SetDateYMD(ref result, year, month, day));
+ }
+
+ private static bool SetDateYDM(ref DateTimeResult result, int year, int day, int month)
+ {
+ return (SetDateYMD(ref result, year, month, day));
+ }
+
+ private static void GetDefaultYear(ref DateTimeResult result, ref DateTimeStyles styles) {
+ result.Year = result.calendar.GetYear(GetDateTimeNow(ref result, ref styles));
+ result.flags |= ParseFlags.YearDefault;
+ }
+
+ // Processing teriminal case: DS.DX_NN
+ private static Boolean GetDayOfNN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
+
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1);
+
+ GetDefaultYear(ref result, ref styles);
+
+ int order;
+ if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out order)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern);
+ return false;
+ }
+
+ if (order == ORDER_MD)
+ {
+ if (SetDateYMD(ref result, result.Year, n1, n2)) // MD
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ } else {
+ // ORDER_DM
+ if (SetDateYMD(ref result, result.Year, n2, n1)) // DM
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ // Processing teriminal case: DS.DX_NNN
+ private static Boolean GetDayOfNNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1);;
+ int n3 = raw.GetNumber(2);
+
+ int order;
+ if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern);
+ return false;
+ }
+ int year;
+
+ if (order == ORDER_YMD) {
+ if (TryAdjustYear(ref result, n1, out year) && SetDateYMD(ref result, year, n2, n3)) // YMD
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ } else if (order == ORDER_MDY) {
+ if (TryAdjustYear(ref result, n3, out year) && SetDateMDY(ref result, n1, n2, year)) // MDY
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ } else if (order == ORDER_DMY) {
+ if (TryAdjustYear(ref result, n3, out year) && SetDateDMY(ref result, n1, n2, year)) // DMY
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ } else if (order == ORDER_YDM) {
+ if (TryAdjustYear(ref result, n1, out year) && SetDateYDM(ref result, year, n2, n3)) // YDM
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ private static Boolean GetDayOfMN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
+
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ // The interpretation is based on the MonthDayPattern and YearMonthPattern
+ //
+ // MonthDayPattern YearMonthPattern Interpretation
+ // --------------- ---------------- ---------------
+ // MMMM dd MMMM yyyy Day
+ // MMMM dd yyyy MMMM Day
+ // dd MMMM MMMM yyyy Year
+ // dd MMMM yyyy MMMM Day
+ //
+ // In the first and last cases, it could be either or neither, but a day is a better default interpretation
+ // than a 2 digit year.
+
+ int monthDayOrder;
+ if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern);
+ return false;
+ }
+ if (monthDayOrder == ORDER_DM) {
+ int yearMonthOrder;
+ if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.YearMonthPattern);
+ return false;
+ }
+ if (yearMonthOrder == ORDER_MY) {
+ int year;
+ if (!TryAdjustYear(ref result, raw.GetNumber(0), out year) || !SetDateYMD(ref result, year, raw.month, 1)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ GetDefaultYear(ref result, ref styles);
+ if (!SetDateYMD(ref result, result.Year, raw.month, raw.GetNumber(0))) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ return true;
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // Actions:
+ // Deal with the terminal state for Hebrew Month/Day pattern
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ private static Boolean GetHebrewDayOfNM(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ int monthDayOrder;
+ if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern);
+ return false;
+ }
+ result.Month = raw.month;
+ if (monthDayOrder == ORDER_DM || monthDayOrder == ORDER_MD)
+ {
+ if (result.calendar.IsValidDay(result.Year, result.Month, raw.GetNumber(0), result.era))
+ {
+ result.Day = raw.GetNumber(0);
+ return true;
+ }
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ private static Boolean GetDayOfNM(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ // The interpretation is based on the MonthDayPattern and YearMonthPattern
+ //
+ // MonthDayPattern YearMonthPattern Interpretation
+ // --------------- ---------------- ---------------
+ // MMMM dd MMMM yyyy Day
+ // MMMM dd yyyy MMMM Year
+ // dd MMMM MMMM yyyy Day
+ // dd MMMM yyyy MMMM Day
+ //
+ // In the first and last cases, it could be either or neither, but a day is a better default interpretation
+ // than a 2 digit year.
+
+ int monthDayOrder;
+ if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern);
+ return false;
+ }
+ if (monthDayOrder == ORDER_MD) {
+ int yearMonthOrder;
+ if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.YearMonthPattern);
+ return false;
+ }
+ if (yearMonthOrder == ORDER_YM) {
+ int year;
+ if (!TryAdjustYear(ref result, raw.GetNumber(0), out year) || !SetDateYMD(ref result, year, raw.month, 1)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ GetDefaultYear(ref result, ref styles);
+ if (!SetDateYMD(ref result, result.Year, raw.month, raw.GetNumber(0))) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ return true;
+ }
+
+ private static Boolean GetDayOfMNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1);
+
+ int order;
+ if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern);
+ return false;
+ }
+ int year;
+
+ if (order == ORDER_MDY)
+ {
+ if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
+ {
+ result.SetDate(year, raw.month, n1); // MDY
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
+ {
+ result.SetDate(year, raw.month, n2); // YMD
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ else if (order == ORDER_YMD)
+ {
+ if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
+ {
+ result.SetDate(year, raw.month, n2); // YMD
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ else if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
+ {
+ result.SetDate(year, raw.month, n1); // DMY
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+ else if (order == ORDER_DMY)
+ {
+ if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
+ {
+ result.SetDate(year, raw.month, n1); // DMY
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
+ {
+ result.SetDate(year, raw.month, n2); // YMD
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ }
+
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ private static Boolean GetDayOfYNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
+
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1);
+ String pattern = dtfi.ShortDatePattern;
+
+ // For compatibility, don't throw if we can't determine the order, but default to YMD instead
+ int order;
+ if (GetYearMonthDayOrder(pattern, dtfi, out order) && order == ORDER_YDM) {
+ if (SetDateYMD(ref result, raw.year, n2, n1)) {
+ result.flags |= ParseFlags.HaveDate;
+ return true; // Year + DM
+ }
+ }
+ else {
+ if (SetDateYMD(ref result, raw.year, n1, n2)) {
+ result.flags |= ParseFlags.HaveDate;
+ return true; // Year + MD
+ }
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ private static Boolean GetDayOfNNY(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
+
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ int n1 = raw.GetNumber(0);
+ int n2 = raw.GetNumber(1);
+
+ int order;
+ if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern);
+ return false;
+ }
+
+ if (order == ORDER_MDY || order == ORDER_YMD) {
+ if (SetDateYMD(ref result, raw.year, n1, n2)) {
+ result.flags |= ParseFlags.HaveDate;
+ return true; // MD + Year
+ }
+ } else {
+ if (SetDateYMD(ref result, raw.year, n2, n1)) {
+ result.flags |= ParseFlags.HaveDate;
+ return true; // DM + Year
+ }
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+
+ private static Boolean GetDayOfYMN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
+
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ if (SetDateYMD(ref result, raw.year, raw.month, raw.GetNumber(0))) {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ private static Boolean GetDayOfYN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ if (SetDateYMD(ref result, raw.year, raw.GetNumber(0), 1))
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ private static Boolean GetDayOfYM(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+ if ((result.flags & ParseFlags.HaveDate) != 0) {
+ // Multiple dates in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ if (SetDateYMD(ref result, raw.year, raw.month, 1))
+ {
+ result.flags |= ParseFlags.HaveDate;
+ return true;
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ private static void AdjustTimeMark(DateTimeFormatInfo dtfi, ref DateTimeRawInfo raw) {
+ // Specail case for culture which uses AM as empty string.
+ // E.g. af-ZA (0x0436)
+ // S1159 \x0000
+ // S2359 nm
+ // In this case, if we are parsing a string like "2005/09/14 12:23", we will assume this is in AM.
+
+ if (raw.timeMark == TM.NotSet) {
+ if (dtfi.AMDesignator != null && dtfi.PMDesignator != null) {
+ if (dtfi.AMDesignator.Length == 0 && dtfi.PMDesignator.Length != 0) {
+ raw.timeMark = TM.AM;
+ }
+ if (dtfi.PMDesignator.Length == 0 && dtfi.AMDesignator.Length != 0) {
+ raw.timeMark = TM.PM;
+ }
+ }
+ }
+ }
+
+ //
+ // Adjust hour according to the time mark.
+ //
+ private static Boolean AdjustHour(ref int hour, TM timeMark) {
+ if (timeMark != TM.NotSet) {
+
+ if (timeMark == TM.AM) {
+ if (hour < 0 || hour > 12) {
+ return false;
+ }
+ hour = (hour == 12) ? 0 : hour;
+ }
+ else {
+ if (hour < 0 || hour > 23) {
+ return false;
+ }
+ if (hour < 12) {
+ hour += 12;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static Boolean GetTimeOfN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if ((result.flags & ParseFlags.HaveTime) != 0) {
+ // Multiple times in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ //
+ // In this case, we need a time mark. Check if so.
+ //
+ if (raw.timeMark == TM.NotSet)
+ {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ result.Hour = raw.GetNumber(0);
+ result.flags |= ParseFlags.HaveTime;
+ return true;
+ }
+
+ private static Boolean GetTimeOfNN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ Contract.Assert(raw.numCount >= 2, "raw.numCount >= 2");
+ if ((result.flags & ParseFlags.HaveTime) != 0) {
+ // Multiple times in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ result.Hour = raw.GetNumber(0);
+ result.Minute = raw.GetNumber(1);
+ result.flags |= ParseFlags.HaveTime;
+ return true;
+ }
+
+ private static Boolean GetTimeOfNNN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if ((result.flags & ParseFlags.HaveTime) != 0) {
+ // Multiple times in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ Contract.Assert(raw.numCount >= 3, "raw.numCount >= 3");
+ result.Hour = raw.GetNumber(0);
+ result.Minute = raw.GetNumber(1);
+ result.Second = raw.GetNumber(2);
+ result.flags |= ParseFlags.HaveTime;
+ return true;
+ }
+
+ //
+ // Processing terminal state: A Date suffix followed by one number.
+ //
+ private static Boolean GetDateOfDSN(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if (raw.numCount != 1 || result.Day != -1)
+ {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ result.Day = raw.GetNumber(0);
+ return true;
+ }
+
+ private static Boolean GetDateOfNDS(ref DateTimeResult result, ref DateTimeRawInfo raw)
+ {
+ if (result.Month == -1)
+ {
+ //Should have a month suffix
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ if (result.Year != -1)
+ {
+ // Aleady has a year suffix
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ if (!TryAdjustYear(ref result, raw.GetNumber(0), out result.Year))
+ {
+ // the year value is out of range
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ result.Day = 1;
+ return true;
+ }
+
+ private static Boolean GetDateOfNNDS(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+
+ // For partial CJK Dates, the only valid formats are with a specified year, followed by two numbers, which
+ // will be the Month and Day, and with a specified Month, when the numbers are either the year and day or
+ // day and year, depending on the short date pattern.
+
+ if ((result.flags & ParseFlags.HaveYear) != 0) {
+ if (((result.flags & ParseFlags.HaveMonth) == 0) && ((result.flags & ParseFlags.HaveDay) == 0)) {
+ if (TryAdjustYear(ref result, raw.year, out result.Year) && SetDateYMD(ref result, result.Year, raw.GetNumber(0), raw.GetNumber(1))) {
+ return true;
+ }
+ }
+ }
+ else if ((result.flags & ParseFlags.HaveMonth) != 0) {
+ if (((result.flags & ParseFlags.HaveYear) == 0) && ((result.flags & ParseFlags.HaveDay) == 0)) {
+ int order;
+ if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern);
+ return false;
+ }
+ int year;
+ if (order == ORDER_YMD) {
+ if (TryAdjustYear(ref result, raw.GetNumber(0), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(1))) {
+ return true;
+ }
+ }
+ else {
+ if (TryAdjustYear(ref result, raw.GetNumber(1), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(0))){
+ return true;
+ }
+ }
+ }
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ //
+ // A date suffix is found, use this method to put the number into the result.
+ //
+ private static bool ProcessDateTimeSuffix(ref DateTimeResult result, ref DateTimeRawInfo raw, ref DateTimeToken dtok)
+ {
+ switch (dtok.suffix)
+ {
+ case TokenType.SEP_YearSuff:
+ if ((result.flags & ParseFlags.HaveYear) != 0) {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveYear;
+ result.Year = raw.year = dtok.num;
+ break;
+ case TokenType.SEP_MonthSuff:
+ if ((result.flags & ParseFlags.HaveMonth) != 0) {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveMonth;
+ result.Month= raw.month = dtok.num;
+ break;
+ case TokenType.SEP_DaySuff:
+ if ((result.flags & ParseFlags.HaveDay) != 0) {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveDay;
+ result.Day = dtok.num;
+ break;
+ case TokenType.SEP_HourSuff:
+ if ((result.flags & ParseFlags.HaveHour) != 0) {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveHour;
+ result.Hour = dtok.num;
+ break;
+ case TokenType.SEP_MinuteSuff:
+ if ((result.flags & ParseFlags.HaveMinute) != 0) {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveMinute;
+ result.Minute = dtok.num;
+ break;
+ case TokenType.SEP_SecondSuff:
+ if ((result.flags & ParseFlags.HaveSecond) != 0) {
+ return false;
+ }
+ result.flags |= ParseFlags.HaveSecond;
+ result.Second = dtok.num;
+ break;
+ }
+ return true;
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // This is used by DateTime.Parse().
+ // Process the terminal state for the Hebrew calendar parsing.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static Boolean ProcessHebrewTerminalState(DS dps, ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
+ // The following are accepted terminal state for Hebrew date.
+ switch (dps) {
+ case DS.DX_MNN:
+ // Deal with the default long/short date format when the year number is ambigous (i.e. year < 100).
+ raw.year = raw.GetNumber(1);
+ if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true)) {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null);
+ return false;
+ }
+ if (!GetDayOfMNN(ref result, ref raw, dtfi)) {
+ return false;
+ }
+ break;
+ case DS.DX_YMN:
+ // Deal with the default long/short date format when the year number is NOT ambigous (i.e. year >= 100).
+ if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true)) {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null);
+ return false;
+ }
+ if (!GetDayOfYMN(ref result, ref raw, dtfi)) {
+ return false;
+ }
+ break;
+ case DS.DX_NM:
+ case DS.DX_MN:
+ // Deal with Month/Day pattern.
+ GetDefaultYear(ref result, ref styles);
+ if (!dtfi.YearMonthAdjustment(ref result.Year, ref raw.month, true)) {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null);
+ return false;
+ }
+ if (!GetHebrewDayOfNM(ref result, ref raw, dtfi)) {
+ return false;
+ }
+ break;
+ case DS.DX_YM:
+ // Deal with Year/Month pattern.
+ if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true)) {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null);
+ return false;
+ }
+ if (!GetDayOfYM(ref result, ref raw, dtfi)) {
+ return false;
+ }
+ break;
+ case DS.TX_N:
+ // Deal hour + AM/PM
+ if (!GetTimeOfN(dtfi, ref result, ref raw)) {
+ return false;
+ }
+ break;
+ case DS.TX_NN:
+ if (!GetTimeOfNN(dtfi, ref result, ref raw)) {
+ return false;
+ }
+ break;
+ case DS.TX_NNN:
+ if (!GetTimeOfNNN(dtfi, ref result, ref raw)) {
+ return false;
+ }
+ break;
+ default:
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+
+ }
+ if (dps > DS.ERROR)
+ {
+ //
+ // We have reached a terminal state. Reset the raw num count.
+ //
+ raw.numCount = 0;
+ }
+ return true;
+ }
+
+ //
+ // A terminal state has been reached, call the appropriate function to fill in the parsing result.
+ // Return true if the state is a terminal state.
+ //
+ internal static Boolean ProcessTerminaltState(DS dps, ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
+ {
+
+ bool passed = true;
+ switch (dps)
+ {
+ case DS.DX_NN:
+ passed = GetDayOfNN(ref result, ref styles, ref raw, dtfi);
+ break;
+ case DS.DX_NNN:
+ passed = GetDayOfNNN(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_MN:
+ passed = GetDayOfMN(ref result, ref styles, ref raw, dtfi);
+ break;
+ case DS.DX_NM:
+ passed = GetDayOfNM(ref result, ref styles, ref raw, dtfi);
+ break;
+ case DS.DX_MNN:
+ passed = GetDayOfMNN(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_DS:
+ // The result has got the correct value. No need to process.
+ passed = true;
+ break;
+ case DS.DX_YNN:
+ passed = GetDayOfYNN(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_NNY:
+ passed = GetDayOfNNY(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_YMN:
+ passed = GetDayOfYMN(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_YN:
+ passed = GetDayOfYN(ref result, ref raw, dtfi);
+ break;
+ case DS.DX_YM:
+ passed = GetDayOfYM(ref result, ref raw, dtfi);
+ break;
+ case DS.TX_N:
+ passed = GetTimeOfN(dtfi, ref result, ref raw);
+ break;
+ case DS.TX_NN:
+ passed = GetTimeOfNN(dtfi, ref result, ref raw);
+ break;
+ case DS.TX_NNN:
+ passed = GetTimeOfNNN(dtfi, ref result, ref raw);
+ break;
+ case DS.TX_TS:
+ // The result has got the correct value. Nothing to do.
+ passed = true;
+ break;
+ case DS.DX_DSN:
+ passed = GetDateOfDSN(ref result, ref raw);
+ break;
+ case DS.DX_NDS:
+ passed = GetDateOfNDS(ref result, ref raw);
+ break;
+ case DS.DX_NNDS:
+ passed = GetDateOfNNDS(ref result, ref raw, dtfi);
+ break;
+ }
+
+ PTSTraceExit(dps, passed);
+ if (!passed) {
+ return false;
+ }
+
+ if (dps > DS.ERROR)
+ {
+ //
+ // We have reached a terminal state. Reset the raw num count.
+ //
+ raw.numCount = 0;
+ }
+ return true;
+ }
+
+ internal static DateTime Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles) {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ result.Init();
+ if (TryParse(s, dtfi, styles, ref result)) {
+ return result.parsedDate;
+ }
+ else {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+ internal static DateTime Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out TimeSpan offset) {
+ DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
+ result.Init();
+ result.flags |= ParseFlags.CaptureOffset;
+ if (TryParse(s, dtfi, styles, ref result)) {
+ offset = result.timeZoneOffset;
+ return result.parsedDate;
+ }
+ else {
+ throw GetDateTimeParseException(ref result);
+ }
+ }
+
+
+ internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result) {
+ result = DateTime.MinValue;
+ DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result.
+ resultData.Init();
+ if (TryParse(s, dtfi, styles, ref resultData)) {
+ result = resultData.parsedDate;
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result, out TimeSpan offset) {
+ result = DateTime.MinValue;
+ offset = TimeSpan.Zero;
+ DateTimeResult parseResult = new DateTimeResult(); // The buffer to store the parsing result.
+ parseResult.Init();
+ parseResult.flags |= ParseFlags.CaptureOffset;
+ if (TryParse(s, dtfi, styles, ref parseResult)) {
+ result = parseResult.parsedDate;
+ offset = parseResult.timeZoneOffset;
+ return true;
+ }
+ return false;
+ }
+
+
+ //
+ // This is the real method to do the parsing work.
+ //
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, ref DateTimeResult result) {
+ if (s == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "s");
+ return false;
+ }
+ if (s.Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ Contract.Assert(dtfi != null, "dtfi == null");
+
+#if _LOGGING
+ DTFITrace(dtfi);
+#endif
+
+ DateTime time;
+ //
+ // First try the predefined format.
+ //
+
+ DS dps = DS.BEGIN; // Date Parsing State.
+ bool reachTerminalState = false;
+
+ DateTimeToken dtok = new DateTimeToken(); // The buffer to store the parsing token.
+ dtok.suffix = TokenType.SEP_Unk;
+ DateTimeRawInfo raw = new DateTimeRawInfo(); // The buffer to store temporary parsing information.
+ unsafe {
+ Int32 * numberPointer = stackalloc Int32[3];
+ raw.Init(numberPointer);
+ }
+ raw.hasSameDateAndTimeSeparators = dtfi.DateSeparator.Equals(dtfi.TimeSeparator, StringComparison.Ordinal);
+
+ result.calendar = dtfi.Calendar;
+ result.era = Calendar.CurrentEra;
+
+ //
+ // The string to be parsed. Use a __DTString wrapper so that we can trace the index which
+ // indicates the begining of next token.
+ //
+ __DTString str = new __DTString(s, dtfi);
+
+ str.GetNext();
+
+ //
+ // The following loop will break out when we reach the end of the str.
+ //
+ do {
+ //
+ // Call the lexer to get the next token.
+ //
+ // If we find a era in Lex(), the era value will be in raw.era.
+ if (!Lex(dps, ref str, ref dtok, ref raw, ref result, ref dtfi, styles))
+ {
+ TPTraceExit("0000", dps);
+ return false;
+ }
+
+ //
+ // If the token is not unknown, process it.
+ // Otherwise, just discard it.
+ //
+ if (dtok.dtt != DTT.Unk)
+ {
+ //
+ // Check if we got any CJK Date/Time suffix.
+ // Since the Date/Time suffix tells us the number belongs to year/month/day/hour/minute/second,
+ // store the number in the appropriate field in the result.
+ //
+ if (dtok.suffix != TokenType.SEP_Unk)
+ {
+ if (!ProcessDateTimeSuffix(ref result, ref raw, ref dtok)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ TPTraceExit("0010", dps);
+ return false;
+ }
+
+ dtok.suffix = TokenType.SEP_Unk; // Reset suffix to SEP_Unk;
+ }
+
+ if (dtok.dtt == DTT.NumLocalTimeMark) {
+ if (dps == DS.D_YNd || dps == DS.D_YN) {
+ // Consider this as ISO 8601 format:
+ // "yyyy-MM-dd'T'HH:mm:ss" 1999-10-31T02:00:00
+ TPTraceExit("0020", dps);
+ return (ParseISO8601(ref raw, ref str, styles, ref result));
+ }
+ else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ TPTraceExit("0030", dps);
+ return false;
+ }
+ }
+
+ if (raw.hasSameDateAndTimeSeparators)
+ {
+ if (dtok.dtt == DTT.YearEnd || dtok.dtt == DTT.YearSpace || dtok.dtt == DTT.YearDateSep)
+ {
+ // When time and date separators are same and we are hitting a year number while the first parsed part of the string was recognized
+ // as part of time (and not a date) DS.T_Nt, DS.T_NNt then change the state to be a date so we try to parse it as a date instead
+ if (dps == DS.T_Nt)
+ {
+ dps = DS.D_Nd;
+ }
+ if (dps == DS.T_NNt)
+ {
+ dps = DS.D_NNd;
+ }
+ }
+
+ bool atEnd = str.AtEnd();
+ if (dateParsingStates[(int)dps][(int)dtok.dtt] == DS.ERROR || atEnd)
+ {
+ switch (dtok.dtt)
+ {
+ // we have the case of Serbia have dates in forms 'd.M.yyyy.' so we can expect '.' after the date parts.
+ // changing the token to end with space instead of Date Separator will avoid failing the parsing.
+
+ case DTT.YearDateSep: dtok.dtt = atEnd ? DTT.YearEnd : DTT.YearSpace; break;
+ case DTT.NumDatesep: dtok.dtt = atEnd ? DTT.NumEnd : DTT.NumSpace; break;
+ case DTT.NumTimesep: dtok.dtt = atEnd ? DTT.NumEnd : DTT.NumSpace; break;
+ case DTT.MonthDatesep: dtok.dtt = atEnd ? DTT.MonthEnd : DTT.MonthSpace; break;
+ }
+ }
+ }
+
+ //
+ // Advance to the next state, and continue
+ //
+ dps = dateParsingStates[(int)dps][(int)dtok.dtt];
+
+ if (dps == DS.ERROR)
+ {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ TPTraceExit("0040 (invalid state transition)", dps);
+ return false;
+ }
+ else if (dps > DS.ERROR)
+ {
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) {
+ if (!ProcessHebrewTerminalState(dps, ref result, ref styles, ref raw, dtfi)) {
+ TPTraceExit("0050 (ProcessHebrewTerminalState)", dps);
+ return false;
+ }
+ } else {
+ if (!ProcessTerminaltState(dps, ref result, ref styles, ref raw, dtfi)) {
+ TPTraceExit("0060 (ProcessTerminaltState)", dps);
+ return false;
+ }
+ }
+ reachTerminalState = true;
+
+ //
+ // If we have reached a terminal state, start over from DS.BEGIN again.
+ // For example, when we parsed "1999-12-23 13:30", we will reach a terminal state at "1999-12-23",
+ // and we start over so we can continue to parse "12:30".
+ //
+ dps = DS.BEGIN;
+ }
+ }
+ } while (dtok.dtt != DTT.End && dtok.dtt != DTT.NumEnd && dtok.dtt != DTT.MonthEnd);
+
+ if (!reachTerminalState) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ TPTraceExit("0070 (did not reach terminal state)", dps);
+ return false;
+ }
+
+ AdjustTimeMark(dtfi, ref raw);
+ if (!AdjustHour(ref result.Hour, raw.timeMark)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ TPTraceExit("0080 (AdjustHour)", dps);
+ return false;
+ }
+
+ // Check if the parased string only contains hour/minute/second values.
+ bool bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);
+
+ //
+ // Check if any year/month/day is missing in the parsing string.
+ // If yes, get the default value from today's date.
+ //
+ if (!CheckDefaultDateTime(ref result, ref result.calendar, styles)) {
+ TPTraceExit("0090 (failed to fill in missing year/month/day defaults)", dps);
+ return false;
+ }
+
+ if (!result.calendar.TryToDateTime(result.Year, result.Month, result.Day,
+ result.Hour, result.Minute, result.Second, 0, result.era, out time)) {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null);
+ TPTraceExit("0100 (result.calendar.TryToDateTime)", dps);
+ return false;
+ }
+ if (raw.fraction > 0) {
+ time = time.AddTicks((long)Math.Round(raw.fraction * Calendar.TicksPerSecond));
+ }
+
+ //
+ // We have to check day of week before we adjust to the time zone.
+ // Otherwise, the value of day of week may change after adjustting to the time zone.
+ //
+ if (raw.dayOfWeek != -1) {
+ //
+ // Check if day of week is correct.
+ //
+ if (raw.dayOfWeek != (int)result.calendar.GetDayOfWeek(time)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDayOfWeek", null);
+ TPTraceExit("0110 (dayOfWeek check)", dps);
+ return false;
+ }
+ }
+
+ result.parsedDate = time;
+
+ if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly)) {
+ TPTraceExit("0120 (DetermineTimeZoneAdjustments)", dps);
+ return false;
+ }
+ TPTraceExit("0130 (success)", dps);
+ return true;
+ }
+
+
+ // Handles time zone adjustments and sets DateTimeKind values as required by the styles
+ private static Boolean DetermineTimeZoneAdjustments(ref DateTimeResult result, DateTimeStyles styles, Boolean bTimeOnly) {
+
+ if ((result.flags & ParseFlags.CaptureOffset) != 0) {
+ // This is a DateTimeOffset parse, so the offset will actually be captured directly, and
+ // no adjustment is required in most cases
+ return DateTimeOffsetTimeZonePostProcessing(ref result, styles);
+ }
+#if FEATURE_CORECLR // on CoreCLR DateTime is also restricted to +- 14:00, just like DateTimeOffset
+ else {
+ Int64 offsetTicks = result.timeZoneOffset.Ticks;
+
+ // the DateTime offset must be within +- 14:00 hours.
+ if (offsetTicks < DateTimeOffset.MinOffset || offsetTicks > DateTimeOffset.MaxOffset) {
+ result.SetFailure(ParseFailureKind.Format, "Format_OffsetOutOfRange", null);
+ return false;
+ }
+ }
+#endif // FEATURE_CORECLR
+
+ // The flags AssumeUniveral and AssumeLocal only apply when the input does not have a time zone
+ if ((result.flags & ParseFlags.TimeZoneUsed) == 0) {
+
+ // If AssumeLocal or AssumeLocal is used, there will always be a kind specified. As in the
+ // case when a time zone is present, it will default to being local unless AdjustToUniversal
+ // is present. These comparisons determine whether setting the kind is sufficient, or if a
+ // time zone adjustment is required. For consistentcy with the rest of parsing, it is desirable
+ // to fall through to the Adjust methods below, so that there is consist handling of boundary
+ // cases like wrapping around on time-only dates and temporarily allowing an adjusted date
+ // to exceed DateTime.MaxValue
+ if ((styles & DateTimeStyles.AssumeLocal) != 0) {
+ if ((styles & DateTimeStyles.AdjustToUniversal) != 0) {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeZoneInfo.GetLocalUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+ else {
+ result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Local);
+ return true;
+ }
+ }
+ else if ((styles & DateTimeStyles.AssumeUniversal) != 0) {
+ if ((styles & DateTimeStyles.AdjustToUniversal) != 0) {
+ result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc);
+ return true;
+ }
+ else {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+ }
+ else {
+ // No time zone and no Assume flags, so DateTimeKind.Unspecified is fine
+ Contract.Assert(result.parsedDate.Kind == DateTimeKind.Unspecified, "result.parsedDate.Kind == DateTimeKind.Unspecified");
+ return true;
+ }
+ }
+
+ if (((styles & DateTimeStyles.RoundtripKind) != 0) && ((result.flags & ParseFlags.TimeZoneUtc) != 0)) {
+ result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc);
+ return true;
+ }
+
+ if ((styles & DateTimeStyles.AdjustToUniversal) != 0) {
+ return (AdjustTimeZoneToUniversal(ref result));
+ }
+ return (AdjustTimeZoneToLocal(ref result, bTimeOnly));
+ }
+
+ // Apply validation and adjustments specific to DateTimeOffset
+ private static Boolean DateTimeOffsetTimeZonePostProcessing(ref DateTimeResult result, DateTimeStyles styles) {
+
+ // For DateTimeOffset, default to the Utc or Local offset when an offset was not specified by
+ // the input string.
+ if ((result.flags & ParseFlags.TimeZoneUsed) == 0) {
+ if ((styles & DateTimeStyles.AssumeUniversal) != 0) {
+ // AssumeUniversal causes the offset to default to zero (0)
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+ else {
+ // AssumeLocal causes the offset to default to Local. This flag is on by default for DateTimeOffset.
+ result.timeZoneOffset = TimeZoneInfo.GetLocalUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime);
+ }
+ }
+
+ Int64 offsetTicks = result.timeZoneOffset.Ticks;
+
+ // there should be no overflow, because the offset can be no more than -+100 hours and the date already
+ // fits within a DateTime.
+ Int64 utcTicks = result.parsedDate.Ticks - offsetTicks;
+
+ // For DateTimeOffset, both the parsed time and the corresponding UTC value must be within the boundaries
+ // of a DateTime instance.
+ if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks) {
+ result.SetFailure(ParseFailureKind.Format, "Format_UTCOutOfRange", null);
+ return false;
+ }
+
+ // the offset must be within +- 14:00 hours.
+ if (offsetTicks < DateTimeOffset.MinOffset || offsetTicks > DateTimeOffset.MaxOffset) {
+ result.SetFailure(ParseFailureKind.Format, "Format_OffsetOutOfRange", null);
+ return false;
+ }
+
+ // DateTimeOffset should still honor the AdjustToUniversal flag for consistency with DateTime. It means you
+ // want to return an adjusted UTC value, so store the utcTicks in the DateTime and set the offset to zero
+ if ((styles & DateTimeStyles.AdjustToUniversal) != 0) {
+ if (((result.flags & ParseFlags.TimeZoneUsed) == 0) && ((styles & DateTimeStyles.AssumeUniversal) == 0)) {
+ // Handle the special case where the timeZoneOffset was defaulted to Local
+ Boolean toUtcResult = AdjustTimeZoneToUniversal(ref result);
+ result.timeZoneOffset = TimeSpan.Zero;
+ return toUtcResult;
+ }
+
+ // The constructor should always succeed because of the range check earlier in the function
+ // Althought it is UTC, internally DateTimeOffset does not use this flag
+ result.parsedDate = new DateTime(utcTicks, DateTimeKind.Utc);
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+
+ return true;
+ }
+
+
+ //
+ // Adjust the specified time to universal time based on the supplied timezone.
+ // E.g. when parsing "2001/06/08 14:00-07:00",
+ // the time is 2001/06/08 14:00, and timeZoneOffset = -07:00.
+ // The result will be "2001/06/08 21:00"
+ //
+ private static Boolean AdjustTimeZoneToUniversal(ref DateTimeResult result) {
+ long resultTicks = result.parsedDate.Ticks;
+ resultTicks -= result.timeZoneOffset.Ticks;
+ if (resultTicks < 0) {
+ resultTicks += Calendar.TicksPerDay;
+ }
+
+ if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks) {
+ result.SetFailure(ParseFailureKind.Format, "Format_DateOutOfRange", null);
+ return false;
+ }
+ result.parsedDate = new DateTime(resultTicks, DateTimeKind.Utc);
+ return true;
+ }
+
+ //
+ // Adjust the specified time to universal time based on the supplied timezone,
+ // and then convert to local time.
+ // E.g. when parsing "2001/06/08 14:00-04:00", and local timezone is GMT-7.
+ // the time is 2001/06/08 14:00, and timeZoneOffset = -05:00.
+ // The result will be "2001/06/08 11:00"
+ //
+ private static Boolean AdjustTimeZoneToLocal(ref DateTimeResult result, bool bTimeOnly) {
+ long resultTicks = result.parsedDate.Ticks;
+ // Convert to local ticks
+ TimeZoneInfo tz = TimeZoneInfo.Local;
+ Boolean isAmbiguousLocalDst = false;
+ if (resultTicks < Calendar.TicksPerDay) {
+ //
+ // This is time of day.
+ //
+
+ // Adjust timezone.
+ resultTicks -= result.timeZoneOffset.Ticks;
+ // If the time is time of day, use the current timezone offset.
+ resultTicks += tz.GetUtcOffset(bTimeOnly ? DateTime.Now: result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks;
+
+ if (resultTicks < 0) {
+ resultTicks += Calendar.TicksPerDay;
+ }
+ } else {
+ // Adjust timezone to GMT.
+ resultTicks -= result.timeZoneOffset.Ticks;
+ if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks) {
+ // If the result ticks is greater than DateTime.MaxValue, we can not create a DateTime from this ticks.
+ // In this case, keep using the old code.
+ resultTicks += tz.GetUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks;
+ } else {
+ // Convert the GMT time to local time.
+ DateTime utcDt = new DateTime(resultTicks, DateTimeKind.Utc);
+ Boolean isDaylightSavings = false;
+ resultTicks += TimeZoneInfo.GetUtcOffsetFromUtc(utcDt, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst).Ticks;
+ }
+ }
+ if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks) {
+ result.parsedDate = DateTime.MinValue;
+ result.SetFailure(ParseFailureKind.Format, "Format_DateOutOfRange", null);
+ return false;
+ }
+ result.parsedDate = new DateTime(resultTicks, DateTimeKind.Local, isAmbiguousLocalDst);
+ return true;
+ }
+
+ //
+ // Parse the ISO8601 format string found during Parse();
+ //
+ //
+ private static bool ParseISO8601(ref DateTimeRawInfo raw, ref __DTString str, DateTimeStyles styles, ref DateTimeResult result) {
+ if (raw.year < 0 || raw.GetNumber(0) < 0 || raw.GetNumber(1) < 0) {
+ }
+ str.Index--;
+ int hour, minute;
+ int second = 0;
+ double partSecond = 0;
+
+ str.SkipWhiteSpaces();
+ if (!ParseDigits(ref str, 2, out hour)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ str.SkipWhiteSpaces();
+ if (!str.Match(':')) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ str.SkipWhiteSpaces();
+ if (!ParseDigits(ref str, 2, out minute)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ str.SkipWhiteSpaces();
+ if (str.Match(':')) {
+ str.SkipWhiteSpaces();
+ if (!ParseDigits(ref str, 2, out second)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ if (str.Match('.')) {
+ if (!ParseFraction(ref str, out partSecond)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ str.Index--;
+ }
+ str.SkipWhiteSpaces();
+ }
+ if (str.GetNext()) {
+ char ch = str.GetChar();
+ if (ch == '+' || ch == '-') {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ if (!ParseTimeZone(ref str, ref result.timeZoneOffset)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ } else if (ch == 'Z' || ch == 'z') {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ result.flags |= ParseFlags.TimeZoneUtc;
+ } else {
+ str.Index--;
+ }
+ str.SkipWhiteSpaces();
+ if (str.Match('#')) {
+ if (!VerifyValidPunctuation(ref str)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ str.SkipWhiteSpaces();
+ }
+ if (str.Match('\0')) {
+ if (!VerifyValidPunctuation(ref str)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ }
+ if (str.GetNext()) {
+ // If this is true, there were non-white space characters remaining in the DateTime
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ }
+
+ DateTime time;
+ Calendar calendar = GregorianCalendar.GetDefaultInstance();
+ if (!calendar.TryToDateTime(raw.year, raw.GetNumber(0), raw.GetNumber(1),
+ hour, minute, second, 0, result.era, out time)) {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null);
+ return false;
+ }
+
+ time = time.AddTicks((long)Math.Round(partSecond * Calendar.TicksPerSecond));
+ result.parsedDate = time;
+ if (!DetermineTimeZoneAdjustments(ref result, styles, false)) {
+ return false;
+ }
+ return true;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Parse the current word as a Hebrew number.
+ // This is used by DateTime.ParseExact().
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static bool MatchHebrewDigits(ref __DTString str, int digitLen, out int number) {
+ number = 0;
+
+ // Create a context object so that we can parse the Hebrew number text character by character.
+ HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
+
+ // Set this to ContinueParsing so that we will run the following while loop in the first time.
+ HebrewNumberParsingState state = HebrewNumberParsingState.ContinueParsing;
+
+ while (state == HebrewNumberParsingState.ContinueParsing && str.GetNext()) {
+ state = HebrewNumber.ParseByChar(str.GetChar(), ref context);
+ }
+
+ if (state == HebrewNumberParsingState.FoundEndOfHebrewNumber) {
+ // If we have reached a terminal state, update the result and returns.
+ number = context.result;
+ return (true);
+ }
+
+ // If we run out of the character before reaching FoundEndOfHebrewNumber, or
+ // the state is InvalidHebrewNumber or ContinueParsing, we fail to match a Hebrew number.
+ // Return an error.
+ return false;
+ }
+
+ /*=================================ParseDigits==================================
+ **Action: Parse the number string in __DTString that are formatted using
+ ** the following patterns:
+ ** "0", "00", and "000..0"
+ **Returns: the integer value
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if error in parsing number.
+ ==============================================================================*/
+
+ internal static bool ParseDigits(ref __DTString str, int digitLen, out int result) {
+ if (digitLen == 1) {
+ // 1 really means 1 or 2 for this call
+ return ParseDigits(ref str, 1, 2, out result);
+ }
+ else {
+ return ParseDigits(ref str, digitLen, digitLen, out result);
+ }
+ }
+
+ internal static bool ParseDigits(ref __DTString str, int minDigitLen, int maxDigitLen, out int result) {
+ Contract.Assert(minDigitLen > 0, "minDigitLen > 0");
+ Contract.Assert(maxDigitLen < 9, "maxDigitLen < 9");
+ Contract.Assert(minDigitLen <= maxDigitLen, "minDigitLen <= maxDigitLen");
+ result = 0;
+ int startingIndex = str.Index;
+ int tokenLength = 0;
+ while (tokenLength < maxDigitLen) {
+ if (!str.GetNextDigit()) {
+ str.Index--;
+ break;
+ }
+ result = result * 10 + str.GetDigit();
+ tokenLength++;
+ }
+ if (tokenLength < minDigitLen) {
+ str.Index = startingIndex;
+ return false;
+ }
+ return true;
+ }
+
+ /*=================================ParseFractionExact==================================
+ **Action: Parse the number string in __DTString that are formatted using
+ ** the following patterns:
+ ** "0", "00", and "000..0"
+ **Returns: the fraction value
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if error in parsing number.
+ ==============================================================================*/
+
+ private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, ref double result) {
+ if (!str.GetNextDigit()) {
+ str.Index--;
+ return false;
+ }
+ result = str.GetDigit();
+
+ int digitLen = 1;
+ for (; digitLen < maxDigitLen; digitLen++) {
+ if (!str.GetNextDigit()) {
+ str.Index--;
+ break;
+ }
+ result = result * 10 + str.GetDigit();
+ }
+
+ result = ((double)result / Math.Pow(10, digitLen));
+ return (digitLen == maxDigitLen);
+ }
+
+ /*=================================ParseSign==================================
+ **Action: Parse a positive or a negative sign.
+ **Returns: true if postive sign. flase if negative sign.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if end of string is encountered or a sign
+ ** symbol is not found.
+ ==============================================================================*/
+
+ private static bool ParseSign(ref __DTString str, ref bool result) {
+ if (!str.GetNext()) {
+ // A sign symbol ('+' or '-') is expected. However, end of string is encountered.
+ return false;
+ }
+ char ch = str.GetChar();
+ if (ch == '+') {
+ result = true;
+ return (true);
+ } else if (ch == '-') {
+ result = false;
+ return (true);
+ }
+ // A sign symbol ('+' or '-') is expected.
+ return false;
+ }
+
+ /*=================================ParseTimeZoneOffset==================================
+ **Action: Parse the string formatted using "z", "zz", "zzz" in DateTime.Format().
+ **Returns: the TimeSpan for the parsed timezone offset.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ ** len: the repeated number of the "z"
+ **Exceptions: FormatException if errors in parsing.
+ ==============================================================================*/
+
+ private static bool ParseTimeZoneOffset(ref __DTString str, int len, ref TimeSpan result) {
+ bool isPositive = true;
+ int hourOffset;
+ int minuteOffset = 0;
+
+ switch (len) {
+ case 1:
+ case 2:
+ if (!ParseSign(ref str, ref isPositive)) {
+ return (false);
+ }
+ if (!ParseDigits(ref str, len, out hourOffset)) {
+ return (false);
+ }
+ break;
+ default:
+ if (!ParseSign(ref str, ref isPositive)) {
+ return (false);
+ }
+
+ // Parsing 1 digit will actually parse 1 or 2.
+ if (!ParseDigits(ref str, 1, out hourOffset)) {
+ return (false);
+ }
+ // ':' is optional.
+ if (str.Match(":")) {
+ // Found ':'
+ if (!ParseDigits(ref str, 2, out minuteOffset)) {
+ return (false);
+ }
+ } else {
+ // Since we can not match ':', put the char back.
+ str.Index--;
+ if (!ParseDigits(ref str, 2, out minuteOffset)) {
+ return (false);
+ }
+ }
+ break;
+ }
+ if (minuteOffset < 0 || minuteOffset >= 60) {
+ return false;
+ }
+
+ result = (new TimeSpan(hourOffset, minuteOffset, 0));
+ if (!isPositive) {
+ result = result.Negate();
+ }
+ return (true);
+ }
+
+ /*=================================MatchAbbreviatedMonthName==================================
+ **Action: Parse the abbreviated month name from string starting at str.Index.
+ **Returns: A value from 1 to 12 for the first month to the twelveth month.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if an abbreviated month name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchAbbreviatedMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) {
+ int maxMatchStrLen = 0;
+ result = -1;
+ if (str.GetNext()) {
+ //
+ // Scan the month names (note that some calendars has 13 months) and find
+ // the matching month name which has the max string length.
+ // We need to do this because some cultures (e.g. "cs-CZ") which have
+ // abbreviated month names with the same prefix.
+ //
+ int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12: 13);
+ for (int i = 1; i <= monthsInYear; i++) {
+ String searchStr = dtfi.GetAbbreviatedMonthName(i);
+ int matchStrLen = searchStr.Length;
+ if ( dtfi.HasSpacesInMonthNames
+ ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
+ : str.MatchSpecifiedWord(searchStr)) {
+ if (matchStrLen > maxMatchStrLen) {
+ maxMatchStrLen = matchStrLen;
+ result = i;
+ }
+ }
+ }
+
+ // Search leap year form.
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) {
+ int tempResult = str.MatchLongestWords(dtfi.internalGetLeapYearMonthNames(), ref maxMatchStrLen);
+ // We found a longer match in the leap year month name. Use this as the result.
+ // The result from MatchLongestWords is 0 ~ length of word array.
+ // So we increment the result by one to become the month value.
+ if (tempResult >= 0) {
+ result = tempResult + 1;
+ }
+ }
+
+
+ }
+ if (result > 0) {
+ str.Index += (maxMatchStrLen - 1);
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchMonthName==================================
+ **Action: Parse the month name from string starting at str.Index.
+ **Returns: A value from 1 to 12 indicating the first month to the twelveth month.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a month name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) {
+ int maxMatchStrLen = 0;
+ result = -1;
+ if (str.GetNext()) {
+ //
+ // Scan the month names (note that some calendars has 13 months) and find
+ // the matching month name which has the max string length.
+ // We need to do this because some cultures (e.g. "vi-VN") which have
+ // month names with the same prefix.
+ //
+ int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12: 13);
+ for (int i = 1; i <= monthsInYear; i++) {
+ String searchStr = dtfi.GetMonthName(i);
+ int matchStrLen = searchStr.Length;
+ if ( dtfi.HasSpacesInMonthNames
+ ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
+ : str.MatchSpecifiedWord(searchStr)) {
+ if (matchStrLen > maxMatchStrLen) {
+ maxMatchStrLen = matchStrLen;
+ result = i;
+ }
+ }
+ }
+
+ // Search genitive form.
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0) {
+ int tempResult = str.MatchLongestWords(dtfi.MonthGenitiveNames, ref maxMatchStrLen);
+ // We found a longer match in the genitive month name. Use this as the result.
+ // The result from MatchLongestWords is 0 ~ length of word array.
+ // So we increment the result by one to become the month value.
+ if (tempResult >= 0) {
+ result = tempResult + 1;
+ }
+ }
+
+ // Search leap year form.
+ if ((dtfi.FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) {
+ int tempResult = str.MatchLongestWords(dtfi.internalGetLeapYearMonthNames(), ref maxMatchStrLen);
+ // We found a longer match in the leap year month name. Use this as the result.
+ // The result from MatchLongestWords is 0 ~ length of word array.
+ // So we increment the result by one to become the month value.
+ if (tempResult >= 0) {
+ result = tempResult + 1;
+ }
+ }
+
+
+ }
+
+ if (result > 0) {
+ str.Index += (maxMatchStrLen - 1);
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchAbbreviatedDayName==================================
+ **Action: Parse the abbreviated day of week name from string starting at str.Index.
+ **Returns: A value from 0 to 6 indicating Sunday to Saturday.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a abbreviated day of week name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchAbbreviatedDayName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) {
+ int maxMatchStrLen = 0;
+ result = -1;
+ if (str.GetNext()) {
+ for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) {
+ String searchStr = dtfi.GetAbbreviatedDayName(i);
+ int matchStrLen = searchStr.Length;
+ if ( dtfi.HasSpacesInDayNames
+ ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
+ : str.MatchSpecifiedWord(searchStr)) {
+ if (matchStrLen > maxMatchStrLen) {
+ maxMatchStrLen = matchStrLen;
+ result = (int)i;
+ }
+ }
+ }
+ }
+ if (result >= 0) {
+ str.Index += maxMatchStrLen - 1;
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchDayName==================================
+ **Action: Parse the day of week name from string starting at str.Index.
+ **Returns: A value from 0 to 6 indicating Sunday to Saturday.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a day of week name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchDayName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) {
+ // Turkish (tr-TR) got day names with the same prefix.
+ int maxMatchStrLen = 0;
+ result = -1;
+ if (str.GetNext()) {
+ for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) {
+ String searchStr = dtfi.GetDayName(i);
+ int matchStrLen = searchStr.Length;
+ if ( dtfi.HasSpacesInDayNames
+ ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
+ : str.MatchSpecifiedWord(searchStr)) {
+ if (matchStrLen > maxMatchStrLen) {
+ maxMatchStrLen = matchStrLen;
+ result = (int)i;
+ }
+ }
+ }
+ }
+ if (result >= 0) {
+ str.Index += maxMatchStrLen - 1;
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchEraName==================================
+ **Action: Parse era name from string starting at str.Index.
+ **Returns: An era value.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if an era name can not be found.
+ ==============================================================================*/
+
+ private static bool MatchEraName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) {
+ if (str.GetNext()) {
+ int[] eras = dtfi.Calendar.Eras;
+
+ if (eras != null) {
+ for (int i = 0; i < eras.Length; i++) {
+ String searchStr = dtfi.GetEraName(eras[i]);
+ if (str.MatchSpecifiedWord(searchStr)) {
+ str.Index += (searchStr.Length - 1);
+ result = eras[i];
+ return (true);
+ }
+ searchStr = dtfi.GetAbbreviatedEraName(eras[i]);
+ if (str.MatchSpecifiedWord(searchStr)) {
+ str.Index += (searchStr.Length - 1);
+ result = eras[i];
+ return (true);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /*=================================MatchTimeMark==================================
+ **Action: Parse the time mark (AM/PM) from string starting at str.Index.
+ **Returns: TM_AM or TM_PM.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a time mark can not be found.
+ ==============================================================================*/
+
+ private static bool MatchTimeMark(ref __DTString str, DateTimeFormatInfo dtfi, ref TM result) {
+ result = TM.NotSet;
+ // In some cultures have empty strings in AM/PM mark. E.g. af-ZA (0x0436), the AM mark is "", and PM mark is "nm".
+ if (dtfi.AMDesignator.Length == 0) {
+ result = TM.AM;
+ }
+ if (dtfi.PMDesignator.Length == 0) {
+ result = TM.PM;
+ }
+
+ if (str.GetNext()) {
+ String searchStr = dtfi.AMDesignator;
+ if (searchStr.Length > 0) {
+ if (str.MatchSpecifiedWord(searchStr)) {
+ // Found an AM timemark with length > 0.
+ str.Index += (searchStr.Length - 1);
+ result = TM.AM;
+ return (true);
+ }
+ }
+ searchStr = dtfi.PMDesignator;
+ if (searchStr.Length > 0) {
+ if (str.MatchSpecifiedWord(searchStr)) {
+ // Found a PM timemark with length > 0.
+ str.Index += (searchStr.Length - 1);
+ result = TM.PM;
+ return (true);
+ }
+ }
+ str.Index--; // Undo the GetNext call.
+ }
+ if (result != TM.NotSet) {
+ // If one of the AM/PM marks is empty string, return the result.
+ return (true);
+ }
+ return false;
+ }
+
+ /*=================================MatchAbbreviatedTimeMark==================================
+ **Action: Parse the abbreviated time mark (AM/PM) from string starting at str.Index.
+ **Returns: TM_AM or TM_PM.
+ **Arguments: str: a __DTString. The parsing will start from the
+ ** next character after str.Index.
+ **Exceptions: FormatException if a abbreviated time mark can not be found.
+ ==============================================================================*/
+
+ private static bool MatchAbbreviatedTimeMark(ref __DTString str, DateTimeFormatInfo dtfi, ref TM result) {
+ // NOTENOTE : the assumption here is that abbreviated time mark is the first
+ // character of the AM/PM designator. If this invariant changes, we have to
+ // change the code below.
+ if (str.GetNext())
+ {
+ if (str.GetChar() == dtfi.AMDesignator[0]) {
+ result = TM.AM;
+ return (true);
+ }
+ if (str.GetChar() == dtfi.PMDesignator[0]) {
+ result = TM.PM;
+ return (true);
+ }
+ }
+ return false;
+ }
+
+ /*=================================CheckNewValue==================================
+ **Action: Check if currentValue is initialized. If not, return the newValue.
+ ** If yes, check if the current value is equal to newValue. Return false
+ ** if they are not equal. This is used to check the case like "d" and "dd" are both
+ ** used to format a string.
+ **Returns: the correct value for currentValue.
+ **Arguments:
+ **Exceptions:
+ ==============================================================================*/
+
+ private static bool CheckNewValue(ref int currentValue, int newValue, char patternChar, ref DateTimeResult result) {
+ if (currentValue == -1) {
+ currentValue = newValue;
+ return (true);
+ } else {
+ if (newValue != currentValue) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", patternChar);
+ return (false);
+ }
+ }
+ return (true);
+ }
+
+ private static DateTime GetDateTimeNow(ref DateTimeResult result, ref DateTimeStyles styles) {
+ if ((result.flags & ParseFlags.CaptureOffset) != 0) {
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0) {
+ // use the supplied offset to calculate 'Now'
+ return new DateTime(DateTime.UtcNow.Ticks + result.timeZoneOffset.Ticks, DateTimeKind.Unspecified);
+ }
+ else if ((styles & DateTimeStyles.AssumeUniversal) != 0) {
+ // assume the offset is Utc
+ return DateTime.UtcNow;
+ }
+ }
+
+ // assume the offset is Local
+ return DateTime.Now;
+ }
+
+ private static bool CheckDefaultDateTime(ref DateTimeResult result, ref Calendar cal, DateTimeStyles styles) {
+
+ if ((result.flags & ParseFlags.CaptureOffset) != 0) {
+ // DateTimeOffset.Parse should allow dates without a year, but only if there is also no time zone marker;
+ // e.g. "May 1 5pm" is OK, but "May 1 5pm -08:30" is not. This is somewhat pragmatic, since we would
+ // have to rearchitect parsing completely to allow this one case to correctly handle things like leap
+ // years and leap months. Is is an extremely corner case, and DateTime is basically incorrect in that
+ // case today.
+ //
+ // values like "11:00Z" or "11:00 -3:00" are also acceptable
+ //
+ // if ((month or day is set) and (year is not set and time zone is set))
+ //
+ if ( ((result.Month != -1) || (result.Day != -1))
+ && ((result.Year == -1 || ((result.flags & ParseFlags.YearDefault) != 0)) && (result.flags & ParseFlags.TimeZoneUsed) != 0) ) {
+ result.SetFailure(ParseFailureKind.Format, "Format_MissingIncompleteDate", null);
+ return false;
+ }
+ }
+
+
+ if ((result.Year == -1) || (result.Month == -1) || (result.Day == -1)) {
+ /*
+ The following table describes the behaviors of getting the default value
+ when a certain year/month/day values are missing.
+
+ An "X" means that the value exists. And "--" means that value is missing.
+
+ Year Month Day => ResultYear ResultMonth ResultDay Note
+
+ X X X Parsed year Parsed month Parsed day
+ X X -- Parsed Year Parsed month First day If we have year and month, assume the first day of that month.
+ X -- X Parsed year First month Parsed day If the month is missing, assume first month of that year.
+ X -- -- Parsed year First month First day If we have only the year, assume the first day of that year.
+
+ -- X X CurrentYear Parsed month Parsed day If the year is missing, assume the current year.
+ -- X -- CurrentYear Parsed month First day If we have only a month value, assume the current year and current day.
+ -- -- X CurrentYear First month Parsed day If we have only a day value, assume current year and first month.
+ -- -- -- CurrentYear Current month Current day So this means that if the date string only contains time, you will get current date.
+
+ */
+
+ DateTime now = GetDateTimeNow(ref result, ref styles);
+ if (result.Month == -1 && result.Day == -1) {
+ if (result.Year == -1) {
+ if ((styles & DateTimeStyles.NoCurrentDateDefault) != 0) {
+ // If there is no year/month/day values, and NoCurrentDateDefault flag is used,
+ // set the year/month/day value to the beginning year/month/day of DateTime().
+ // Note we should be using Gregorian for the year/month/day.
+ cal = GregorianCalendar.GetDefaultInstance();
+ result.Year = result.Month = result.Day = 1;
+ } else {
+ // Year/Month/Day are all missing.
+ result.Year = cal.GetYear(now);
+ result.Month = cal.GetMonth(now);
+ result.Day = cal.GetDayOfMonth(now);
+ }
+ } else {
+ // Month/Day are both missing.
+ result.Month = 1;
+ result.Day = 1;
+ }
+ } else {
+ if (result.Year == -1) {
+ result.Year = cal.GetYear(now);
+ }
+ if (result.Month == -1) {
+ result.Month = 1;
+ }
+ if (result.Day == -1) {
+ result.Day = 1;
+ }
+ }
+ }
+ // Set Hour/Minute/Second to zero if these value are not in str.
+ if (result.Hour == -1) result.Hour = 0;
+ if (result.Minute == -1) result.Minute = 0;
+ if (result.Second == -1) result.Second = 0;
+ if (result.era == -1) result.era = Calendar.CurrentEra;
+ return true;
+ }
+
+ // Expand a pre-defined format string (like "D" for long date) to the real format that
+ // we are going to use in the date time parsing.
+ // This method also set the dtfi according/parseInfo to some special pre-defined
+ // formats.
+ //
+ private static String ExpandPredefinedFormat(String format, ref DateTimeFormatInfo dtfi, ref ParsingInfo parseInfo, ref DateTimeResult result) {
+ //
+ // Check the format to see if we need to override the dtfi to be InvariantInfo,
+ // and see if we need to set up the userUniversalTime flag.
+ //
+ switch (format[0]) {
+ case 'o':
+ case 'O': // Round Trip Format
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ break;
+ case 'r':
+ case 'R': // RFC 1123 Standard. (in Universal time)
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+
+ if ((result.flags & ParseFlags.CaptureOffset) != 0) {
+ result.flags |= ParseFlags.Rfc1123Pattern;
+ }
+ break;
+ case 's': // Sortable format (in local time)
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ break;
+ case 'u': // Universal time format in sortable format.
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ dtfi = DateTimeFormatInfo.InvariantInfo;
+
+ if ((result.flags & ParseFlags.CaptureOffset) != 0) {
+ result.flags |= ParseFlags.UtcSortPattern;
+ }
+ break;
+ case 'U': // Universal time format with culture-dependent format.
+ parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = new TimeSpan(0);
+ result.flags |= ParseFlags.TimeZoneUtc;
+ if (dtfi.Calendar.GetType() != typeof(GregorianCalendar)) {
+ dtfi = (DateTimeFormatInfo)dtfi.Clone();
+ dtfi.Calendar = GregorianCalendar.GetDefaultInstance();
+ }
+ break;
+ }
+
+ //
+ // Expand the pre-defined format character to the real format from DateTimeFormatInfo.
+ //
+ return (DateTimeFormat.GetRealFormat(format, dtfi));
+ }
+
+
+
+
+
+ // Given a specified format character, parse and update the parsing result.
+ //
+ private static bool ParseByFormat(
+ ref __DTString str,
+ ref __DTString format,
+ ref ParsingInfo parseInfo,
+ DateTimeFormatInfo dtfi,
+ ref DateTimeResult result) {
+
+ int tokenLen = 0;
+ int tempYear = 0, tempMonth = 0, tempDay = 0, tempDayOfWeek = 0, tempHour = 0, tempMinute = 0, tempSecond = 0;
+ double tempFraction = 0;
+ TM tempTimeMark = 0;
+
+ char ch = format.GetChar();
+
+ switch (ch) {
+ case 'y':
+ tokenLen = format.GetRepeatCount();
+ bool parseResult;
+ if (dtfi.HasForceTwoDigitYears) {
+ parseResult = ParseDigits(ref str, 1, 4, out tempYear);
+ }
+ else {
+ if (tokenLen <= 2) {
+ parseInfo.fUseTwoDigitYear = true;
+ }
+ parseResult = ParseDigits(ref str, tokenLen, out tempYear);
+ }
+ if (!parseResult && parseInfo.fCustomNumberParser) {
+ parseResult = parseInfo.parseNumberDelegate(ref str, tokenLen, out tempYear);
+ }
+ if (!parseResult) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Year, tempYear, ch, ref result)) {
+ return (false);
+ }
+ break;
+ case 'M':
+ tokenLen = format.GetRepeatCount();
+ if (tokenLen <= 2) {
+ if (!ParseDigits(ref str, tokenLen, out tempMonth)) {
+ if (!parseInfo.fCustomNumberParser ||
+ !parseInfo.parseNumberDelegate(ref str, tokenLen, out tempMonth)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ }
+ } else {
+ if (tokenLen == 3) {
+ if (!MatchAbbreviatedMonthName(ref str, dtfi, ref tempMonth)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ } else {
+ if (!MatchMonthName(ref str, dtfi, ref tempMonth)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ }
+ result.flags |= ParseFlags.ParsedMonthName;
+ }
+ if (!CheckNewValue(ref result.Month, tempMonth, ch, ref result)) {
+ return (false);
+ }
+ break;
+ case 'd':
+ // Day & Day of week
+ tokenLen = format.GetRepeatCount();
+ if (tokenLen <= 2) {
+ // "d" & "dd"
+
+ if (!ParseDigits(ref str, tokenLen, out tempDay)) {
+ if (!parseInfo.fCustomNumberParser ||
+ !parseInfo.parseNumberDelegate(ref str, tokenLen, out tempDay)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ }
+ if (!CheckNewValue(ref result.Day, tempDay, ch, ref result)) {
+ return (false);
+ }
+ } else {
+ if (tokenLen == 3) {
+ // "ddd"
+ if (!MatchAbbreviatedDayName(ref str, dtfi, ref tempDayOfWeek)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ } else {
+ // "dddd*"
+ if (!MatchDayName(ref str, dtfi, ref tempDayOfWeek)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ }
+ if (!CheckNewValue(ref parseInfo.dayOfWeek, tempDayOfWeek, ch, ref result)) {
+ return (false);
+ }
+ }
+ break;
+ case 'g':
+ tokenLen = format.GetRepeatCount();
+ // Put the era value in result.era.
+ if (!MatchEraName(ref str, dtfi, ref result.era)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ break;
+ case 'h':
+ parseInfo.fUseHour12 = true;
+ tokenLen = format.GetRepeatCount();
+ if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempHour)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result)) {
+ return (false);
+ }
+ break;
+ case 'H':
+ tokenLen = format.GetRepeatCount();
+ if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempHour)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result)) {
+ return (false);
+ }
+ break;
+ case 'm':
+ tokenLen = format.GetRepeatCount();
+ if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempMinute)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Minute, tempMinute, ch, ref result)) {
+ return (false);
+ }
+ break;
+ case 's':
+ tokenLen = format.GetRepeatCount();
+ if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempSecond)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ if (!CheckNewValue(ref result.Second, tempSecond, ch, ref result)) {
+ return (false);
+ }
+ break;
+ case 'f':
+ case 'F':
+ tokenLen = format.GetRepeatCount();
+ if (tokenLen <= DateTimeFormat.MaxSecondsFractionDigits) {
+ if (!ParseFractionExact(ref str, tokenLen, ref tempFraction)) {
+ if (ch == 'f') {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ }
+ if (result.fraction < 0) {
+ result.fraction = tempFraction;
+ } else {
+ if (tempFraction != result.fraction) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", ch);
+ return (false);
+ }
+ }
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ break;
+ case 't':
+ // AM/PM designator
+ tokenLen = format.GetRepeatCount();
+ if (tokenLen == 1) {
+ if (!MatchAbbreviatedTimeMark(ref str, dtfi, ref tempTimeMark)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ } else {
+ if (!MatchTimeMark(ref str, dtfi, ref tempTimeMark)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ }
+
+ if (parseInfo.timeMark == TM.NotSet) {
+ parseInfo.timeMark = tempTimeMark;
+ }
+ else {
+ if (parseInfo.timeMark != tempTimeMark) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", ch);
+ return (false);
+ }
+ }
+ break;
+ case 'z':
+ // timezone offset
+ tokenLen = format.GetRepeatCount();
+ {
+ TimeSpan tempTimeZoneOffset = new TimeSpan(0);
+ if (!ParseTimeZoneOffset(ref str, tokenLen, ref tempTimeZoneOffset)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'z');
+ return (false);
+ }
+ result.timeZoneOffset = tempTimeZoneOffset;
+ result.flags |= ParseFlags.TimeZoneUsed;
+ }
+ break;
+ case 'Z':
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'Z');
+ return (false);
+ }
+
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = new TimeSpan(0);
+ result.flags |= ParseFlags.TimeZoneUtc;
+
+ // The updating of the indexes is to reflect that ParseExact MatchXXX methods assume that
+ // they need to increment the index and Parse GetXXX do not. Since we are calling a Parse
+ // method from inside ParseExact we need to adjust this. Long term, we should try to
+ // eliminate this discrepancy.
+ str.Index++;
+ if (!GetTimeZoneName(ref str)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ str.Index--;
+ break;
+ case 'K':
+ // This should parse either as a blank, the 'Z' character or a local offset like "-07:00"
+ if (str.Match('Z')) {
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'K');
+ return (false);
+ }
+
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = new TimeSpan(0);
+ result.flags |= ParseFlags.TimeZoneUtc;
+ }
+ else if (str.Match('+') || str.Match('-')) {
+ str.Index--; // Put the character back for the parser
+ TimeSpan tempTimeZoneOffset = new TimeSpan(0);
+ if (!ParseTimeZoneOffset(ref str, 3, ref tempTimeZoneOffset)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return (false);
+ }
+ if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'K');
+ return (false);
+ }
+ result.timeZoneOffset = tempTimeZoneOffset;
+ result.flags |= ParseFlags.TimeZoneUsed;
+ }
+ // Otherwise it is unspecified and we consume no characters
+ break;
+ case ':':
+ // We match the separator in time pattern with the character in the time string if both equal to ':' or the date separator is matching the characters in the date string
+ // We have to exclude the case when the time separator is more than one character and starts with ':' something like "::" for instance.
+ if (((dtfi.TimeSeparator.Length > 1 && dtfi.TimeSeparator[0] == ':') || !str.Match(':')) &&
+ !str.Match(dtfi.TimeSeparator)) {
+ // A time separator is expected.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ break;
+ case '/':
+ // We match the separator in date pattern with the character in the date string if both equal to '/' or the date separator is matching the characters in the date string
+ // We have to exclude the case when the date separator is more than one character and starts with '/' something like "//" for instance.
+ if (((dtfi.DateSeparator.Length > 1 && dtfi.DateSeparator[0] == '/') || !str.Match('/')) &&
+ !str.Match(dtfi.DateSeparator))
+ {
+ // A date separator is expected.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ break;
+ case '\"':
+ case '\'':
+ StringBuilder enquotedString = new StringBuilder();
+ // Use ParseQuoteString so that we can handle escape characters within the quoted string.
+ if (!TryParseQuoteString(format.Value, format.Index, enquotedString, out tokenLen)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadQuote", ch);
+ return (false);
+ }
+ format.Index += tokenLen - 1;
+
+ // Some cultures uses space in the quoted string. E.g. Spanish has long date format as:
+ // "dddd, dd' de 'MMMM' de 'yyyy". When inner spaces flag is set, we should skip whitespaces if there is space
+ // in the quoted string.
+ String quotedStr = enquotedString.ToString();
+
+ for (int i = 0; i < quotedStr.Length; i++) {
+ if (quotedStr[i] == ' ' && parseInfo.fAllowInnerWhite) {
+ str.SkipWhiteSpaces();
+ } else if (!str.Match(quotedStr[i])) {
+ // Can not find the matching quoted string.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ }
+
+ // The "r" and "u" formats incorrectly quoted 'GMT' and 'Z', respectively. We cannot
+ // correct this mistake for DateTime.ParseExact for compatibility reasons, but we can
+ // fix it for DateTimeOffset.ParseExact as DateTimeOffset has not been publically released
+ // with this issue.
+ if ((result.flags & ParseFlags.CaptureOffset) != 0) {
+ if ((result.flags & ParseFlags.Rfc1123Pattern) != 0 && quotedStr == GMTName) {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+ else if ((result.flags & ParseFlags.UtcSortPattern) != 0 && quotedStr == ZuluName) {
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ }
+ }
+
+ break;
+ case '%':
+ // Skip this so we can get to the next pattern character.
+ // Used in case like "%d", "%y"
+
+ // Make sure the next character is not a '%' again.
+ if (format.Index >= format.Value.Length - 1 || format.Value[format.Index + 1] == '%') {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
+ return false;
+ }
+ break;
+ case '\\':
+ // Escape character. For example, "\d".
+ // Get the next character in format, and see if we can
+ // find a match in str.
+ if (format.GetNext()) {
+ if (!str.Match(format.GetChar())) {
+ // Can not find a match for the escaped character.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ } else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
+ return false;
+ }
+ break;
+ case '.':
+ if (!str.Match(ch)) {
+ if (format.GetNext()) {
+ // If we encounter the pattern ".F", and the dot is not present, it is an optional
+ // second fraction and we can skip this format.
+ if (format.Match('F')) {
+ format.GetRepeatCount();
+ break;
+ }
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ break;
+ default:
+ if (ch == ' ') {
+ if (parseInfo.fAllowInnerWhite) {
+ // Skip whitespaces if AllowInnerWhite.
+ // Do nothing here.
+ } else {
+ if (!str.Match(ch)) {
+ // If the space does not match, and trailing space is allowed, we do
+ // one more step to see if the next format character can lead to
+ // successful parsing.
+ // This is used to deal with special case that a empty string can match
+ // a specific pattern.
+ // The example here is af-ZA, which has a time format like "hh:mm:ss tt". However,
+ // its AM symbol is "" (empty string). If fAllowTrailingWhite is used, and time is in
+ // the AM, we will trim the whitespaces at the end, which will lead to a failure
+ // when we are trying to match the space before "tt".
+ if (parseInfo.fAllowTrailingWhite) {
+ if (format.GetNext()) {
+ if (ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result)) {
+ return (true);
+ }
+ }
+ }
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ // Found a macth.
+ }
+ } else {
+ if (format.MatchSpecifiedWord(GMTName)) {
+ format.Index += (GMTName.Length - 1);
+ // Found GMT string in format. This means the DateTime string
+ // is in GMT timezone.
+ result.flags |= ParseFlags.TimeZoneUsed;
+ result.timeZoneOffset = TimeSpan.Zero;
+ if (!str.Match(GMTName)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ } else if (!str.Match(ch)) {
+ // ch is expected.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ }
+ break;
+ } // switch
+ return (true);
+ }
+
+ //
+ // The pos should point to a quote character. This method will
+ // get the string enclosed by the quote character.
+ //
+ internal static bool TryParseQuoteString(String format, int pos, StringBuilder result, out int returnValue) {
+ //
+ // NOTE : pos will be the index of the quote character in the 'format' string.
+ //
+ returnValue = 0;
+ int formatLen = format.Length;
+ int beginPos = pos;
+ char quoteChar = format[pos++]; // Get the character used to quote the following string.
+
+ bool foundQuote = false;
+ while (pos < formatLen) {
+ char ch = format[pos++];
+ if (ch == quoteChar) {
+ foundQuote = true;
+ break;
+ }
+ else if (ch == '\\') {
+ // The following are used to support escaped character.
+ // Escaped character is also supported in the quoted string.
+ // Therefore, someone can use a format like "'minute:' mm\"" to display:
+ // minute: 45"
+ // because the second double quote is escaped.
+ if (pos < formatLen) {
+ result.Append(format[pos++]);
+ } else {
+ //
+ // This means that '\' is at the end of the formatting string.
+ //
+ return false;
+ }
+ } else {
+ result.Append(ch);
+ }
+ }
+
+ if (!foundQuote) {
+ // Here we can't find the matching quote.
+ return false;
+ }
+
+ //
+ // Return the character count including the begin/end quote characters and enclosed string.
+ //
+ returnValue = (pos - beginPos);
+ return true;
+ }
+
+
+
+
+ /*=================================DoStrictParse==================================
+ **Action: Do DateTime parsing using the format in formatParam.
+ **Returns: The parsed DateTime.
+ **Arguments:
+ **Exceptions:
+ **
+ **Notes:
+ ** When the following general formats are used, InvariantInfo is used in dtfi:
+ ** 'r', 'R', 's'.
+ ** When the following general formats are used, the time is assumed to be in Universal time.
+ **
+ **Limitations:
+ ** Only GregarianCalendar is supported for now.
+ ** Only support GMT timezone.
+ ==============================================================================*/
+
+ private static bool DoStrictParse(
+ String s,
+ String formatParam,
+ DateTimeStyles styles,
+ DateTimeFormatInfo dtfi,
+ ref DateTimeResult result) {
+
+
+
+ ParsingInfo parseInfo = new ParsingInfo();
+ parseInfo.Init();
+
+ parseInfo.calendar = dtfi.Calendar;
+ parseInfo.fAllowInnerWhite = ((styles & DateTimeStyles.AllowInnerWhite) != 0);
+ parseInfo.fAllowTrailingWhite = ((styles & DateTimeStyles.AllowTrailingWhite) != 0);
+
+ // We need the original values of the following two below.
+ String originalFormat = formatParam;
+
+ if (formatParam.Length == 1) {
+ if (((result.flags & ParseFlags.CaptureOffset) != 0) && formatParam[0] == 'U') {
+ // The 'U' format is not allowed for DateTimeOffset
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
+ return false;
+ }
+ formatParam = ExpandPredefinedFormat(formatParam, ref dtfi, ref parseInfo, ref result);
+ }
+
+ bool bTimeOnly = false;
+ result.calendar = parseInfo.calendar;
+
+ if (parseInfo.calendar.ID == Calendar.CAL_HEBREW) {
+ parseInfo.parseNumberDelegate = m_hebrewNumberParser;
+ parseInfo.fCustomNumberParser = true;
+ }
+
+ // Reset these values to negative one so that we could throw exception
+ // if we have parsed every item twice.
+ result.Hour = result.Minute = result.Second = -1;
+
+ __DTString format = new __DTString(formatParam, dtfi, false);
+ __DTString str = new __DTString(s, dtfi, false);
+
+ if (parseInfo.fAllowTrailingWhite) {
+ // Trim trailing spaces if AllowTrailingWhite.
+ format.TrimTail();
+ format.RemoveTrailingInQuoteSpaces();
+ str.TrimTail();
+ }
+
+ if ((styles & DateTimeStyles.AllowLeadingWhite) != 0) {
+ format.SkipWhiteSpaces();
+ format.RemoveLeadingInQuoteSpaces();
+ str.SkipWhiteSpaces();
+ }
+
+ //
+ // Scan every character in format and match the pattern in str.
+ //
+ while (format.GetNext()) {
+ // We trim inner spaces here, so that we will not eat trailing spaces when
+ // AllowTrailingWhite is not used.
+ if (parseInfo.fAllowInnerWhite) {
+ str.SkipWhiteSpaces();
+ }
+ if (!ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result)) {
+ return (false);
+ }
+ }
+
+ if (str.Index < str.Value.Length - 1) {
+ // There are still remaining character in str.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ if (parseInfo.fUseTwoDigitYear && ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) == 0)) {
+ // A two digit year value is expected. Check if the parsed year value is valid.
+ if (result.Year >= 100) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ try {
+ result.Year = parseInfo.calendar.ToFourDigitYear(result.Year);
+ }
+ catch (ArgumentOutOfRangeException e) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", e);
+ return false;
+ }
+ }
+
+ if (parseInfo.fUseHour12) {
+ if (parseInfo.timeMark == TM.NotSet) {
+ // hh is used, but no AM/PM designator is specified.
+ // Assume the time is AM.
+ // Don't throw exceptions in here becasue it is very confusing for the caller.
+ // I always got confused myself when I use "hh:mm:ss" to parse a time string,
+ // and ParseExact() throws on me (because I didn't use the 24-hour clock 'HH').
+ parseInfo.timeMark = TM.AM;
+ }
+ if (result.Hour > 12) {
+ // AM/PM is used, but the value for HH is too big.
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ if (parseInfo.timeMark == TM.AM) {
+ if (result.Hour == 12) {
+ result.Hour = 0;
+ }
+ } else {
+ result.Hour = (result.Hour == 12) ? 12 : result.Hour + 12;
+ }
+ }
+ else
+ {
+ // Military (24-hour time) mode
+ //
+ // AM cannot be set with a 24-hour time like 17:15.
+ // PM cannot be set with a 24-hour time like 03:15.
+ if ( (parseInfo.timeMark == TM.AM && result.Hour >= 12)
+ ||(parseInfo.timeMark == TM.PM && result.Hour < 12)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+ }
+
+
+ // Check if the parased string only contains hour/minute/second values.
+ bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);
+ if (!CheckDefaultDateTime(ref result, ref parseInfo.calendar, styles)) {
+ return false;
+ }
+
+ if (!bTimeOnly && dtfi.HasYearMonthAdjustment) {
+ if (!dtfi.YearMonthAdjustment(ref result.Year, ref result.Month, ((result.flags & ParseFlags.ParsedMonthName) != 0))) {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null);
+ return false;
+ }
+ }
+ if (!parseInfo.calendar.TryToDateTime(result.Year, result.Month, result.Day,
+ result.Hour, result.Minute, result.Second, 0, result.era, out result.parsedDate)) {
+ result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, "Format_BadDateTimeCalendar", null);
+ return false;
+ }
+ if (result.fraction > 0) {
+ result.parsedDate = result.parsedDate.AddTicks((long)Math.Round(result.fraction * Calendar.TicksPerSecond));
+ }
+
+ //
+ // We have to check day of week before we adjust to the time zone.
+ // It is because the value of day of week may change after adjusting
+ // to the time zone.
+ //
+ if (parseInfo.dayOfWeek != -1) {
+ //
+ // Check if day of week is correct.
+ //
+ if (parseInfo.dayOfWeek != (int)parseInfo.calendar.GetDayOfWeek(result.parsedDate)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDayOfWeek", null);
+ return false;
+ }
+ }
+
+
+ if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly)) {
+ return false;
+ }
+ return true;
+ }
+
+ private static Exception GetDateTimeParseException(ref DateTimeResult result) {
+ switch (result.failure) {
+ case ParseFailureKind.ArgumentNull:
+ return new ArgumentNullException(result.failureArgumentName, Environment.GetResourceString(result.failureMessageID));
+ case ParseFailureKind.Format:
+ return new FormatException(Environment.GetResourceString(result.failureMessageID));
+ case ParseFailureKind.FormatWithParameter:
+ return new FormatException(Environment.GetResourceString(result.failureMessageID, result.failureMessageFormatArgument));
+ case ParseFailureKind.FormatBadDateTimeCalendar:
+ return new FormatException(Environment.GetResourceString(result.failureMessageID, result.calendar));
+ default:
+ Contract.Assert(false, "Unkown DateTimeParseFailure: " + result);
+ return null;
+
+ }
+ }
+
+ // Builds with _LOGGING defined (x86dbg, amd64chk, etc) support tracing
+ // Set the following internal-only/unsupported environment variables to enable DateTime tracing to the console:
+ //
+ // COMPlus_LogEnable=1
+ // COMPlus_LogToConsole=1
+ // COMPlus_LogLevel=9
+ // COMPlus_ManagedLogFacility=0x00001000
+ [Pure]
+ [Conditional("_LOGGING")]
+ internal static void LexTraceExit(string message, DS dps) {
+#if _LOGGING
+ if (!_tracingEnabled)
+ return;
+ BCLDebug.Trace("DATETIME", "[DATETIME] Lex return {0}, DS.{1}", message, dps);
+#endif // _LOGGING
+ }
+ [Pure]
+ [Conditional("_LOGGING")]
+ internal static void PTSTraceExit(DS dps, bool passed) {
+#if _LOGGING
+ if (!_tracingEnabled)
+ return;
+ BCLDebug.Trace("DATETIME", "[DATETIME] ProcessTerminalState {0} @ DS.{1}", passed ? "passed" : "failed", dps);
+#endif // _LOGGING
+ }
+ [Pure]
+ [Conditional("_LOGGING")]
+ internal static void TPTraceExit(string message, DS dps) {
+#if _LOGGING
+ if (!_tracingEnabled)
+ return;
+ BCLDebug.Trace("DATETIME", "[DATETIME] TryParse return {0}, DS.{1}", message, dps);
+#endif // _LOGGING
+ }
+ [Pure]
+ [Conditional("_LOGGING")]
+ internal static void DTFITrace(DateTimeFormatInfo dtfi) {
+#if _LOGGING
+ if (!_tracingEnabled)
+ return;
+
+ BCLDebug.Trace("DATETIME", "[DATETIME] DateTimeFormatInfo Properties");
+#if !FEATURE_COREFX_GLOBALIZATION
+ BCLDebug.Trace("DATETIME", " NativeCalendarName {0}", Hex(dtfi.NativeCalendarName));
+#endif
+ BCLDebug.Trace("DATETIME", " AMDesignator {0}", Hex(dtfi.AMDesignator));
+ BCLDebug.Trace("DATETIME", " PMDesignator {0}", Hex(dtfi.PMDesignator));
+ BCLDebug.Trace("DATETIME", " TimeSeparator {0}", Hex(dtfi.TimeSeparator));
+ BCLDebug.Trace("DATETIME", " AbbrvDayNames {0}", Hex(dtfi.AbbreviatedDayNames));
+ BCLDebug.Trace("DATETIME", " ShortestDayNames {0}", Hex(dtfi.ShortestDayNames));
+ BCLDebug.Trace("DATETIME", " DayNames {0}", Hex(dtfi.DayNames));
+ BCLDebug.Trace("DATETIME", " AbbrvMonthNames {0}", Hex(dtfi.AbbreviatedMonthNames));
+ BCLDebug.Trace("DATETIME", " MonthNames {0}", Hex(dtfi.MonthNames));
+ BCLDebug.Trace("DATETIME", " AbbrvMonthGenNames {0}", Hex(dtfi.AbbreviatedMonthGenitiveNames));
+ BCLDebug.Trace("DATETIME", " MonthGenNames {0}", Hex(dtfi.MonthGenitiveNames));
+#endif // _LOGGING
+ }
+#if _LOGGING
+ [Pure]
+ // return a string in the form: "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ internal static string Hex(string[] strs) {
+ if (strs == null || strs.Length == 0)
+ return String.Empty;
+ if (strs.Length == 1)
+ return Hex(strs[0]);
+
+ int curLineLength = 0;
+ int maxLineLength = 55;
+ int newLinePadding = 20;
+
+
+ //invariant: strs.Length >= 2
+ StringBuilder buffer = new StringBuilder();
+ buffer.Append(Hex(strs[0]));
+ curLineLength = buffer.Length;
+ String s;
+
+ for (int i = 1; i < strs.Length-1; i++) {
+ s = Hex(strs[i]);
+
+ if (s.Length > maxLineLength || (curLineLength + s.Length + 2) > maxLineLength) {
+ buffer.Append(',');
+ buffer.Append(Environment.NewLine);
+ buffer.Append(' ', newLinePadding);
+ curLineLength = 0;
+ }
+ else {
+ buffer.Append(", ");
+ curLineLength += 2;
+ }
+ buffer.Append(s);
+ curLineLength += s.Length;
+ }
+
+ buffer.Append(',');
+ s = Hex(strs[strs.Length-1]);
+ if (s.Length > maxLineLength || (curLineLength + s.Length + 6) > maxLineLength) {
+ buffer.Append(Environment.NewLine);
+ buffer.Append(' ', newLinePadding);
+ }
+ else {
+ buffer.Append(' ');
+ }
+ buffer.Append(s);
+ return buffer.ToString();
+ }
+ [Pure]
+ // return a string in the form: "Sun"
+ internal static string Hex(string str) {
+ StringBuilder buffer = new StringBuilder();
+ buffer.Append("\"");
+ for (int i = 0; i < str.Length; i++) {
+ if (str[i] <= '\x007f')
+ buffer.Append(str[i]);
+ else
+ buffer.Append("\\u" + ((int)str[i]).ToString("x4", CultureInfo.InvariantCulture));
+ }
+ buffer.Append("\"");
+ return buffer.ToString();
+ }
+ [Pure]
+ // return an unicode escaped string form of char c
+ internal static String Hex(char c) {
+ if (c <= '\x007f')
+ return c.ToString(CultureInfo.InvariantCulture);
+ else
+ return "\\u" + ((int)c).ToString("x4", CultureInfo.InvariantCulture);
+ }
+
+ internal static bool _tracingEnabled = BCLDebug.CheckEnabled("DATETIME");
+#endif // _LOGGING
+ }
+
+
+ //
+ // This is a string parsing helper which wraps a String object.
+ // It has a Index property which tracks
+ // the current parsing pointer of the string.
+ //
+ internal
+ struct __DTString
+ {
+
+ //
+ // Value propery: stores the real string to be parsed.
+ //
+ internal String Value;
+
+ //
+ // Index property: points to the character that we are currently parsing.
+ //
+ internal int Index;
+
+ // The length of Value string.
+ internal int len;
+
+ // The current chracter to be looked at.
+ internal char m_current;
+
+ private CompareInfo m_info;
+ // Flag to indicate if we encouter an digit, we should check for token or not.
+ // In some cultures, such as mn-MN, it uses "\x0031\x00a0\x0434\x04af\x0433\x044d\x044d\x0440\x00a0\x0441\x0430\x0440" in month names.
+ private bool m_checkDigitToken;
+
+ internal __DTString(String str, DateTimeFormatInfo dtfi, bool checkDigitToken) : this (str, dtfi)
+ {
+ m_checkDigitToken = checkDigitToken;
+ }
+
+ internal __DTString(String str, DateTimeFormatInfo dtfi)
+ {
+ Index = -1;
+ Value = str;
+ len = Value.Length;
+
+ m_current = '\0';
+ if (dtfi != null)
+ {
+ m_info = dtfi.CompareInfo;
+ m_checkDigitToken = ((dtfi.FormatFlags & DateTimeFormatFlags.UseDigitPrefixInTokens) != 0);
+ } else
+ {
+ m_info = Thread.CurrentThread.CurrentCulture.CompareInfo;
+ m_checkDigitToken = false;
+ }
+ }
+
+ internal CompareInfo CompareInfo
+ {
+ get { return m_info; }
+ }
+
+ //
+ // Advance the Index.
+ // Return true if Index is NOT at the end of the string.
+ //
+ // Typical usage:
+ // while (str.GetNext())
+ // {
+ // char ch = str.GetChar()
+ // }
+ internal bool GetNext() {
+ Index++;
+ if (Index < len) {
+ m_current = Value[Index];
+ return (true);
+ }
+ return (false);
+ }
+
+ internal bool AtEnd()
+ {
+ return Index < len ? false : true;
+ }
+
+ internal bool Advance(int count) {
+ Contract.Assert(Index + count <= len, "__DTString::Advance: Index + count <= len");
+ Index += count;
+ if (Index < len) {
+ m_current = Value[Index];
+ return (true);
+ }
+ return (false);
+ }
+
+
+ // Used by DateTime.Parse() to get the next token.
+ [System.Security.SecurityCritical] // auto-generated
+ internal void GetRegularToken(out TokenType tokenType, out int tokenValue, DateTimeFormatInfo dtfi) {
+ tokenValue = 0;
+ if (Index >= len) {
+ tokenType = TokenType.EndOfString;
+ return;
+ }
+
+ tokenType = TokenType.UnknownToken;
+
+Start:
+ if (DateTimeParse.IsDigit(m_current)) {
+ // This is a digit.
+ tokenValue = m_current - '0';
+ int value;
+ int start = Index;
+
+ //
+ // Collect other digits.
+ //
+ while (++Index < len)
+ {
+ m_current = Value[Index];
+ value = m_current - '0';
+ if (value >= 0 && value <= 9) {
+ tokenValue = tokenValue * 10 + value;
+ } else {
+ break;
+ }
+ }
+ if (Index - start > DateTimeParse.MaxDateTimeNumberDigits) {
+ tokenType = TokenType.NumberToken;
+ tokenValue = -1;
+ } else if (Index - start < 3) {
+ tokenType = TokenType.NumberToken;
+ } else {
+ // If there are more than 3 digits, assume that it's a year value.
+ tokenType = TokenType.YearNumberToken;
+ }
+ if (m_checkDigitToken)
+ {
+ int save = Index;
+ char saveCh = m_current;
+ // Re-scan using the staring Index to see if this is a token.
+ Index = start; // To include the first digit.
+ m_current = Value[Index];
+ TokenType tempType;
+ int tempValue;
+ // This DTFI has tokens starting with digits.
+ // E.g. mn-MN has month name like "\x0031\x00a0\x0434\x04af\x0433\x044d\x044d\x0440\x00a0\x0441\x0430\x0440"
+ if (dtfi.Tokenize(TokenType.RegularTokenMask, out tempType, out tempValue, ref this))
+ {
+ tokenType = tempType;
+ tokenValue = tempValue;
+ // This is a token, so the Index has been advanced propertly in DTFI.Tokenizer().
+ } else
+ {
+ // Use the number token value.
+ // Restore the index.
+ Index = save;
+ m_current = saveCh;
+ }
+
+ }
+
+ } else if (Char.IsWhiteSpace( m_current)) {
+ // Just skip to the next character.
+ while (++Index < len) {
+ m_current = Value[Index];
+ if (!(Char.IsWhiteSpace(m_current))) {
+ goto Start;
+ }
+ }
+ // We have reached the end of string.
+ tokenType = TokenType.EndOfString;
+ } else {
+ dtfi.Tokenize(TokenType.RegularTokenMask, out tokenType, out tokenValue, ref this);
+ }
+ }
+
+ [System.Security.SecurityCritical] // auto-generated
+ internal TokenType GetSeparatorToken(DateTimeFormatInfo dtfi, out int indexBeforeSeparator, out char charBeforeSeparator) {
+ indexBeforeSeparator = Index;
+ charBeforeSeparator = m_current;
+ TokenType tokenType;
+ if (!SkipWhiteSpaceCurrent()) {
+ // Reach the end of the string.
+ return (TokenType.SEP_End);
+ }
+ if (!DateTimeParse.IsDigit(m_current)) {
+ // Not a digit. Tokenize it.
+ int tokenValue;
+ bool found = dtfi.Tokenize(TokenType.SeparatorTokenMask, out tokenType, out tokenValue, ref this);
+ if (!found) {
+ tokenType = TokenType.SEP_Space;
+ }
+ } else {
+ // Do nothing here. If we see a number, it will not be a separator. There is no need wasting time trying to find the
+ // separator token.
+ tokenType = TokenType.SEP_Space;
+ }
+ return (tokenType);
+ }
+
+ internal bool MatchSpecifiedWord(String target) {
+ return MatchSpecifiedWord(target, target.Length + Index);
+ }
+
+ internal bool MatchSpecifiedWord(String target, int endIndex) {
+ int count = endIndex - Index;
+
+ if (count != target.Length) {
+ return false;
+ }
+
+ if (Index + count > len) {
+ return false;
+ }
+
+ return (m_info.Compare(Value, Index, count, target, 0, count, CompareOptions.IgnoreCase)==0);
+ }
+
+ private static Char[] WhiteSpaceChecks = new Char[] { ' ', '\u00A0' };
+
+ internal bool MatchSpecifiedWords(String target, bool checkWordBoundary, ref int matchLength) {
+ int valueRemaining = Value.Length - Index;
+ matchLength = target.Length;
+
+ if (matchLength > valueRemaining || m_info.Compare(Value, Index, matchLength, target, 0, matchLength, CompareOptions.IgnoreCase) !=0) {
+ // Check word by word
+ int targetPosition = 0; // Where we are in the target string
+ int thisPosition = Index; // Where we are in this string
+ int wsIndex = target.IndexOfAny(WhiteSpaceChecks, targetPosition);
+ if (wsIndex == -1) {
+ return false;
+ }
+ do {
+ int segmentLength = wsIndex - targetPosition;
+ if (thisPosition >= Value.Length - segmentLength) { // Subtraction to prevent overflow.
+ return false;
+ }
+ if (segmentLength == 0) {
+ // If segmentLength == 0, it means that we have leading space in the target string.
+ // In that case, skip the leading spaces in the target and this string.
+ matchLength--;
+ } else {
+ // Make sure we also have whitespace in the input string
+ if (!Char.IsWhiteSpace(Value[thisPosition + segmentLength])) {
+ return false;
+ }
+ if (m_info.Compare(Value, thisPosition, segmentLength, target, targetPosition, segmentLength, CompareOptions.IgnoreCase) !=0) {
+ return false;
+ }
+ // Advance the input string
+ thisPosition = thisPosition + segmentLength + 1;
+ }
+ // Advance our target string
+ targetPosition = wsIndex + 1;
+
+
+ // Skip past multiple whitespace
+ while (thisPosition < Value.Length && Char.IsWhiteSpace(Value[thisPosition])) {
+ thisPosition++;
+ matchLength++;
+ }
+ } while ((wsIndex = target.IndexOfAny(WhiteSpaceChecks, targetPosition)) >= 0);
+ // now check the last segment;
+ if (targetPosition < target.Length) {
+ int segmentLength = target.Length - targetPosition;
+ if (thisPosition > Value.Length - segmentLength) {
+ return false;
+ }
+ if (m_info.Compare(Value, thisPosition, segmentLength, target, targetPosition, segmentLength, CompareOptions.IgnoreCase) !=0) {
+ return false;
+ }
+ }
+ }
+
+ if (checkWordBoundary) {
+ int nextCharIndex = Index + matchLength;
+ if (nextCharIndex < Value.Length) {
+ if (Char.IsLetter(Value[nextCharIndex])) {
+ return (false);
+ }
+ }
+ }
+ return (true);
+ }
+
+ //
+ // Check to see if the string starting from Index is a prefix of
+ // str.
+ // If a match is found, true value is returned and Index is updated to the next character to be parsed.
+ // Otherwise, Index is unchanged.
+ //
+ internal bool Match(String str) {
+ if (++Index >= len) {
+ return (false);
+ }
+
+ if (str.Length > (Value.Length - Index)) {
+ return false;
+ }
+
+ if (m_info.Compare(Value, Index, str.Length, str, 0, str.Length, CompareOptions.Ordinal)==0) {
+ // Update the Index to the end of the matching string.
+ // So the following GetNext()/Match() opeartion will get
+ // the next character to be parsed.
+ Index += (str.Length - 1);
+ return (true);
+ }
+ return (false);
+ }
+
+ internal bool Match(char ch) {
+ if (++Index >= len) {
+ return (false);
+ }
+ if (Value[Index] == ch) {
+ m_current = ch;
+ return (true);
+ }
+ Index--;
+ return (false);
+ }
+
+ //
+ // Actions: From the current position, try matching the longest word in the specified string array.
+ // E.g. words[] = {"AB", "ABC", "ABCD"}, if the current position points to a substring like "ABC DEF",
+ // MatchLongestWords(words, ref MaxMatchStrLen) will return 1 (the index), and maxMatchLen will be 3.
+ // Returns:
+ // The index that contains the longest word to match
+ // Arguments:
+ // words The string array that contains words to search.
+ // maxMatchStrLen [in/out] the initailized maximum length. This parameter can be used to
+ // find the longest match in two string arrays.
+ //
+ internal int MatchLongestWords(String[] words, ref int maxMatchStrLen) {
+ int result = -1;
+ for (int i = 0; i < words.Length; i++) {
+ String word = words[i];
+ int matchLength = word.Length;
+ if (MatchSpecifiedWords(word, false, ref matchLength)) {
+ if (matchLength > maxMatchStrLen) {
+ maxMatchStrLen = matchLength;
+ result = i;
+ }
+ }
+ }
+
+ return (result);
+ }
+
+ //
+ // Get the number of repeat character after the current character.
+ // For a string "hh:mm:ss" at Index of 3. GetRepeatCount() = 2, and Index
+ // will point to the second ':'.
+ //
+ internal int GetRepeatCount() {
+ char repeatChar = Value[Index];
+ int pos = Index + 1;
+ while ((pos < len) && (Value[pos] == repeatChar)) {
+ pos++;
+ }
+ int repeatCount = (pos - Index);
+ // Update the Index to the end of the repeated characters.
+ // So the following GetNext() opeartion will get
+ // the next character to be parsed.
+ Index = pos - 1;
+ return (repeatCount);
+ }
+
+ // Return false when end of string is encountered or a non-digit character is found.
+ internal bool GetNextDigit() {
+ if (++Index >= len) {
+ return (false);
+ }
+ return (DateTimeParse.IsDigit(Value[Index]));
+ }
+
+ //
+ // Get the current character.
+ //
+ internal char GetChar() {
+ Contract.Assert(Index >= 0 && Index < len, "Index >= 0 && Index < len");
+ return (Value[Index]);
+ }
+
+ //
+ // Convert the current character to a digit, and return it.
+ //
+ internal int GetDigit() {
+ Contract.Assert(Index >= 0 && Index < len, "Index >= 0 && Index < len");
+ Contract.Assert(DateTimeParse.IsDigit(Value[Index]), "IsDigit(Value[Index])");
+ return (Value[Index] - '0');
+ }
+
+ //
+ // Eat White Space ahead of the current position
+ //
+ // Return false if end of string is encountered.
+ //
+ internal void SkipWhiteSpaces()
+ {
+ // Look ahead to see if the next character
+ // is a whitespace.
+ while (Index+1 < len)
+ {
+ char ch = Value[Index+1];
+ if (!Char.IsWhiteSpace(ch)) {
+ return;
+ }
+ Index++;
+ }
+ return;
+ }
+
+ //
+ // Skip white spaces from the current position
+ //
+ // Return false if end of string is encountered.
+ //
+ internal bool SkipWhiteSpaceCurrent()
+ {
+ if (Index >= len) {
+ return (false);
+ }
+
+ if (!Char.IsWhiteSpace(m_current))
+ {
+ return (true);
+ }
+
+ while (++Index < len)
+ {
+ m_current = Value[Index];
+ if (!Char.IsWhiteSpace(m_current))
+ {
+ return (true);
+ }
+ // Nothing here.
+ }
+ return (false);
+ }
+
+ internal void TrimTail() {
+ int i = len - 1;
+ while (i >= 0 && Char.IsWhiteSpace(Value[i])) {
+ i--;
+ }
+ Value = Value.Substring(0, i + 1);
+ len = Value.Length;
+ }
+
+ // Trim the trailing spaces within a quoted string.
+ // Call this after TrimTail() is done.
+ internal void RemoveTrailingInQuoteSpaces() {
+ int i = len - 1;
+ if (i <= 1) {
+ return;
+ }
+ char ch = Value[i];
+ // Check if the last character is a quote.
+ if (ch == '\'' || ch == '\"') {
+ if (Char.IsWhiteSpace(Value[i-1])) {
+ i--;
+ while (i >= 1 && Char.IsWhiteSpace(Value[i-1])) {
+ i--;
+ }
+ Value = Value.Remove(i, Value.Length - 1 - i);
+ len = Value.Length;
+ }
+ }
+ }
+
+ // Trim the leading spaces within a quoted string.
+ // Call this after the leading spaces before quoted string are trimmed.
+ internal void RemoveLeadingInQuoteSpaces() {
+ if (len <= 2) {
+ return;
+ }
+ int i = 0;
+ char ch = Value[i];
+ // Check if the last character is a quote.
+ if (ch == '\'' || ch == '\"') {
+ while ((i + 1) < len && Char.IsWhiteSpace(Value[i+1])) {
+ i++;
+ }
+ if (i != 0) {
+ Value = Value.Remove(1, i);
+ len = Value.Length;
+ }
+ }
+ }
+
+ internal DTSubString GetSubString() {
+ DTSubString sub = new DTSubString();
+ sub.index = Index;
+ sub.s = Value;
+ while (Index + sub.length < len) {
+ DTSubStringType currentType;
+ Char ch = Value[Index + sub.length];
+ if (ch >= '0' && ch <= '9') {
+ currentType = DTSubStringType.Number;
+ }
+ else {
+ currentType = DTSubStringType.Other;
+ }
+
+ if (sub.length == 0) {
+ sub.type = currentType;
+ }
+ else {
+ if (sub.type != currentType) {
+ break;
+ }
+ }
+ sub.length++;
+ if (currentType == DTSubStringType.Number) {
+ // Incorporate the number into the value
+ // Limit the digits to prevent overflow
+ if (sub.length > DateTimeParse.MaxDateTimeNumberDigits) {
+ sub.type = DTSubStringType.Invalid;
+ return sub;
+ }
+ int number = ch - '0';
+ Contract.Assert(number >= 0 && number <= 9, "number >= 0 && number <= 9");
+ sub.value = sub.value * 10 + number;
+ }
+ else {
+ // For non numbers, just return this length 1 token. This should be expanded
+ // to more types of thing if this parsing approach is used for things other
+ // than numbers and single characters
+ break;
+ }
+ }
+ if (sub.length == 0) {
+ sub.type = DTSubStringType.End;
+ return sub;
+ }
+
+ return sub;
+ }
+
+ internal void ConsumeSubString(DTSubString sub) {
+ Contract.Assert(sub.index == Index, "sub.index == Index");
+ Contract.Assert(sub.index + sub.length <= len, "sub.index + sub.length <= len");
+ Index = sub.index + sub.length;
+ if (Index < len) {
+ m_current = Value[Index];
+ }
+ }
+ }
+
+ internal enum DTSubStringType {
+ Unknown = 0,
+ Invalid = 1,
+ Number = 2,
+ End = 3,
+ Other = 4,
+ }
+
+ internal struct DTSubString {
+ internal String s;
+ internal Int32 index;
+ internal Int32 length;
+ internal DTSubStringType type;
+ internal Int32 value;
+
+ internal Char this[Int32 relativeIndex] {
+ get {
+ return s[index + relativeIndex];
+ }
+ }
+ }
+
+ //
+ // The buffer to store the parsing token.
+ //
+ internal
+ struct DateTimeToken {
+ internal DateTimeParse.DTT dtt; // Store the token
+ internal TokenType suffix; // Store the CJK Year/Month/Day suffix (if any)
+ internal int num; // Store the number that we are parsing (if any)
+ }
+
+ //
+ // The buffer to store temporary parsing information.
+ //
+ internal
+ unsafe struct DateTimeRawInfo {
+ [SecurityCritical]
+ private int* num;
+ internal int numCount;
+ internal int month;
+ internal int year;
+ internal int dayOfWeek;
+ internal int era;
+ internal DateTimeParse.TM timeMark;
+ internal double fraction;
+ internal bool hasSameDateAndTimeSeparators;
+ //
+
+ internal bool timeZone;
+
+ [System.Security.SecurityCritical] // auto-generated
+ internal void Init(int * numberBuffer) {
+ month = -1;
+ year = -1;
+ dayOfWeek = -1;
+ era = -1;
+ timeMark = DateTimeParse.TM.NotSet;
+ fraction = -1;
+ num = numberBuffer;
+ }
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe void AddNumber(int value) {
+ num[numCount++] = value;
+ }
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe int GetNumber(int index) {
+ return num[index];
+ }
+ }
+
+ internal enum ParseFailureKind {
+ None = 0,
+ ArgumentNull = 1,
+ Format = 2,
+ FormatWithParameter = 3,
+ FormatBadDateTimeCalendar = 4, // FormatException when ArgumentOutOfRange is thrown by a Calendar.TryToDateTime().
+ };
+
+ [Flags]
+ internal enum ParseFlags {
+ HaveYear = 0x00000001,
+ HaveMonth = 0x00000002,
+ HaveDay = 0x00000004,
+ HaveHour = 0x00000008,
+ HaveMinute = 0x00000010,
+ HaveSecond = 0x00000020,
+ HaveTime = 0x00000040,
+ HaveDate = 0x00000080,
+ TimeZoneUsed = 0x00000100,
+ TimeZoneUtc = 0x00000200,
+ ParsedMonthName = 0x00000400,
+ CaptureOffset = 0x00000800,
+ YearDefault = 0x00001000,
+ Rfc1123Pattern = 0x00002000,
+ UtcSortPattern = 0x00004000,
+ }
+
+ //
+ // This will store the result of the parsing. And it will be eventually
+ // used to construct a DateTime instance.
+ //
+ internal
+ struct DateTimeResult
+ {
+ internal int Year;
+ internal int Month;
+ internal int Day;
+ //
+ // Set time defualt to 00:00:00.
+ //
+ internal int Hour;
+ internal int Minute;
+ internal int Second;
+ internal double fraction;
+
+ internal int era;
+
+ internal ParseFlags flags;
+
+ internal TimeSpan timeZoneOffset;
+
+ internal Calendar calendar;
+
+ internal DateTime parsedDate;
+
+ internal ParseFailureKind failure;
+ internal string failureMessageID;
+ internal object failureMessageFormatArgument;
+ internal string failureArgumentName;
+
+ internal void Init() {
+ Year = -1;
+ Month = -1;
+ Day = -1;
+ fraction = -1;
+ era = -1;
+ }
+
+ internal void SetDate(int year, int month, int day)
+ {
+ Year = year;
+ Month = month;
+ Day = day;
+ }
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument) {
+ this.failure = failure;
+ this.failureMessageID = failureMessageID;
+ this.failureMessageFormatArgument = failureMessageFormatArgument;
+ }
+
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument, string failureArgumentName) {
+ this.failure = failure;
+ this.failureMessageID = failureMessageID;
+ this.failureMessageFormatArgument = failureMessageFormatArgument;
+ this.failureArgumentName = failureArgumentName;
+ }
+
+
+
+
+ }
+
+ // This is the helper data structure used in ParseExact().
+ internal struct ParsingInfo {
+
+ internal Calendar calendar;
+ internal int dayOfWeek;
+ internal DateTimeParse.TM timeMark;
+
+ internal bool fUseHour12;
+ internal bool fUseTwoDigitYear;
+ internal bool fAllowInnerWhite;
+ internal bool fAllowTrailingWhite;
+ internal bool fCustomNumberParser;
+ internal DateTimeParse.MatchNumberDelegate parseNumberDelegate;
+
+ internal void Init() {
+ dayOfWeek = -1;
+ timeMark = DateTimeParse.TM.NotSet;
+ }
+
+ }
+
+ //
+ // The type of token that will be returned by DateTimeFormatInfo.Tokenize().
+ //
+ internal enum TokenType {
+ // The valid token should start from 1.
+
+ // Regular tokens. The range is from 0x00 ~ 0xff.
+ NumberToken = 1, // The number. E.g. "12"
+ YearNumberToken = 2, // The number which is considered as year number, which has 3 or more digits. E.g. "2003"
+ Am = 3, // AM timemark. E.g. "AM"
+ Pm = 4, // PM timemark. E.g. "PM"
+ MonthToken = 5, // A word (or words) that represents a month name. E.g. "March"
+ EndOfString = 6, // End of string
+ DayOfWeekToken = 7, // A word (or words) that represents a day of week name. E.g. "Monday" or "Mon"
+ TimeZoneToken = 8, // A word that represents a timezone name. E.g. "GMT"
+ EraToken = 9, // A word that represents a era name. E.g. "A.D."
+ DateWordToken = 10, // A word that can appear in a DateTime string, but serves no parsing semantics. E.g. "de" in Spanish culture.
+ UnknownToken = 11, // An unknown word, which signals an error in parsing.
+ HebrewNumber = 12, // A number that is composed of Hebrew text. Hebrew calendar uses Hebrew digits for year values, month values, and day values.
+ JapaneseEraToken= 13, // Era name for JapaneseCalendar
+ TEraToken = 14, // Era name for TaiwanCalendar
+ IgnorableSymbol = 15, // A separator like "," that is equivalent to whitespace
+
+
+ // Separator tokens.
+ SEP_Unk = 0x100, // Unknown separator.
+ SEP_End = 0x200, // The end of the parsing string.
+ SEP_Space = 0x300, // Whitespace (including comma).
+ SEP_Am = 0x400, // AM timemark. E.g. "AM"
+ SEP_Pm = 0x500, // PM timemark. E.g. "PM"
+ SEP_Date = 0x600, // date separator. E.g. "/"
+ SEP_Time = 0x700, // time separator. E.g. ":"
+ SEP_YearSuff = 0x800, // Chinese/Japanese/Korean year suffix.
+ SEP_MonthSuff = 0x900, // Chinese/Japanese/Korean month suffix.
+ SEP_DaySuff = 0xa00, // Chinese/Japanese/Korean day suffix.
+ SEP_HourSuff = 0xb00, // Chinese/Japanese/Korean hour suffix.
+ SEP_MinuteSuff = 0xc00, // Chinese/Japanese/Korean minute suffix.
+ SEP_SecondSuff = 0xd00, // Chinese/Japanese/Korean second suffix.
+ SEP_LocalTimeMark = 0xe00, // 'T', used in ISO 8601 format.
+ SEP_DateOrOffset = 0xf00, // '-' which could be a date separator or start of a time zone offset
+
+ RegularTokenMask = 0x00ff,
+ SeparatorTokenMask = 0xff00,
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/DateTimeStyles.cs b/src/mscorlib/src/System/Globalization/DateTimeStyles.cs
new file mode 100644
index 0000000000..ddae7b2f29
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/DateTimeStyles.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Contains valid formats for DateTime recognized by
+** the DateTime class' parsing code.
+**
+**
+===========================================================*/
+namespace System.Globalization {
+
+
+[Serializable]
+ [Flags]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public enum DateTimeStyles {
+ // Bit flag indicating that leading whitespace is allowed. Character values
+ // 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, and 0x0020 are considered to be
+ // whitespace.
+
+
+ None = 0x00000000,
+
+ AllowLeadingWhite = 0x00000001,
+
+ AllowTrailingWhite = 0x00000002, //Bitflag indicating trailing whitespace is allowed.
+
+ AllowInnerWhite = 0x00000004,
+
+ AllowWhiteSpaces = AllowLeadingWhite | AllowInnerWhite | AllowTrailingWhite,
+ // When parsing a date/time string, if all year/month/day are missing, set the default date
+ // to 0001/1/1, instead of the current year/month/day.
+
+ NoCurrentDateDefault = 0x00000008,
+ // When parsing a date/time string, if a timezone specifier ("GMT","Z","+xxxx", "-xxxx" exists), we will
+ // ajdust the parsed time based to GMT.
+
+ AdjustToUniversal = 0x00000010,
+
+ AssumeLocal = 0x00000020,
+
+ AssumeUniversal = 0x00000040,
+ // Attempt to preserve whether the input is unspecified, local or UTC
+ RoundtripKind = 0x00000080,
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/DaylightTime.cs b/src/mscorlib/src/System/Globalization/DaylightTime.cs
new file mode 100644
index 0000000000..037d9ffdf3
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/DaylightTime.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+
+ using System;
+ // This class represents a starting/ending time for a period of daylight saving time.
+ [Serializable]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public class DaylightTime
+ {
+ internal DateTime m_start;
+ internal DateTime m_end;
+ internal TimeSpan m_delta;
+
+ private DaylightTime() {
+ }
+
+ public DaylightTime(DateTime start, DateTime end, TimeSpan delta) {
+ m_start = start;
+ m_end = end;
+ m_delta = delta;
+ }
+
+ // The start date of a daylight saving period.
+ public DateTime Start {
+ get {
+ return m_start;
+ }
+ }
+
+ // The end date of a daylight saving period.
+ public DateTime End {
+ get {
+ return m_end;
+ }
+ }
+
+ // Delta to stardard offset in ticks.
+ public TimeSpan Delta {
+ get {
+ return m_delta;
+ }
+ }
+
+ }
+
+}
diff --git a/src/mscorlib/src/System/Globalization/DigitShapes.cs b/src/mscorlib/src/System/Globalization/DigitShapes.cs
new file mode 100644
index 0000000000..d5b5ecc89c
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/DigitShapes.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// The enumeration constants used in NumberFormatInfo.DigitSubstitution.
+//
+namespace System.Globalization {
+
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public enum DigitShapes : int {
+
+ Context = 0x0000, // The shape depends on the previous text in the same output.
+
+ None = 0x0001, // Gives full Unicode compatibility.
+
+ NativeNational = 0x0002, // National shapes determined by LOCALE_SNATIVEDIGITS
+ }
+}
+
diff --git a/src/mscorlib/src/System/Globalization/EastAsianLunisolarCalendar.cs b/src/mscorlib/src/System/Globalization/EastAsianLunisolarCalendar.cs
new file mode 100644
index 0000000000..2460eee3af
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/EastAsianLunisolarCalendar.cs
@@ -0,0 +1,644 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+ using System.Diagnostics.Contracts;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Notes about EastAsianLunisolarCalendar
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public abstract class EastAsianLunisolarCalendar : Calendar {
+ internal const int LeapMonth = 0;
+ internal const int Jan1Month = 1;
+ internal const int Jan1Date = 2;
+ internal const int nDaysPerMonth = 3;
+
+ // # of days so far in the solar year
+ internal static readonly int[] DaysToMonth365 =
+ {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+
+ internal static readonly int[] DaysToMonth366 =
+ {
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
+ };
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+
+ // Return the type of the East Asian Lunisolar calendars.
+ //
+
+ public override CalendarAlgorithmType AlgorithmType {
+ get {
+ return CalendarAlgorithmType.LunisolarCalendar;
+ }
+ }
+
+ // Return the year number in the 60-year cycle.
+ //
+
+ public virtual int GetSexagenaryYear (DateTime time) {
+ CheckTicksRange(time.Ticks);
+
+ int year = 0, month = 0, day = 0;
+ TimeToLunar(time, ref year, ref month, ref day);
+
+ return ((year - 4) % 60) + 1;
+ }
+
+ // Return the celestial year from the 60-year cycle.
+ // The returned value is from 1 ~ 10.
+ //
+
+ public int GetCelestialStem(int sexagenaryYear) {
+ if ((sexagenaryYear < 1) || (sexagenaryYear > 60)) {
+ throw new ArgumentOutOfRangeException(
+ "sexagenaryYear",
+ Environment.GetResourceString("ArgumentOutOfRange_Range", 1, 60));
+ }
+ Contract.EndContractBlock();
+
+ return ((sexagenaryYear - 1) % 10) + 1;
+ }
+
+ // Return the Terrestial Branch from the the 60-year cycle.
+ // The returned value is from 1 ~ 12.
+ //
+
+ public int GetTerrestrialBranch(int sexagenaryYear) {
+ if ((sexagenaryYear < 1) || (sexagenaryYear > 60)) {
+ throw new ArgumentOutOfRangeException(
+ "sexagenaryYear",
+ Environment.GetResourceString("ArgumentOutOfRange_Range", 1, 60));
+ }
+ Contract.EndContractBlock();
+
+ return ((sexagenaryYear - 1) % 12) + 1;
+ }
+
+ internal abstract int GetYearInfo(int LunarYear, int Index);
+ internal abstract int GetYear(int year, DateTime time);
+ internal abstract int GetGregorianYear(int year, int era);
+
+ internal abstract int MinCalendarYear {get;}
+ internal abstract int MaxCalendarYear {get;}
+ internal abstract EraInfo[] CalEraInfo{get;}
+ internal abstract DateTime MinDate {get;}
+ internal abstract DateTime MaxDate {get;}
+
+ internal const int MaxCalendarMonth = 13;
+ internal const int MaxCalendarDay = 30;
+
+ internal int MinEraCalendarYear (int era) {
+ EraInfo[] mEraInfo = CalEraInfo;
+ //ChineseLunisolarCalendar does not has m_EraInfo it is going to retuen null
+ if (mEraInfo == null) {
+ return MinCalendarYear;
+ }
+
+ if (era == Calendar.CurrentEra) {
+ era = CurrentEraValue;
+ }
+ //era has to be in the supported range otherwise we will throw exception in CheckEraRange()
+ if (era == GetEra(MinDate)) {
+ return (GetYear(MinCalendarYear, MinDate));
+ }
+
+ for (int i = 0; i < mEraInfo.Length; i++) {
+ if (era == mEraInfo[i].era) {
+ return (mEraInfo[i].minEraYear);
+ }
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ internal int MaxEraCalendarYear (int era) {
+ EraInfo[] mEraInfo = CalEraInfo;
+ //ChineseLunisolarCalendar does not has m_EraInfo it is going to retuen null
+ if (mEraInfo == null) {
+ return MaxCalendarYear;
+ }
+
+ if (era == Calendar.CurrentEra) {
+ era = CurrentEraValue;
+ }
+ //era has to be in the supported range otherwise we will throw exception in CheckEraRange()
+ if (era == GetEra(MaxDate)) {
+ return (GetYear(MaxCalendarYear, MaxDate));
+ }
+
+ for (int i = 0; i < mEraInfo.Length; i++) {
+ if (era == mEraInfo[i].era) {
+ return (mEraInfo[i].maxEraYear);
+ }
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ // Construct an instance of EastAsianLunisolar calendar.
+
+ internal EastAsianLunisolarCalendar() {
+ }
+
+ internal void CheckTicksRange(long ticks) {
+ if (ticks < MinSupportedDateTime.Ticks || ticks > MaxSupportedDateTime.Ticks) {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(CultureInfo.InvariantCulture, Environment.GetResourceString("ArgumentOutOfRange_CalendarRange"),
+ MinSupportedDateTime, MaxSupportedDateTime));
+ }
+ Contract.EndContractBlock();
+ }
+
+ internal void CheckEraRange (int era) {
+ if (era == Calendar.CurrentEra) {
+ era = CurrentEraValue;
+ }
+
+ if ((era <GetEra(MinDate)) || (era > GetEra(MaxDate))) {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+ }
+
+ internal int CheckYearRange(int year, int era) {
+ CheckEraRange(era);
+ year = GetGregorianYear(year, era);
+
+ if ((year < MinCalendarYear) || (year > MaxCalendarYear)) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ Environment.GetResourceString("ArgumentOutOfRange_Range", MinEraCalendarYear(era), MaxEraCalendarYear(era)));
+ }
+ return year;
+ }
+
+ internal int CheckYearMonthRange(int year, int month, int era) {
+ year = CheckYearRange(year, era);
+
+ if (month == 13)
+ {
+ //Reject if there is no leap month this year
+ if (GetYearInfo(year , LeapMonth) == 0)
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
+ }
+
+ if (month < 1 || month > 13) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
+ }
+ return year;
+ }
+
+ internal int InternalGetDaysInMonth(int year, int month) {
+ int nDays;
+ int mask; // mask for extracting bits
+
+ mask = 0x8000;
+ // convert the lunar day into a lunar month/date
+ mask >>= (month-1);
+ if ((GetYearInfo(year, nDaysPerMonth) & mask)== 0)
+ nDays = 29;
+ else
+ nDays = 30;
+ return nDays;
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ year = CheckYearMonthRange(year, month, era);
+ return InternalGetDaysInMonth(year, month);
+ }
+
+ static int GregorianIsLeapYear(int y) {
+ return ((((y)%4)!=0)?0:((((y)%100)!=0)?1:((((y)%400)!=0)?0:1)));
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ year = CheckYearMonthRange(year, month, era);
+ int daysInMonth = InternalGetDaysInMonth(year, month);
+ if (day < 1 || day > daysInMonth) {
+ BCLDebug.Log("year = " + year + ", month = " + month + ", day = " + day);
+ throw new ArgumentOutOfRangeException(
+ "day",
+ Environment.GetResourceString("ArgumentOutOfRange_Day", daysInMonth, month));
+ }
+
+ int gy=0; int gm=0; int gd=0;
+
+ if (LunarToGregorian(year, month, day, ref gy, ref gm, ref gd)) {
+ return new DateTime(gy, gm, gd, hour, minute, second, millisecond);
+ } else {
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
+ }
+ }
+
+
+ //
+ // GregorianToLunar calculates lunar calendar info for the given gregorian year, month, date.
+ // The input date should be validated before calling this method.
+ //
+ internal void GregorianToLunar(int nSYear, int nSMonth, int nSDate, ref int nLYear, ref int nLMonth, ref int nLDate) {
+ // unsigned int nLYear, nLMonth, nLDate; // lunar ymd
+ int nSolarDay; // day # in solar year
+ int nLunarDay; // day # in lunar year
+ int fLeap; // is it a solar leap year?
+ int LDpM; // lunar days/month bitfield
+ int mask; // mask for extracting bits
+ int nDays; // # days this lunar month
+ int nJan1Month, nJan1Date;
+
+ // calc the solar day of year
+ fLeap = GregorianIsLeapYear(nSYear);
+ nSolarDay = (fLeap==1) ? DaysToMonth366[nSMonth-1]: DaysToMonth365[nSMonth-1] ;
+ nSolarDay += nSDate;
+
+ // init lunar year info
+ nLunarDay = nSolarDay;
+ nLYear = nSYear;
+ if (nLYear == (MaxCalendarYear + 1)) {
+ nLYear--;
+ nLunarDay += ((GregorianIsLeapYear(nLYear) == 1) ? 366 : 365);
+ nJan1Month = GetYearInfo(nLYear, Jan1Month);
+ nJan1Date = GetYearInfo(nLYear,Jan1Date);
+ } else {
+
+ nJan1Month = GetYearInfo(nLYear, Jan1Month);
+ nJan1Date = GetYearInfo(nLYear,Jan1Date);
+
+ // check if this solar date is actually part of the previous
+ // lunar year
+ if ((nSMonth < nJan1Month) ||
+ (nSMonth == nJan1Month && nSDate < nJan1Date)) {
+ // the corresponding lunar day is actually part of the previous
+ // lunar year
+ nLYear--;
+
+ // add a solar year to the lunar day #
+ nLunarDay += ((GregorianIsLeapYear(nLYear) == 1) ? 366 : 365);
+
+ // update the new start of year
+ nJan1Month = GetYearInfo(nLYear, Jan1Month);
+ nJan1Date = GetYearInfo(nLYear, Jan1Date);
+ }
+ }
+
+ // convert solar day into lunar day.
+ // subtract off the beginning part of the solar year which is not
+ // part of the lunar year. since this part is always in Jan or Feb,
+ // we don't need to handle Leap Year (LY only affects March
+ // and later).
+ nLunarDay -= DaysToMonth365[nJan1Month-1];
+ nLunarDay -= (nJan1Date - 1);
+
+ // convert the lunar day into a lunar month/date
+ mask = 0x8000;
+ LDpM = GetYearInfo(nLYear, nDaysPerMonth);
+ nDays = ((LDpM & mask) != 0) ? 30 : 29;
+ nLMonth = 1;
+ while (nLunarDay > nDays) {
+ nLunarDay -= nDays;
+ nLMonth++;
+ mask >>= 1;
+ nDays = ((LDpM & mask) != 0) ? 30 : 29;
+ }
+ nLDate = nLunarDay;
+ }
+
+ /*
+ //Convert from Lunar to Gregorian
+ //Highly inefficient, but it works based on the forward conversion
+ */
+ internal bool LunarToGregorian(int nLYear, int nLMonth, int nLDate, ref int nSolarYear, ref int nSolarMonth, ref int nSolarDay) {
+ int numLunarDays;
+
+ if (nLDate < 1 || nLDate > 30)
+ return false;
+
+ numLunarDays = nLDate-1;
+
+ //Add previous months days to form the total num of days from the first of the month.
+ for (int i = 1; i < nLMonth; i++) {
+ numLunarDays += InternalGetDaysInMonth(nLYear, i);
+ }
+
+ //Get Gregorian First of year
+ int nJan1Month = GetYearInfo(nLYear, Jan1Month);
+ int nJan1Date = GetYearInfo(nLYear, Jan1Date);
+
+ // calc the solar day of year of 1 Lunar day
+ int fLeap = GregorianIsLeapYear(nLYear);
+ int[] days = (fLeap==1)? DaysToMonth366: DaysToMonth365;
+
+ nSolarDay = nJan1Date;
+
+ if (nJan1Month > 1)
+ nSolarDay += days [nJan1Month-1];
+
+ // Add the actual lunar day to get the solar day we want
+ nSolarDay = nSolarDay + numLunarDays;// - 1;
+
+ if ( nSolarDay > (fLeap + 365)) {
+ nSolarYear = nLYear + 1;
+ nSolarDay -= (fLeap + 365);
+ } else {
+ nSolarYear = nLYear;
+ }
+
+ for (nSolarMonth = 1; nSolarMonth < 12; nSolarMonth++) {
+ if (days[nSolarMonth] >= nSolarDay)
+ break;
+ }
+
+ nSolarDay -= days[nSolarMonth-1];
+ return true;
+ }
+
+ internal DateTime LunarToTime(DateTime time, int year, int month, int day) {
+ int gy=0; int gm=0; int gd=0;
+ LunarToGregorian(year, month, day, ref gy, ref gm, ref gd);
+ return (GregorianCalendar.GetDefaultInstance().ToDateTime(gy,gm,gd,time.Hour,time.Minute,time.Second,time.Millisecond));
+ }
+
+ internal void TimeToLunar(DateTime time, ref int year, ref int month, ref int day) {
+ int gy=0; int gm=0; int gd=0;
+
+ Calendar Greg = GregorianCalendar.GetDefaultInstance();
+ gy = Greg.GetYear(time);
+ gm = Greg.GetMonth(time);
+ gd = Greg.GetDayOfMonth(time);
+
+ GregorianToLunar(gy, gm, gd, ref year, ref month, ref day);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+
+ public override DateTime AddMonths(DateTime time, int months) {
+ if (months < -120000 || months > 120000) {
+ throw new ArgumentOutOfRangeException(
+ "months",
+ Environment.GetResourceString("ArgumentOutOfRange_Range", -120000, 120000));
+ }
+ Contract.EndContractBlock();
+
+ CheckTicksRange(time.Ticks);
+
+ int y=0; int m=0; int d=0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ int i = m + months;
+ if (i > 0) {
+ int monthsInYear = InternalIsLeapYear(y)?13:12;
+
+ while (i-monthsInYear > 0) {
+ i -= monthsInYear;
+ y++;
+ monthsInYear = InternalIsLeapYear(y)?13:12;
+ }
+ m = i;
+ } else {
+ int monthsInYear;
+ while (i <= 0) {
+ monthsInYear = InternalIsLeapYear(y-1)?13:12;
+ i += monthsInYear;
+ y--;
+ }
+ m = i;
+ }
+
+ int days = InternalGetDaysInMonth(y, m);
+ if (d > days) {
+ d = days;
+ }
+ DateTime dt = LunarToTime(time, y, m, d);
+
+ CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (dt);
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years) {
+ CheckTicksRange(time.Ticks);
+
+ int y=0; int m=0; int d=0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ y += years;
+
+ if (m==13 && !InternalIsLeapYear(y)) {
+ m = 12;
+ d = InternalGetDaysInMonth(y, m);
+ }
+ int DaysInMonths = InternalGetDaysInMonth(y, m);
+ if (d > DaysInMonths) {
+ d = DaysInMonths;
+ }
+
+ DateTime dt = LunarToTime(time, y, m, d);
+ CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (dt);
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and [354|355 |383|384].
+ //
+
+ public override int GetDayOfYear(DateTime time) {
+ CheckTicksRange(time.Ticks);
+
+ int y=0; int m=0; int d=0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ for (int i=1; i<m ;i++)
+ {
+ d = d + InternalGetDaysInMonth(y, i);
+ }
+ return d;
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 29 or 30.
+ //
+
+ public override int GetDayOfMonth(DateTime time) {
+ CheckTicksRange(time.Ticks);
+
+ int y=0; int m=0; int d=0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ return d;
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era) {
+ year = CheckYearRange(year, era);
+
+ int Days = 0;
+ int monthsInYear = InternalIsLeapYear(year) ? 13 : 12;
+
+ while (monthsInYear != 0)
+ Days += InternalGetDaysInMonth(year, monthsInYear--);
+
+ return Days;
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 13.
+ //
+
+ public override int GetMonth(DateTime time) {
+ CheckTicksRange(time.Ticks);
+
+ int y=0; int m=0; int d=0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ return m;
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and MaxCalendarYear.
+ //
+
+ public override int GetYear(DateTime time) {
+ CheckTicksRange(time.Ticks);
+
+ int y=0; int m=0; int d=0;
+ TimeToLunar(time, ref y, ref m, ref d);
+
+ return GetYear(y, time);
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ CheckTicksRange(time.Ticks);
+ return ((DayOfWeek)((int)(time.Ticks / Calendar.TicksPerDay + 1) % 7));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public override int GetMonthsInYear(int year, int era) {
+ year = CheckYearRange(year, era);
+ return (InternalIsLeapYear(year)?13:12);
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public override bool IsLeapDay(int year, int month, int day, int era) {
+ year = CheckYearMonthRange(year, month, era);
+ int daysInMonth = InternalGetDaysInMonth(year, month);
+
+ if (day < 1 || day > daysInMonth) {
+ throw new ArgumentOutOfRangeException(
+ "day",
+ Environment.GetResourceString("ArgumentOutOfRange_Day", daysInMonth, month));
+ }
+ int m = GetYearInfo(year, LeapMonth);
+ return ((m!=0) && (month == (m+1)));
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public override bool IsLeapMonth(int year, int month, int era) {
+ year = CheckYearMonthRange(year, month, era);
+ int m = GetYearInfo(year, LeapMonth);
+ return ((m!=0) && (month == (m+1)));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this this year is not a leap year.
+ //
+
+ public override int GetLeapMonth(int year, int era) {
+ year = CheckYearRange(year, era);
+ int month = GetYearInfo(year, LeapMonth);
+ if (month>0)
+ {
+ return (month+1);
+ }
+ return 0;
+ }
+
+ internal bool InternalIsLeapYear(int year) {
+ return (GetYearInfo(year, LeapMonth)!=0);
+ }
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era) {
+ year = CheckYearRange(year, era);
+ return InternalIsLeapYear(year);
+ }
+
+ private const int DEFAULT_GREGORIAN_TWO_DIGIT_YEAR_MAX = 2029;
+
+
+ public override int TwoDigitYearMax {
+ get {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(BaseCalendarID, GetYear(new DateTime(DEFAULT_GREGORIAN_TWO_DIGIT_YEAR_MAX, 1, 1)));
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value < 99 || value > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "value",
+ Environment.GetResourceString("ArgumentOutOfRange_Range", 99, MaxCalendarYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ year = base.ToFourDigitYear(year);
+ CheckYearRange(year, CurrentEra);
+ return (year);
+ }
+
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/EncodingDataItem.Unix.cs b/src/mscorlib/src/System/Globalization/EncodingDataItem.Unix.cs
new file mode 100644
index 0000000000..408f6e681e
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/EncodingDataItem.Unix.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ [Serializable]
+ internal class CodePageDataItem
+ {
+ private readonly int _codePage;
+ private readonly int _uiFamilyCodePage;
+ private readonly string _webName;
+ private readonly uint _flags;
+ private string _displayNameResourceKey;
+
+ internal CodePageDataItem(int codePage, int uiFamilyCodePage, string webName, uint flags)
+ {
+ _codePage = codePage;
+ _uiFamilyCodePage = uiFamilyCodePage;
+ _webName = webName;
+ _flags = flags;
+ }
+
+ public int CodePage
+ {
+ get { return _codePage; }
+ }
+
+ public int UIFamilyCodePage
+ {
+ get { return _uiFamilyCodePage; }
+ }
+
+ public String WebName
+ {
+ get { return _webName; }
+ }
+
+ public String HeaderName
+ {
+ get { return _webName; } // all the code pages used on unix only have a single name
+ }
+
+ public String BodyName
+ {
+ get { return _webName; } // all the code pages used on unix only have a single name
+ }
+
+ public uint Flags
+ {
+ get { return _flags; }
+ }
+
+ // PAL ends here
+
+ public string DisplayNameResourceKey
+ {
+ get
+ {
+ if (_displayNameResourceKey == null)
+ {
+ _displayNameResourceKey = "Globalization.cp_" + CodePage;
+ }
+
+ return _displayNameResourceKey;
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/EncodingDataItem.cs b/src/mscorlib/src/System/Globalization/EncodingDataItem.cs
new file mode 100644
index 0000000000..1dc1bd2eaf
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/EncodingDataItem.cs
@@ -0,0 +1,115 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System.Text;
+ using System.Runtime.Remoting;
+ using System;
+ using System.Security;
+
+ //
+ // Data item for EncodingTable. Along with EncodingTable, they are used by
+ // System.Text.Encoding.
+ //
+ // This class stores a pointer to the internal data and the index into that data
+ // where our required information is found. We load the code page, flags and uiFamilyCodePage
+ // immediately because they don't require creating an object. Creating any of the string
+ // names is delayed until somebody actually asks for them and the names are then cached.
+
+ [Serializable]
+ internal class CodePageDataItem
+ {
+ internal int m_dataIndex;
+ internal int m_uiFamilyCodePage;
+ internal String m_webName;
+ internal String m_headerName;
+ internal String m_bodyName;
+ internal uint m_flags;
+
+ [SecurityCritical]
+ unsafe internal CodePageDataItem(int dataIndex) {
+ m_dataIndex = dataIndex;
+ m_uiFamilyCodePage = EncodingTable.codePageDataPtr[dataIndex].uiFamilyCodePage;
+ m_flags = EncodingTable.codePageDataPtr[dataIndex].flags;
+ }
+
+ [System.Security.SecurityCritical]
+ unsafe internal static String CreateString(sbyte* pStrings, uint index)
+ {
+ if (pStrings[0] == '|') // |str1|str2|str3
+ {
+ int start = 1;
+
+ for (int i = 1; true; i ++)
+ {
+ sbyte ch = pStrings[i];
+
+ if ((ch == '|') || (ch == 0))
+ {
+ if (index == 0)
+ {
+ return new String(pStrings, start, i - start);
+ }
+
+ index --;
+ start = i + 1;
+
+ if (ch == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ throw new ArgumentException("pStrings");
+ }
+ else
+ {
+ return new String(pStrings);
+ }
+ }
+
+ unsafe public String WebName {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ if (m_webName==null) {
+ m_webName = CreateString(EncodingTable.codePageDataPtr[m_dataIndex].Names, 0);
+ }
+ return m_webName;
+ }
+ }
+
+ public virtual int UIFamilyCodePage {
+ get {
+ return m_uiFamilyCodePage;
+ }
+ }
+
+ unsafe public String HeaderName {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ if (m_headerName==null) {
+ m_headerName = CreateString(EncodingTable.codePageDataPtr[m_dataIndex].Names, 1);
+ }
+ return m_headerName;
+ }
+ }
+
+ unsafe public String BodyName {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ if (m_bodyName==null) {
+ m_bodyName = CreateString(EncodingTable.codePageDataPtr[m_dataIndex].Names, 2);
+ }
+ return m_bodyName;
+ }
+ }
+
+ unsafe public uint Flags {
+ get {
+ return (m_flags);
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/EncodingTable.Unix.cs b/src/mscorlib/src/System/Globalization/EncodingTable.Unix.cs
new file mode 100644
index 0000000000..5628a2def9
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/EncodingTable.Unix.cs
@@ -0,0 +1,178 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Text;
+
+namespace System.Globalization
+{
+ internal static class EncodingTable
+ {
+ // Return a list of all EncodingInfo objects describing all of our encodings
+ internal static EncodingInfo[] GetEncodings()
+ {
+ EncodingInfo[] arrayEncodingInfo = new EncodingInfo[s_encodingDataTableItems.Length];
+
+ for (int i = 0; i < s_encodingDataTableItems.Length; i++)
+ {
+ CodePageDataItem dataItem = s_encodingDataTableItems[i];
+
+ arrayEncodingInfo[i] = new EncodingInfo(dataItem.CodePage, dataItem.WebName,
+ Environment.GetResourceString(dataItem.DisplayNameResourceKey));
+ }
+
+ return arrayEncodingInfo;
+ }
+
+ internal static int GetCodePageFromName(string name)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException("name");
+ }
+ Contract.EndContractBlock();
+
+ ushort codePage;
+ if (s_encodingDataTable.TryGetValue(name, out codePage))
+ {
+ return codePage;
+ }
+
+ // The encoding name is not valid.
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("Argument_EncodingNotSupported"), name), "name");
+ }
+
+ internal static CodePageDataItem GetCodePageDataItem(int codepage)
+ {
+ CodePageDataItem item;
+
+ switch (codepage)
+ {
+ case 1200:
+ item = s_encodingDataTableItems[0];
+ break;
+ case 1201:
+ item = s_encodingDataTableItems[1];
+ break;
+ case 12000:
+ item = s_encodingDataTableItems[2];
+ break;
+ case 12001:
+ item = s_encodingDataTableItems[3];
+ break;
+ case 20127:
+ item = s_encodingDataTableItems[4];
+ break;
+ case 28591:
+ item = s_encodingDataTableItems[5];
+ break;
+ case 65000:
+ item = s_encodingDataTableItems[6];
+ break;
+ case 65001:
+ item = s_encodingDataTableItems[7];
+ break;
+ default:
+ item = null;
+ break;
+ }
+
+ Contract.Assert(item == null || item.CodePage == codepage, "item.CodePage needs to equal the specified codepage");
+ return item;
+ }
+
+ // PAL ends here.
+
+#if DEBUG
+ static EncodingTable()
+ {
+ Contract.Assert(
+ s_encodingDataTable.Count == EncodingTableCapacity,
+ string.Format(CultureInfo.InvariantCulture,
+ "EncodingTable s_encodingDataTable's initial capacity (EncodingTableCapacity) is incorrect.{0}Expected (s_encodingDataTable.Count): {1}, Actual (EncodingTableCapacity): {2}",
+ Environment.NewLine,
+ s_encodingDataTable.Count,
+ EncodingTableCapacity));
+ }
+#endif
+
+ // NOTE: the following two lists were taken from ~\src\classlibnative\nls\encodingdata.cpp
+ // and should be kept in sync with those lists
+
+ private const int EncodingTableCapacity = 42;
+ private readonly static Dictionary<string, ushort> s_encodingDataTable =
+ new Dictionary<string, ushort>(EncodingTableCapacity, StringComparer.OrdinalIgnoreCase)
+ {
+ { "ANSI_X3.4-1968", 20127 },
+ { "ANSI_X3.4-1986", 20127 },
+ { "ascii", 20127 },
+ { "cp367", 20127 },
+ { "cp819", 28591 },
+ { "csASCII", 20127 },
+ { "csISOLatin1", 28591 },
+ { "csUnicode11UTF7", 65000 },
+ { "IBM367", 20127 },
+ { "ibm819", 28591 },
+ { "ISO-10646-UCS-2", 1200 },
+ { "iso-8859-1", 28591 },
+ { "iso-ir-100", 28591 },
+ { "iso-ir-6", 20127 },
+ { "ISO646-US", 20127 },
+ { "iso8859-1", 28591 },
+ { "ISO_646.irv:1991", 20127 },
+ { "iso_8859-1", 28591 },
+ { "iso_8859-1:1987", 28591 },
+ { "l1", 28591 },
+ { "latin1", 28591 },
+ { "ucs-2", 1200 },
+ { "unicode", 1200},
+ { "unicode-1-1-utf-7", 65000 },
+ { "unicode-1-1-utf-8", 65001 },
+ { "unicode-2-0-utf-7", 65000 },
+ { "unicode-2-0-utf-8", 65001 },
+ // People get confused about the FFFE here. We can't change this because it'd break existing apps.
+ // This has been this way for a long time, including in Mlang.
+ // Big Endian, BOM seems backwards, think of the BOM in little endian order.
+ { "unicodeFFFE", 1201},
+ { "us", 20127 },
+ { "us-ascii", 20127 },
+ { "utf-16", 1200 },
+ { "UTF-16BE", 1201},
+ { "UTF-16LE", 1200},
+ { "utf-32", 12000 },
+ { "UTF-32BE", 12001 },
+ { "UTF-32LE", 12000 },
+ { "utf-7", 65000 },
+ { "utf-8", 65001 },
+ { "x-unicode-1-1-utf-7", 65000 },
+ { "x-unicode-1-1-utf-8", 65001 },
+ { "x-unicode-2-0-utf-7", 65000 },
+ { "x-unicode-2-0-utf-8", 65001 },
+ };
+
+ // redeclaring these constants here for readability below
+ private const uint MIMECONTF_MAILNEWS = Encoding.MIMECONTF_MAILNEWS;
+ private const uint MIMECONTF_BROWSER = Encoding.MIMECONTF_BROWSER;
+ private const uint MIMECONTF_SAVABLE_MAILNEWS = Encoding.MIMECONTF_SAVABLE_MAILNEWS;
+ private const uint MIMECONTF_SAVABLE_BROWSER = Encoding.MIMECONTF_SAVABLE_BROWSER;
+
+ // keep this array sorted by code page, so the order is consistent for GetEncodings()
+ // Remember to update GetCodePageDataItem() if this list is updated
+ private readonly static CodePageDataItem[] s_encodingDataTableItems = new[]
+ {
+ new CodePageDataItem(1200, 1200, "utf-16", MIMECONTF_SAVABLE_BROWSER), // "Unicode"
+ new CodePageDataItem(1201, 1200, "utf-16BE", 0), // Big Endian, old FFFE BOM seems backwards, think of the BOM in little endian order.
+ new CodePageDataItem(12000, 1200, "utf-32", 0), // "Unicode (UTF-32)"
+ new CodePageDataItem(12001, 1200, "utf-32BE", 0), // "Unicode (UTF-32 Big Endian)"
+ new CodePageDataItem(20127, 1252, "us-ascii", MIMECONTF_MAILNEWS | MIMECONTF_SAVABLE_MAILNEWS), // "US-ASCII"
+ new CodePageDataItem(28591, 1252, "iso-8859-1", MIMECONTF_MAILNEWS | MIMECONTF_BROWSER | MIMECONTF_SAVABLE_MAILNEWS | MIMECONTF_SAVABLE_BROWSER), // "Western European (ISO)"
+ new CodePageDataItem(65000, 1200, "utf-7", MIMECONTF_MAILNEWS | MIMECONTF_SAVABLE_MAILNEWS), // "Unicode (UTF-7)"
+ new CodePageDataItem(65001, 1200, "utf-8", MIMECONTF_MAILNEWS | MIMECONTF_BROWSER | MIMECONTF_SAVABLE_MAILNEWS | MIMECONTF_SAVABLE_BROWSER), // "Unicode (UTF-8)"
+ };
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/EncodingTable.cs b/src/mscorlib/src/System/Globalization/EncodingTable.cs
new file mode 100644
index 0000000000..cdda9eaf6d
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/EncodingTable.cs
@@ -0,0 +1,250 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ using System;
+ using System.Text;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
+ using System.Runtime.Versioning;
+ using System.Security;
+ using System.Threading;
+ using System.Diagnostics.Contracts;
+ //
+ // Data table for encoding classes. Used by System.Text.Encoding.
+ // This class contains two hashtables to allow System.Text.Encoding
+ // to retrieve the data item either by codepage value or by webName.
+ //
+
+ // Only statics, does not need to be marked with the serializable attribute
+ internal static class EncodingTable
+ {
+
+ //This number is the size of the table in native. The value is retrieved by
+ //calling the native GetNumEncodingItems().
+ private static int lastEncodingItem = GetNumEncodingItems() - 1;
+
+ //This number is the size of the code page table. Its generated when we walk the table the first time.
+ private static volatile int lastCodePageItem;
+
+ //
+ // This points to a native data table which maps an encoding name to the correct code page.
+ //
+ [SecurityCritical]
+ unsafe internal static InternalEncodingDataItem *encodingDataPtr = GetEncodingData();
+ //
+ // This points to a native data table which stores the properties for the code page, and
+ // the table is indexed by code page.
+ //
+ [SecurityCritical]
+ unsafe internal static InternalCodePageDataItem *codePageDataPtr = GetCodePageData();
+ //
+ // This caches the mapping of an encoding name to a code page.
+ //
+ private static Hashtable hashByName = Hashtable.Synchronized(new Hashtable(StringComparer.OrdinalIgnoreCase));
+ //
+ // THe caches the data item which is indexed by the code page value.
+ //
+ private static Hashtable hashByCodePage = Hashtable.Synchronized(new Hashtable());
+
+ [System.Security.SecuritySafeCritical] // static constructors should be safe to call
+ static EncodingTable()
+ {
+ }
+
+ // Find the data item by binary searching the table that we have in native.
+ // nativeCompareOrdinalWC is an internal-only function.
+ [System.Security.SecuritySafeCritical] // auto-generated
+ unsafe private static int internalGetCodePageFromName(String name) {
+ int left = 0;
+ int right = lastEncodingItem;
+ int index;
+ int result;
+
+ //Binary search the array until we have only a couple of elements left and then
+ //just walk those elements.
+ while ((right - left)>3) {
+ index = ((right - left)/2) + left;
+
+ result = String.nativeCompareOrdinalIgnoreCaseWC(name, encodingDataPtr[index].webName);
+
+ if (result == 0) {
+ //We found the item, return the associated codepage.
+ return (encodingDataPtr[index].codePage);
+ } else if (result<0) {
+ //The name that we're looking for is less than our current index.
+ right = index;
+ } else {
+ //The name that we're looking for is greater than our current index
+ left = index;
+ }
+ }
+
+ //Walk the remaining elements (it'll be 3 or fewer).
+ for (; left<=right; left++) {
+ if (String.nativeCompareOrdinalIgnoreCaseWC(name, encodingDataPtr[left].webName) == 0) {
+ return (encodingDataPtr[left].codePage);
+ }
+ }
+ // The encoding name is not valid.
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("Argument_EncodingNotSupported"), name), "name");
+ }
+
+ // Return a list of all EncodingInfo objects describing all of our encodings
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal static unsafe EncodingInfo[] GetEncodings()
+ {
+ if (lastCodePageItem == 0)
+ {
+ int count;
+ for (count = 0; codePageDataPtr[count].codePage != 0; count++)
+ {
+ // Count them
+ }
+ lastCodePageItem = count;
+ }
+
+ EncodingInfo[] arrayEncodingInfo = new EncodingInfo[lastCodePageItem];
+
+ int i;
+ for (i = 0; i < lastCodePageItem; i++)
+ {
+ arrayEncodingInfo[i] = new EncodingInfo(codePageDataPtr[i].codePage, CodePageDataItem.CreateString(codePageDataPtr[i].Names, 0),
+ Environment.GetResourceString("Globalization.cp_" + codePageDataPtr[i].codePage));
+ }
+
+ return arrayEncodingInfo;
+ }
+
+ /*=================================GetCodePageFromName==========================
+ **Action: Given a encoding name, return the correct code page number for this encoding.
+ **Returns: The code page for the encoding.
+ **Arguments:
+ ** name the name of the encoding
+ **Exceptions:
+ ** ArgumentNullException if name is null.
+ ** internalGetCodePageFromName will throw ArgumentException if name is not a valid encoding name.
+ ============================================================================*/
+
+ internal static int GetCodePageFromName(String name)
+ {
+ if (name==null) {
+ throw new ArgumentNullException("name");
+ }
+ Contract.EndContractBlock();
+
+ Object codePageObj;
+
+ //
+ // The name is case-insensitive, but ToLower isn't free. Check for
+ // the code page in the given capitalization first.
+ //
+ codePageObj = hashByName[name];
+
+ if (codePageObj!=null) {
+ return ((int)codePageObj);
+ }
+
+ //Okay, we didn't find it in the hash table, try looking it up in the
+ //unmanaged data.
+ int codePage = internalGetCodePageFromName(name);
+
+ hashByName[name] = codePage;
+
+ return codePage;
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ unsafe internal static CodePageDataItem GetCodePageDataItem(int codepage) {
+ CodePageDataItem dataItem;
+
+ // We synchronize around dictionary gets/sets. There's still a possibility that two threads
+ // will create a CodePageDataItem and the second will clobber the first in the dictionary.
+ // However, that's acceptable because the contents are correct and we make no guarantees
+ // other than that.
+
+ //Look up the item in the hashtable.
+ dataItem = (CodePageDataItem)hashByCodePage[codepage];
+
+ //If we found it, return it.
+ if (dataItem!=null) {
+ return dataItem;
+ }
+
+
+ //If we didn't find it, try looking it up now.
+ //If we find it, add it to the hashtable.
+ //This is a linear search, but we probably won't be doing it very often.
+ //
+ int i = 0;
+ int data;
+ while ((data = codePageDataPtr[i].codePage) != 0) {
+ if (data == codepage) {
+ dataItem = new CodePageDataItem(i);
+ hashByCodePage[codepage] = dataItem;
+ return (dataItem);
+ }
+ i++;
+ }
+
+ //Nope, we didn't find it.
+ return null;
+ }
+
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private unsafe static extern InternalEncodingDataItem *GetEncodingData();
+
+ //
+ // Return the number of encoding data items.
+ //
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private static extern int GetNumEncodingItems();
+
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private unsafe static extern InternalCodePageDataItem* GetCodePageData();
+
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal unsafe static extern byte* nativeCreateOpenFileMapping(
+ String inSectionName, int inBytesToAllocate, out IntPtr mappedFileHandle);
+ }
+
+ /*=================================InternalEncodingDataItem==========================
+ **Action: This is used to map a encoding name to a correct code page number. By doing this,
+ ** we can get the properties of this encoding via the InternalCodePageDataItem.
+ **
+ ** We use this structure to access native data exposed by the native side.
+ ============================================================================*/
+
+ [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct InternalEncodingDataItem {
+ [SecurityCritical]
+ internal sbyte * webName;
+ internal UInt16 codePage;
+ }
+
+ /*=================================InternalCodePageDataItem==========================
+ **Action: This is used to access the properties related to a code page.
+ ** We use this structure to access native data exposed by the native side.
+ ============================================================================*/
+
+ [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct InternalCodePageDataItem {
+ internal UInt16 codePage;
+ internal UInt16 uiFamilyCodePage;
+ internal uint flags;
+ [SecurityCritical]
+ internal sbyte * Names;
+ }
+
+}
diff --git a/src/mscorlib/src/System/Globalization/GlobalizationAssembly.cs b/src/mscorlib/src/System/Globalization/GlobalizationAssembly.cs
new file mode 100644
index 0000000000..4de3fd399b
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/GlobalizationAssembly.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization {
+ using System;
+ using System.Reflection;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Security;
+ using System.Security.Permissions;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.ConstrainedExecution;
+ using System.Runtime.Versioning;
+ using System.IO;
+ using System.Diagnostics.Contracts;
+
+
+ /*=================================GlobalizationAssembly==========================
+ **
+ ** This class provides the table loading wrapper that calls GetManifestResourceStream
+ **
+ ** It used to provide an idea for sort versioning, but that proved to not work
+ **
+ ============================================================================*/
+ internal sealed class GlobalizationAssembly
+ {
+ // ----------------------------------------------------------------------------------------------------
+ //
+ // Instance data members and instance methods.
+ //
+ // ----------------------------------------------------------------------------------------------------
+ [System.Security.SecurityCritical] // auto-generated
+ internal unsafe static byte* GetGlobalizationResourceBytePtr(Assembly assembly, String tableName) {
+ Contract.Assert(assembly != null, "assembly can not be null. This should be generally the "+System.CoreLib.Name+" assembly.");
+ Contract.Assert(tableName != null, "table name can not be null");
+
+ Stream stream = assembly.GetManifestResourceStream(tableName);
+ UnmanagedMemoryStream bytesStream = stream as UnmanagedMemoryStream;
+ if (bytesStream != null) {
+ byte* bytes = bytesStream.PositionPointer;
+ if (bytes != null) {
+ return (bytes);
+ }
+ }
+
+ Contract.Assert(
+ false,
+ String.Format(
+ CultureInfo.CurrentCulture,
+ "Didn't get the resource table {0} for System.Globalization from {1}",
+ tableName,
+ assembly));
+
+ // We can not continue if we can't get the resource.
+ throw new InvalidOperationException();
+ }
+
+ }
+}
+
diff --git a/src/mscorlib/src/System/Globalization/GregorianCalendar.cs b/src/mscorlib/src/System/Globalization/GregorianCalendar.cs
new file mode 100644
index 0000000000..e540adda9f
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/GregorianCalendar.cs
@@ -0,0 +1,627 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ //
+ // N.B.:
+ // A lot of this code is directly from DateTime.cs. If you update that class,
+ // update this one as well.
+ // However, we still need these duplicated code because we will add era support
+ // in this class.
+ //
+ //
+
+ using System.Threading;
+ using System;
+ using System.Globalization;
+ using System.Runtime.Serialization;
+ using System.Diagnostics.Contracts;
+
+ // This calendar recognizes two era values:
+ // 0 CurrentEra (AD)
+ // 1 BeforeCurrentEra (BC)
+
+ [System.Runtime.InteropServices.ComVisible(true)]
+ [Serializable]
+ public class GregorianCalendar : Calendar
+ {
+ /*
+ A.D. = anno Domini
+ */
+
+ public const int ADEra = 1;
+
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+
+ //
+ // This is the max Gregorian year can be represented by DateTime class. The limitation
+ // is derived from DateTime class.
+ //
+ internal const int MaxYear = 9999;
+
+ internal GregorianCalendarTypes m_type;
+
+ internal static readonly int[] DaysToMonth365 =
+ {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
+ };
+
+ internal static readonly int[] DaysToMonth366 =
+ {
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
+ };
+
+ private static volatile Calendar s_defaultInstance;
+
+
+#region Serialization
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ if (m_type < GregorianCalendarTypes.Localized ||
+ m_type > GregorianCalendarTypes.TransliteratedFrench)
+ {
+ throw new SerializationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString(
+ "Serialization_MemberOutOfRange"),
+ "type",
+ "GregorianCalendar"));
+ }
+ }
+#endregion Serialization
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ // Return the type of the Gregorian calendar.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of GregorianCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ internal static Calendar GetDefaultInstance() {
+ if (s_defaultInstance == null) {
+ s_defaultInstance = new GregorianCalendar();
+ }
+ return (s_defaultInstance);
+ }
+
+ // Construct an instance of gregorian calendar.
+
+ public GregorianCalendar() :
+ this(GregorianCalendarTypes.Localized) {
+ }
+
+
+ public GregorianCalendar(GregorianCalendarTypes type) {
+ if ((int)type < (int)GregorianCalendarTypes.Localized || (int)type > (int)GregorianCalendarTypes.TransliteratedFrench) {
+ throw new ArgumentOutOfRangeException(
+ "type",
+ Environment.GetResourceString("ArgumentOutOfRange_Range",
+ GregorianCalendarTypes.Localized, GregorianCalendarTypes.TransliteratedFrench));
+ }
+ Contract.EndContractBlock();
+ this.m_type = type;
+ }
+
+ public virtual GregorianCalendarTypes CalendarType {
+ get {
+ return (m_type);
+ }
+
+ set {
+ VerifyWritable();
+
+ switch (value) {
+ case GregorianCalendarTypes.Localized:
+ case GregorianCalendarTypes.USEnglish:
+ case GregorianCalendarTypes.MiddleEastFrench:
+ case GregorianCalendarTypes.Arabic:
+ case GregorianCalendarTypes.TransliteratedEnglish:
+ case GregorianCalendarTypes.TransliteratedFrench:
+ m_type = value;
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException("m_type", Environment.GetResourceString("ArgumentOutOfRange_Enum"));
+ }
+ }
+ }
+
+ internal override int ID {
+ get {
+ // By returning different ID for different variations of GregorianCalendar,
+ // we can support the Transliterated Gregorian calendar.
+ // DateTimeFormatInfo will use this ID to get formatting information about
+ // the calendar.
+ return ((int)m_type);
+ }
+ }
+
+
+ // Returns a given date part of this DateTime. This method is used
+ // to compute the year, day-of-year, month, or day part.
+ internal virtual int GetDatePart(long ticks, int part)
+ {
+ // n = number of days since 1/1/0001
+ int n = (int)(ticks / TicksPerDay);
+ // y400 = number of whole 400-year periods since 1/1/0001
+ int y400 = n / DaysPer400Years;
+ // n = day number within 400-year period
+ n -= y400 * DaysPer400Years;
+ // y100 = number of whole 100-year periods within 400-year period
+ int y100 = n / DaysPer100Years;
+ // Last 100-year period has an extra day, so decrement result if 4
+ if (y100 == 4) y100 = 3;
+ // n = day number within 100-year period
+ n -= y100 * DaysPer100Years;
+ // y4 = number of whole 4-year periods within 100-year period
+ int y4 = n / DaysPer4Years;
+ // n = day number within 4-year period
+ n -= y4 * DaysPer4Years;
+ // y1 = number of whole years within 4-year period
+ int y1 = n / DaysPerYear;
+ // Last year has an extra day, so decrement result if 4
+ if (y1 == 4) y1 = 3;
+ // If year was requested, compute and return it
+ if (part == DatePartYear)
+ {
+ return (y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1);
+ }
+ // n = day number within year
+ n -= y1 * DaysPerYear;
+ // If day-of-year was requested, return it
+ if (part == DatePartDayOfYear)
+ {
+ return (n + 1);
+ }
+ // Leap year calculation looks different from IsLeapYear since y1, y4,
+ // and y100 are relative to year 1, not year 0
+ bool leapYear = (y1 == 3 && (y4 != 24 || y100 == 3));
+ int[] days = leapYear? DaysToMonth366: DaysToMonth365;
+ // All months have less than 32 days, so n >> 5 is a good conservative
+ // estimate for the month
+ int m = n >> 5 + 1;
+ // m = 1-based month number
+ while (n >= days[m]) m++;
+ // If month was requested, return it
+ if (part == DatePartMonth) return (m);
+ // Return 1-based day-of-month
+ return (n - days[m - 1] + 1);
+ }
+
+ /*=================================GetAbsoluteDate==========================
+ **Action: Gets the absolute date for the given Gregorian date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns: the absolute date
+ **Arguments:
+ ** year the Gregorian year
+ ** month the Gregorian month
+ ** day the day
+ **Exceptions:
+ ** ArgumentOutOfRangException if year, month, day value is valid.
+ **Note:
+ ** This is an internal method used by DateToTicks() and the calculations of Hijri and Hebrew calendars.
+ ** Number of Days in Prior Years (both common and leap years) +
+ ** Number of Days in Prior Months of Current Year +
+ ** Number of Days in Current Month
+ **
+ ============================================================================*/
+
+ internal static long GetAbsoluteDate(int year, int month, int day) {
+ if (year >= 1 && year <= MaxYear && month >= 1 && month <= 12)
+ {
+ int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) ? DaysToMonth366: DaysToMonth365;
+ if (day >= 1 && (day <= days[month] - days[month - 1])) {
+ int y = year - 1;
+ int absoluteDate = y * 365 + y / 4 - y / 100 + y / 400 + days[month - 1] + day - 1;
+ return (absoluteDate);
+ }
+ }
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
+ }
+
+ // Returns the tick count corresponding to the given year, month, and day.
+ // Will check the if the parameters are valid.
+ internal virtual long DateToTicks(int year, int month, int day) {
+ return (GetAbsoluteDate(year, month, day)* TicksPerDay);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000) {
+ throw new ArgumentOutOfRangeException(
+ "months",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ -120000,
+ 120000));
+ }
+ Contract.EndContractBlock();
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+ int i = m - 1 + months;
+ if (i >= 0)
+ {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else
+ {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int[] daysArray = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? DaysToMonth366: DaysToMonth365;
+ int days = (daysArray[m] - daysArray[m - 1]);
+
+ if (d > days)
+ {
+ d = days;
+ }
+ long ticks = DateToTicks(y, m, d) + time.Ticks % TicksPerDay;
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+
+ return (new DateTime(ticks));
+ }
+
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+ public override DateTime AddYears(DateTime time, int years)
+ {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+ public override int GetDayOfMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public override DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDayOfYear));
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ if (era == CurrentEra || era == ADEra) {
+ if (year < 1 || year > MaxYear) {
+ throw new ArgumentOutOfRangeException("year", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ 1, MaxYear));
+ }
+ if (month < 1 || month > 12) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
+ }
+ int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366: DaysToMonth365);
+ return (days[month] - days[month - 1]);
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ if (era == CurrentEra || era == ADEra) {
+ if (year >= 1 && year <= MaxYear) {
+ return ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 366:365);
+ }
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ MaxYear));
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ // Returns the era for the specified DateTime value.
+
+ public override int GetEra(DateTime time)
+ {
+ return (ADEra);
+ }
+
+
+ public override int[] Eras {
+ get {
+ return (new int[] {ADEra} );
+ }
+ }
+
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+ public override int GetMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ if (era == CurrentEra || era == ADEra) {
+ if (year >= 1 && year <= MaxYear)
+ {
+ return (12);
+ }
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ MaxYear));
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and 9999.
+ //
+
+ public override int GetYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartYear));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ if (month < 1 || month > 12) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ 1, 12));
+ }
+ Contract.EndContractBlock();
+
+ if (era != CurrentEra && era != ADEra)
+ {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+ if (year < 1 || year > MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ Environment.GetResourceString("ArgumentOutOfRange_Range", 1, MaxYear));
+ }
+
+ if (day < 1 || day > GetDaysInMonth(year, month)) {
+ throw new ArgumentOutOfRangeException("day", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ 1, GetDaysInMonth(year, month)));
+ }
+ if (!IsLeapYear(year)) {
+ return (false);
+ }
+ if (month == 2 && day == 29) {
+ return (true);
+ }
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetLeapMonth(int year, int era)
+ {
+ if (era != CurrentEra && era != ADEra)
+ {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+ if (year < 1 || year > MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), 1, MaxYear));
+ }
+ Contract.EndContractBlock();
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ if (era != CurrentEra && era != ADEra) {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ if (year < 1 || year > MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), 1, MaxYear));
+ }
+
+ if (month < 1 || month > 12) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Range",
+ 1, 12));
+ }
+ Contract.EndContractBlock();
+ return (false);
+
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era) {
+ if (era == CurrentEra || era == ADEra) {
+ if (year >= 1 && year <= MaxYear) {
+ return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
+ }
+
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), 1, MaxYear));
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ if (era == CurrentEra || era == ADEra) {
+ return new DateTime(year, month, day, hour, minute, second, millisecond);
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ internal override Boolean TryToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era, out DateTime result) {
+ if (era == CurrentEra || era == ADEra) {
+ return DateTime.TryCreate(year, month, day, hour, minute, second, millisecond, out result);
+ }
+ result = DateTime.MinValue;
+ return false;
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 2029;
+
+
+ public override int TwoDigitYearMax
+ {
+ get {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value < 99 || value > MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 99,
+ MaxYear));
+
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (year > MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), 1, MaxYear));
+ }
+ return (base.ToFourDigitYear(year));
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/GregorianCalendarHelper.cs b/src/mscorlib/src/System/Globalization/GregorianCalendarHelper.cs
new file mode 100644
index 0000000000..75b280d457
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/GregorianCalendarHelper.cs
@@ -0,0 +1,633 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization {
+ using System;
+ using System.Runtime.Serialization;
+ using System.Threading;
+ using System.Diagnostics.Contracts;
+
+ // Gregorian Calendars use Era Info
+ // Note: We shouldn't have to serialize this since the info doesn't change, but we have been.
+ // (We really only need the calendar #, and maybe culture)
+ [Serializable]
+ internal class EraInfo
+ {
+ internal int era; // The value of the era.
+ internal long ticks; // The time in ticks when the era starts
+ internal int yearOffset; // The offset to Gregorian year when the era starts.
+ // Gregorian Year = Era Year + yearOffset
+ // Era Year = Gregorian Year - yearOffset
+ internal int minEraYear; // Min year value in this era. Generally, this value is 1, but this may
+ // be affected by the DateTime.MinValue;
+ internal int maxEraYear; // Max year value in this era. (== the year length of the era + 1)
+
+ [OptionalField(VersionAdded = 4)]
+ internal String eraName; // The era name
+ [OptionalField(VersionAdded = 4)]
+ internal String abbrevEraName; // Abbreviated Era Name
+ [OptionalField(VersionAdded = 4)]
+ internal String englishEraName; // English era name
+
+ internal EraInfo(int era, int startYear, int startMonth, int startDay, int yearOffset, int minEraYear, int maxEraYear)
+ {
+ this.era = era;
+ this.yearOffset = yearOffset;
+ this.minEraYear = minEraYear;
+ this.maxEraYear = maxEraYear;
+ this.ticks = new DateTime(startYear, startMonth, startDay).Ticks;
+ }
+
+ internal EraInfo(int era, int startYear, int startMonth, int startDay, int yearOffset, int minEraYear, int maxEraYear,
+ String eraName, String abbrevEraName, String englishEraName)
+ {
+ this.era = era;
+ this.yearOffset = yearOffset;
+ this.minEraYear = minEraYear;
+ this.maxEraYear = maxEraYear;
+ this.ticks = new DateTime(startYear, startMonth, startDay).Ticks;
+ this.eraName = eraName;
+ this.abbrevEraName = abbrevEraName;
+ this.englishEraName = englishEraName;
+ }
+ }
+
+ // This calendar recognizes two era values:
+ // 0 CurrentEra (AD)
+ // 1 BeforeCurrentEra (BC)
+ [Serializable] internal class GregorianCalendarHelper {
+
+ // 1 tick = 100ns = 10E-7 second
+ // Number of ticks per time unit
+ internal const long TicksPerMillisecond = 10000;
+ internal const long TicksPerSecond = TicksPerMillisecond * 1000;
+ internal const long TicksPerMinute = TicksPerSecond * 60;
+ internal const long TicksPerHour = TicksPerMinute * 60;
+ internal const long TicksPerDay = TicksPerHour * 24;
+
+ // Number of milliseconds per time unit
+ internal const int MillisPerSecond = 1000;
+ internal const int MillisPerMinute = MillisPerSecond * 60;
+ internal const int MillisPerHour = MillisPerMinute * 60;
+ internal const int MillisPerDay = MillisPerHour * 24;
+
+ // Number of days in a non-leap year
+ internal const int DaysPerYear = 365;
+ // Number of days in 4 years
+ internal const int DaysPer4Years = DaysPerYear * 4 + 1;
+ // Number of days in 100 years
+ internal const int DaysPer100Years = DaysPer4Years * 25 - 1;
+ // Number of days in 400 years
+ internal const int DaysPer400Years = DaysPer100Years * 4 + 1;
+
+ // Number of days from 1/1/0001 to 1/1/10000
+ internal const int DaysTo10000 = DaysPer400Years * 25 - 366;
+
+ internal const long MaxMillis = (long)DaysTo10000 * MillisPerDay;
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+
+ //
+ // This is the max Gregorian year can be represented by DateTime class. The limitation
+ // is derived from DateTime class.
+ //
+ internal int MaxYear {
+ get {
+ return (m_maxYear);
+ }
+ }
+
+ internal static readonly int[] DaysToMonth365 =
+ {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
+ };
+
+ internal static readonly int[] DaysToMonth366 =
+ {
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
+ };
+
+ // Strictly these don't need serialized since they can be recreated from the calendar id
+ [OptionalField(VersionAdded = 1)]
+ internal int m_maxYear = 9999;
+ [OptionalField(VersionAdded = 1)]
+ internal int m_minYear;
+ internal Calendar m_Cal;
+
+ // Era information doesn't need serialized, its constant for the same calendars (ie: we can recreate it from the calendar id)
+ [OptionalField(VersionAdded = 1)]
+ internal EraInfo[] m_EraInfo;
+ [OptionalField(VersionAdded = 1)]
+ internal int[] m_eras = null;
+
+ // m_minDate is existing here just to keep the serialization compatibility.
+ // it has nothing to do with the code anymore.
+ [OptionalField(VersionAdded = 1)]
+ internal DateTime m_minDate;
+
+ // Construct an instance of gregorian calendar.
+ internal GregorianCalendarHelper(Calendar cal, EraInfo[] eraInfo) {
+ m_Cal = cal;
+ m_EraInfo = eraInfo;
+ // m_minDate is existing here just to keep the serialization compatibility.
+ // it has nothing to do with the code anymore.
+ m_minDate = m_Cal.MinSupportedDateTime;
+ m_maxYear = m_EraInfo[0].maxEraYear;
+ 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) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (era == Calendar.CurrentEra) {
+ era = m_Cal.CurrentEraValue;
+ }
+
+ 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) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ m_EraInfo[i].minEraYear,
+ m_EraInfo[i].maxEraYear));
+ }
+ return (m_EraInfo[i].yearOffset + year);
+ }
+ }
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+
+ internal bool IsValidYear(int year, int era) {
+ if (year < 0) {
+ return false;
+ }
+
+ if (era == Calendar.CurrentEra) {
+ era = m_Cal.CurrentEraValue;
+ }
+
+ 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;
+ }
+
+
+ // Returns a given date part of this DateTime. This method is used
+ // to compute the year, day-of-year, month, or day part.
+ internal virtual int GetDatePart(long ticks, int part)
+ {
+ CheckTicksRange(ticks);
+ // n = number of days since 1/1/0001
+ int n = (int)(ticks / TicksPerDay);
+ // y400 = number of whole 400-year periods since 1/1/0001
+ int y400 = n / DaysPer400Years;
+ // n = day number within 400-year period
+ n -= y400 * DaysPer400Years;
+ // y100 = number of whole 100-year periods within 400-year period
+ int y100 = n / DaysPer100Years;
+ // Last 100-year period has an extra day, so decrement result if 4
+ if (y100 == 4) y100 = 3;
+ // n = day number within 100-year period
+ n -= y100 * DaysPer100Years;
+ // y4 = number of whole 4-year periods within 100-year period
+ int y4 = n / DaysPer4Years;
+ // n = day number within 4-year period
+ n -= y4 * DaysPer4Years;
+ // y1 = number of whole years within 4-year period
+ int y1 = n / DaysPerYear;
+ // Last year has an extra day, so decrement result if 4
+ if (y1 == 4) y1 = 3;
+ // If year was requested, compute and return it
+ if (part == DatePartYear)
+ {
+ return (y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1);
+ }
+ // n = day number within year
+ n -= y1 * DaysPerYear;
+ // If day-of-year was requested, return it
+ if (part == DatePartDayOfYear)
+ {
+ return (n + 1);
+ }
+ // Leap year calculation looks different from IsLeapYear since y1, y4,
+ // and y100 are relative to year 1, not year 0
+ bool leapYear = (y1 == 3 && (y4 != 24 || y100 == 3));
+ int[] days = leapYear? DaysToMonth366: DaysToMonth365;
+ // All months have less than 32 days, so n >> 5 is a good conservative
+ // estimate for the month
+ int m = n >> 5 + 1;
+ // m = 1-based month number
+ while (n >= days[m]) m++;
+ // If month was requested, return it
+ if (part == DatePartMonth) return (m);
+ // Return 1-based day-of-month
+ return (n - days[m - 1] + 1);
+ }
+
+ /*=================================GetAbsoluteDate==========================
+ **Action: Gets the absolute date for the given Gregorian date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns: the absolute date
+ **Arguments:
+ ** year the Gregorian year
+ ** month the Gregorian month
+ ** day the day
+ **Exceptions:
+ ** ArgumentOutOfRangException if year, month, day value is valid.
+ **Note:
+ ** This is an internal method used by DateToTicks() and the calculations of Hijri and Hebrew calendars.
+ ** Number of Days in Prior Years (both common and leap years) +
+ ** Number of Days in Prior Months of Current Year +
+ ** Number of Days in Current Month
+ **
+ ============================================================================*/
+
+ internal static long GetAbsoluteDate(int year, int month, int day) {
+ if (year >= 1 && year <= 9999 && month >= 1 && month <= 12)
+ {
+ int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) ? DaysToMonth366: DaysToMonth365;
+ if (day >= 1 && (day <= days[month] - days[month - 1])) {
+ int y = year - 1;
+ int absoluteDate = y * 365 + y / 4 - y / 100 + y / 400 + days[month - 1] + day - 1;
+ return (absoluteDate);
+ }
+ }
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
+ }
+
+ // Returns the tick count corresponding to the given year, month, and day.
+ // Will check the if the parameters are valid.
+ internal static long DateToTicks(int year, int month, int day) {
+ return (GetAbsoluteDate(year, month, day)* TicksPerDay);
+ }
+
+ // Return the tick count corresponding to the given hour, minute, second.
+ // Will check the if the parameters are valid.
+ internal static long TimeToTicks(int hour, int minute, int second, int millisecond)
+ {
+ //TimeSpan.TimeToTicks is a family access function which does no error checking, so
+ //we need to put some error checking out here.
+ if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >=0 && second < 60)
+ {
+ if (millisecond < 0 || millisecond >= MillisPerSecond) {
+ throw new ArgumentOutOfRangeException(
+ "millisecond",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ MillisPerSecond - 1));
+ }
+ return (TimeSpan.TimeToTicks(hour, minute, second) + millisecond * TicksPerMillisecond);;
+ }
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadHourMinuteSecond"));
+ }
+
+
+ internal void CheckTicksRange(long ticks) {
+ if (ticks < m_Cal.MinSupportedDateTime.Ticks || ticks > m_Cal.MaxSupportedDateTime.Ticks) {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_CalendarRange"),
+ m_Cal.MinSupportedDateTime,
+ m_Cal.MaxSupportedDateTime));
+ }
+ Contract.EndContractBlock();
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+ public DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000) {
+ throw new ArgumentOutOfRangeException(
+ "months",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ -120000,
+ 120000));
+ }
+ Contract.EndContractBlock();
+ CheckTicksRange(time.Ticks);
+
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+ int i = m - 1 + months;
+ if (i >= 0)
+ {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else
+ {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int[] daysArray = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? DaysToMonth366: DaysToMonth365;
+ int days = (daysArray[m] - daysArray[m - 1]);
+
+ if (d > days)
+ {
+ d = days;
+ }
+ long ticks = DateToTicks(y, m, d) + (time.Ticks % TicksPerDay);
+ Calendar.CheckAddResult(ticks, m_Cal.MinSupportedDateTime, m_Cal.MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+ public DateTime AddYears(DateTime time, int years)
+ {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+ public int GetDayOfMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+ public DayOfWeek GetDayOfWeek(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+ return ((DayOfWeek)((time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+ public int GetDayOfYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartDayOfYear));
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+ [Pure]
+ public int GetDaysInMonth(int year, int month, int era) {
+ //
+ // Convert year/era value to Gregorain year value.
+ //
+ year = GetGregorianYear(year, era);
+ if (month < 1 || month > 12) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
+ }
+ int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366: DaysToMonth365);
+ return (days[month] - days[month - 1]);
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public int GetDaysInYear(int year, int era)
+ {
+ //
+ // Convert year/era value to Gregorain year value.
+ //
+ year = GetGregorianYear(year, era);
+ return ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 366:365);
+ }
+
+ // Returns the era for the specified DateTime value.
+ public int GetEra(DateTime time)
+ {
+ long ticks = time.Ticks;
+ // The assumption here is that m_EraInfo is listed in reverse order.
+ for (int i = 0; i < m_EraInfo.Length; i++) {
+ if (ticks >= m_EraInfo[i].ticks) {
+ return (m_EraInfo[i].era);
+ }
+ }
+ throw new ArgumentOutOfRangeException("time", Environment.GetResourceString("ArgumentOutOfRange_Era"));
+ }
+
+
+ public int[] Eras {
+ get {
+ if (m_eras == null) {
+ m_eras = new int[m_EraInfo.Length];
+ for (int i = 0; i < m_EraInfo.Length; i++) {
+ m_eras[i] = m_EraInfo[i].era;
+ }
+ }
+ return ((int[])m_eras.Clone());
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+ public int GetMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+ public int GetMonthsInYear(int year, int era)
+ {
+ year = GetGregorianYear(year, era);
+ return (12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and 9999.
+ //
+ public int GetYear(DateTime time)
+ {
+ long ticks = time.Ticks;
+ int year = GetDatePart(ticks, DatePartYear);
+ for (int i = 0; i < m_EraInfo.Length; i++) {
+ if (ticks >= m_EraInfo[i].ticks) {
+ return (year - m_EraInfo[i].yearOffset);
+ }
+ }
+ throw new ArgumentException(Environment.GetResourceString("Argument_NoEra"));
+ }
+
+ // Returns the year that match the specified Gregorian year. The returned value is an
+ // integer between 1 and 9999.
+ //
+ public int GetYear(int year, DateTime time)
+ {
+ long ticks = time.Ticks;
+ for (int i = 0; i < m_EraInfo.Length; i++) {
+ // while calculating dates with JapaneseLuniSolarCalendar, we can run into cases right after the start of the era
+ // and still belong to the month which is started in previous era. Calculating equivalent calendar date will cause
+ // using the new era info which will have the year offset equal to the year we are calculating year = m_EraInfo[i].yearOffset
+ // which will end up with zero as calendar year.
+ // We should use the previous era info instead to get the right year number. Example of such date is Feb 2nd 1989
+ if (ticks >= m_EraInfo[i].ticks && year > m_EraInfo[i].yearOffset) {
+ return (year - m_EraInfo[i].yearOffset);
+ }
+ }
+ throw new ArgumentException(Environment.GetResourceString("Argument_NoEra"));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+ public bool IsLeapDay(int year, int month, int day, int era)
+ {
+ // year/month/era checking is done in GetDaysInMonth()
+ if (day < 1 || day > GetDaysInMonth(year, month, era)) {
+ throw new ArgumentOutOfRangeException(
+ "day",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ GetDaysInMonth(year, month, era)));
+ }
+ Contract.EndContractBlock();
+
+ if (!IsLeapYear(year, era)) {
+ return (false);
+ }
+
+ if (month == 2 && day == 29) {
+ return (true);
+ }
+
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+ public int GetLeapMonth(int year, int era)
+ {
+ year = GetGregorianYear(year, era);
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+ public bool IsLeapMonth(int year, int month, int era)
+ {
+ year = GetGregorianYear(year, era);
+ if (month < 1 || month > 12) {
+ throw new ArgumentOutOfRangeException(
+ "month",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ 12));
+ }
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+ public bool IsLeapYear(int year, int era) {
+ year = GetGregorianYear(year, era);
+ return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+ public DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ year = GetGregorianYear(year, era);
+ long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second, millisecond);
+ CheckTicksRange(ticks);
+ return (new DateTime(ticks));
+ }
+
+ public virtual int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek) {
+ CheckTicksRange(time.Ticks);
+ // Use GregorianCalendar to get around the problem that the implmentation in Calendar.GetWeekOfYear()
+ // can call GetYear() that exceeds the supported range of the Gregorian-based calendars.
+ return (GregorianCalendar.GetDefaultInstance().GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+
+ public int ToFourDigitYear(int year, int twoDigitYearMax) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (year < 100) {
+ int y = year % 100;
+ return ((twoDigitYearMax/100 - ( y > twoDigitYearMax % 100 ? 1 : 0))*100 + y);
+ }
+
+ if (year < m_minYear || year > m_maxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), m_minYear, m_maxYear));
+ }
+ // If the year value is above 100, just return the year value. Don't have to do
+ // the TwoDigitYearMax comparison.
+ return (year);
+ }
+ }
+}
+
diff --git a/src/mscorlib/src/System/Globalization/GregorianCalendarTypes.cs b/src/mscorlib/src/System/Globalization/GregorianCalendarTypes.cs
new file mode 100644
index 0000000000..a9e25d06bf
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/GregorianCalendarTypes.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public enum GregorianCalendarTypes {
+ Localized = Calendar.CAL_GREGORIAN,
+ USEnglish = Calendar.CAL_GREGORIAN_US,
+ MiddleEastFrench = Calendar.CAL_GREGORIAN_ME_FRENCH,
+ Arabic = Calendar.CAL_GREGORIAN_ARABIC,
+ TransliteratedEnglish = Calendar.CAL_GREGORIAN_XLIT_ENGLISH,
+ TransliteratedFrench = Calendar.CAL_GREGORIAN_XLIT_FRENCH,
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/HebrewCalendar.cs b/src/mscorlib/src/System/Globalization/HebrewCalendar.cs
new file mode 100644
index 0000000000..3a17494001
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/HebrewCalendar.cs
@@ -0,0 +1,1084 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+ using System.Text;
+ using System.Diagnostics.Contracts;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Rules for the Hebrew calendar:
+ // - The Hebrew calendar is both a Lunar (months) and Solar (years)
+ // calendar, but allows for a week of seven days.
+ // - Days begin at sunset.
+ // - Leap Years occur in the 3, 6, 8, 11, 14, 17, & 19th years of a
+ // 19-year cycle. Year = leap iff ((7y+1) mod 19 < 7).
+ // - There are 12 months in a common year and 13 months in a leap year.
+ // - In a common year, the 6th month, Adar, has 29 days. In a leap
+ // year, the 6th month, Adar I, has 30 days and the leap month,
+ // Adar II, has 29 days.
+ // - Common years have 353-355 days. Leap years have 383-385 days.
+ // - The Hebrew new year (Rosh HaShanah) begins on the 1st of Tishri,
+ // the 7th month in the list below.
+ // - The new year may not begin on Sunday, Wednesday, or Friday.
+ // - If the new year would fall on a Tuesday and the conjunction of
+ // the following year were at midday or later, the new year is
+ // delayed until Thursday.
+ // - If the new year would fall on a Monday after a leap year, the
+ // new year is delayed until Tuesday.
+ // - The length of the 8th and 9th months vary from year to year,
+ // depending on the overall length of the year.
+ // - The length of a year is determined by the dates of the new
+ // years (Tishri 1) preceding and following the year in question.
+ // - The 2th month is long (30 days) if the year has 355 or 385 days.
+ // - The 3th month is short (29 days) if the year has 353 or 383 days.
+ // - The Hebrew months are:
+ // 1. Tishri (30 days)
+ // 2. Heshvan (29 or 30 days)
+ // 3. Kislev (29 or 30 days)
+ // 4. Teveth (29 days)
+ // 5. Shevat (30 days)
+ // 6. Adar I (30 days)
+ // 7. Adar {II} (29 days, this only exists if that year is a leap year)
+ // 8. Nisan (30 days)
+ // 9. Iyyar (29 days)
+ // 10. Sivan (30 days)
+ // 11. Tammuz (29 days)
+ // 12. Av (30 days)
+ // 13. Elul (29 days)
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1583/01/01 2239/09/29
+ ** Hebrew 5343/04/07 5999/13/29
+ */
+
+// Includes CHebrew implemetation;i.e All the code necessary for converting
+// Gregorian to Hebrew Lunar from 1583 to 2239.
+
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public class HebrewCalendar : Calendar {
+
+
+ public static readonly int HebrewEra = 1;
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+ internal const int DatePartDayOfWeek = 4;
+
+ //
+ // Hebrew Translation Table.
+ //
+ // This table is used to get the following Hebrew calendar information for a
+ // given Gregorian year:
+ // 1. The day of the Hebrew month corresponding to Gregorian January 1st
+ // for a given Gregorian year.
+ // 2. The month of the Hebrew month corresponding to Gregorian January 1st
+ // for a given Gregorian year.
+ // The information is not directly in the table. Instead, the info is decoded
+ // by special values (numbers above 29 and below 1).
+ // 3. The type of the Hebrew year for a given Gregorian year.
+ //
+
+ /*
+ More notes:
+
+ This table includes 2 numbers for each year.
+ The offset into the table determines the year. (offset 0 is Gregorian year 1500)
+ 1st number determines the day of the Hebrew month coresponeds to January 1st.
+ 2nd number determines the type of the Hebrew year. (the type determines how
+ many days are there in the year.)
+
+ normal years : 1 = 353 days 2 = 354 days 3 = 355 days.
+ Leap years : 4 = 383 5 384 6 = 385 days.
+
+ A 99 means the year is not supported for translation.
+ for convenience the table was defined for 750 year,
+ but only 640 years are supported. (from 1583 to 2239)
+ the years before 1582 (starting of Georgian calander)
+ and after 2239, are filled with 99.
+
+ Greogrian January 1st falls usually in Tevet (4th month). Tevet has always 29 days.
+ That's why, there no nead to specify the lunar month in the table.
+ There are exceptions, these are coded by giving numbers above 29 and below 1.
+ Actual decoding is takenig place whenever fetching information from the table.
+ The function for decoding is in GetLunarMonthDay().
+
+ Example:
+ The data for 2000 - 2005 A.D. is:
+
+ 23,6,6,1,17,2,27,6,7,3, // 2000 - 2004
+
+ For year 2000, we know it has a Hebrew year type 6, which means it has 385 days.
+ And 1/1/2000 A.D. is Hebrew year 5760, 23rd day of 4th month.
+ */
+
+ //
+ // Jewish Era in use today is dated from the supposed year of the
+ // Creation with its beginning in 3761 B.C.
+ //
+
+ // The Hebrew year of Gregorian 1st year AD.
+ // 0001/01/01 AD is Hebrew 3760/01/01
+ private const int HebrewYearOf1AD = 3760;
+
+ // The first Gregorian year in HebrewTable.
+ private const int FirstGregorianTableYear = 1583; // == Hebrew Year 5343
+ // The last Gregorian year in HebrewTable.
+ private const int LastGregorianTableYear = 2239; // == Hebrew Year 5999
+ private const int TABLESIZE = (LastGregorianTableYear-FirstGregorianTableYear);
+
+ private const int MinHebrewYear = HebrewYearOf1AD + FirstGregorianTableYear; // == 5343
+ private const int MaxHebrewYear = HebrewYearOf1AD + LastGregorianTableYear; // == 5999
+
+ private static readonly int[] HebrewTable = {
+ 7,3,17,3, // 1583-1584 (Hebrew year: 5343 - 5344)
+ 0,4,11,2,21,6,1,3,13,2, // 1585-1589
+ 25,4,5,3,16,2,27,6,9,1, // 1590-1594
+ 20,2,0,6,11,3,23,4,4,2, // 1595-1599
+ 14,3,27,4,8,2,18,3,28,6, // 1600
+ 11,1,22,5,2,3,12,3,25,4, // 1605
+ 6,2,16,3,26,6,8,2,20,1, // 1610
+ 0,6,11,2,24,4,4,3,15,2, // 1615
+ 25,6,8,1,19,2,29,6,9,3, // 1620
+ 22,4,3,2,13,3,25,4,6,3, // 1625
+ 17,2,27,6,7,3,19,2,31,4, // 1630
+ 11,3,23,4,5,2,15,3,25,6, // 1635
+ 6,2,19,1,29,6,10,2,22,4, // 1640
+ 3,3,14,2,24,6,6,1,17,3, // 1645
+ 28,5,8,3,20,1,32,5,12,3, // 1650
+ 22,6,4,1,16,2,26,6,6,3, // 1655
+ 17,2,0,4,10,3,22,4,3,2, // 1660
+ 14,3,24,6,5,2,17,1,28,6, // 1665
+ 9,2,19,3,31,4,13,2,23,6, // 1670
+ 3,3,15,1,27,5,7,3,17,3, // 1675
+ 29,4,11,2,21,6,3,1,14,2, // 1680
+ 25,6,5,3,16,2,28,4,9,3, // 1685
+ 20,2,0,6,12,1,23,6,4,2, // 1690
+ 14,3,26,4,8,2,18,3,0,4, // 1695
+ 10,3,21,5,1,3,13,1,24,5, // 1700
+ 5,3,15,3,27,4,8,2,19,3, // 1705
+ 29,6,10,2,22,4,3,3,14,2, // 1710
+ 26,4,6,3,18,2,28,6,10,1, // 1715
+ 20,6,2,2,12,3,24,4,5,2, // 1720
+ 16,3,28,4,8,3,19,2,0,6, // 1725
+ 12,1,23,5,3,3,14,3,26,4, // 1730
+ 7,2,17,3,28,6,9,2,21,4, // 1735
+ 1,3,13,2,25,4,5,3,16,2, // 1740
+ 27,6,9,1,19,3,0,5,11,3, // 1745
+ 23,4,4,2,14,3,25,6,7,1, // 1750
+ 18,2,28,6,9,3,21,4,2,2, // 1755
+ 12,3,25,4,6,2,16,3,26,6, // 1760
+ 8,2,20,1,0,6,11,2,22,6, // 1765
+ 4,1,15,2,25,6,6,3,18,1, // 1770
+ 29,5,9,3,22,4,2,3,13,2, // 1775
+ 23,6,4,3,15,2,27,4,7,3, // 1780
+ 19,2,31,4,11,3,21,6,3,2, // 1785
+ 15,1,25,6,6,2,17,3,29,4, // 1790
+ 10,2,20,6,3,1,13,3,24,5, // 1795
+ 4,3,16,1,27,5,7,3,17,3, // 1800
+ 0,4,11,2,21,6,1,3,13,2, // 1805
+ 25,4,5,3,16,2,29,4,9,3, // 1810
+ 19,6,30,2,13,1,23,6,4,2, // 1815
+ 14,3,27,4,8,2,18,3,0,4, // 1820
+ 11,3,22,5,2,3,14,1,26,5, // 1825
+ 6,3,16,3,28,4,10,2,20,6, // 1830
+ 30,3,11,2,24,4,4,3,15,2, // 1835
+ 25,6,8,1,19,2,29,6,9,3, // 1840
+ 22,4,3,2,13,3,25,4,7,2, // 1845
+ 17,3,27,6,9,1,21,5,1,3, // 1850
+ 11,3,23,4,5,2,15,3,25,6, // 1855
+ 6,2,19,1,29,6,10,2,22,4, // 1860
+ 3,3,14,2,24,6,6,1,18,2, // 1865
+ 28,6,8,3,20,4,2,2,12,3, // 1870
+ 24,4,4,3,16,2,26,6,6,3, // 1875
+ 17,2,0,4,10,3,22,4,3,2, // 1880
+ 14,3,24,6,5,2,17,1,28,6, // 1885
+ 9,2,21,4,1,3,13,2,23,6, // 1890
+ 5,1,15,3,27,5,7,3,19,1, // 1895
+ 0,5,10,3,22,4,2,3,13,2, // 1900
+ 24,6,4,3,15,2,27,4,8,3, // 1905
+ 20,4,1,2,11,3,22,6,3,2, // 1910
+ 15,1,25,6,7,2,17,3,29,4, // 1915
+ 10,2,21,6,1,3,13,1,24,5, // 1920
+ 5,3,15,3,27,4,8,2,19,6, // 1925
+ 1,1,12,2,22,6,3,3,14,2, // 1930
+ 26,4,6,3,18,2,28,6,10,1, // 1935
+ 20,6,2,2,12,3,24,4,5,2, // 1940
+ 16,3,28,4,9,2,19,6,30,3, // 1945
+ 12,1,23,5,3,3,14,3,26,4, // 1950
+ 7,2,17,3,28,6,9,2,21,4, // 1955
+ 1,3,13,2,25,4,5,3,16,2, // 1960
+ 27,6,9,1,19,6,30,2,11,3, // 1965
+ 23,4,4,2,14,3,27,4,7,3, // 1970
+ 18,2,28,6,11,1,22,5,2,3, // 1975
+ 12,3,25,4,6,2,16,3,26,6, // 1980
+ 8,2,20,4,30,3,11,2,24,4, // 1985
+ 4,3,15,2,25,6,8,1,18,3, // 1990
+ 29,5,9,3,22,4,3,2,13,3, // 1995
+ 23,6,6,1,17,2,27,6,7,3, // 2000 - 2004
+ 20,4,1,2,11,3,23,4,5,2, // 2005 - 2009
+ 15,3,25,6,6,2,19,1,29,6, // 2010
+ 10,2,20,6,3,1,14,2,24,6, // 2015
+ 4,3,17,1,28,5,8,3,20,4, // 2020
+ 1,3,12,2,22,6,2,3,14,2, // 2025
+ 26,4,6,3,17,2,0,4,10,3, // 2030
+ 20,6,1,2,14,1,24,6,5,2, // 2035
+ 15,3,28,4,9,2,19,6,1,1, // 2040
+ 12,3,23,5,3,3,15,1,27,5, // 2045
+ 7,3,17,3,29,4,11,2,21,6, // 2050
+ 1,3,12,2,25,4,5,3,16,2, // 2055
+ 28,4,9,3,19,6,30,2,12,1, // 2060
+ 23,6,4,2,14,3,26,4,8,2, // 2065
+ 18,3,0,4,10,3,22,5,2,3, // 2070
+ 14,1,25,5,6,3,16,3,28,4, // 2075
+ 9,2,20,6,30,3,11,2,23,4, // 2080
+ 4,3,15,2,27,4,7,3,19,2, // 2085
+ 29,6,11,1,21,6,3,2,13,3, // 2090
+ 25,4,6,2,17,3,27,6,9,1, // 2095
+ 20,5,30,3,10,3,22,4,3,2, // 2100
+ 14,3,24,6,5,2,17,1,28,6, // 2105
+ 9,2,21,4,1,3,13,2,23,6, // 2110
+ 5,1,16,2,27,6,7,3,19,4, // 2115
+ 30,2,11,3,23,4,3,3,14,2, // 2120
+ 25,6,5,3,16,2,28,4,9,3, // 2125
+ 21,4,2,2,12,3,23,6,4,2, // 2130
+ 16,1,26,6,8,2,20,4,30,3, // 2135
+ 11,2,22,6,4,1,14,3,25,5, // 2140
+ 6,3,18,1,29,5,9,3,22,4, // 2145
+ 2,3,13,2,23,6,4,3,15,2, // 2150
+ 27,4,7,3,20,4,1,2,11,3, // 2155
+ 21,6,3,2,15,1,25,6,6,2, // 2160
+ 17,3,29,4,10,2,20,6,3,1, // 2165
+ 13,3,24,5,4,3,17,1,28,5, // 2170
+ 8,3,18,6,1,1,12,2,22,6, // 2175
+ 2,3,14,2,26,4,6,3,17,2, // 2180
+ 28,6,10,1,20,6,1,2,12,3, // 2185
+ 24,4,5,2,15,3,28,4,9,2, // 2190
+ 19,6,33,3,12,1,23,5,3,3, // 2195
+ 13,3,25,4,6,2,16,3,26,6, // 2200
+ 8,2,20,4,30,3,11,2,24,4, // 2205
+ 4,3,15,2,25,6,8,1,18,6, // 2210
+ 33,2,9,3,22,4,3,2,13,3, // 2215
+ 25,4,6,3,17,2,27,6,9,1, // 2220
+ 21,5,1,3,11,3,23,4,5,2, // 2225
+ 15,3,25,6,6,2,19,4,33,3, // 2230
+ 10,2,22,4,3,3,14,2,24,6, // 2235
+ 6,1 // 2240 (Hebrew year: 6000)
+ };
+
+ //
+ // The lunar calendar has 6 different variations of month lengths
+ // within a year.
+ //
+ private static readonly int[,] LunarMonthLen = {
+ {0,00,00,00,00,00,00,00,00,00,00,00,00,0},
+ {0,30,29,29,29,30,29,30,29,30,29,30,29,0}, // 3 common year variations
+ {0,30,29,30,29,30,29,30,29,30,29,30,29,0},
+ {0,30,30,30,29,30,29,30,29,30,29,30,29,0},
+ {0,30,29,29,29,30,30,29,30,29,30,29,30,29}, // 3 leap year variations
+ {0,30,29,30,29,30,30,29,30,29,30,29,30,29},
+ {0,30,30,30,29,30,30,29,30,29,30,29,30,29}
+ };
+
+ //internal static Calendar m_defaultInstance;
+
+ internal static readonly DateTime calendarMinValue = new DateTime(1583, 1, 1);
+ // Gregorian 2239/9/29 = Hebrew 5999/13/29 (last day in Hebrew year 5999).
+ // We can only format/parse Hebrew numbers up to 999, so we limit the max range to Hebrew year 5999.
+ internal static readonly DateTime calendarMaxValue = new DateTime((new DateTime(2239, 9, 29, 23, 59, 59, 999)).Ticks + 9999);
+
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (calendarMaxValue);
+ }
+ }
+
+
+ // Return the type of the Hebrew calendar.
+ //
+
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.LunisolarCalendar;
+ }
+ }
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of HebrewCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ /*
+ internal static Calendar GetDefaultInstance() {
+ if (m_defaultInstance == null) {
+ m_defaultInstance = new HebrewCalendar();
+ }
+ return (m_defaultInstance);
+ }
+ */
+
+
+ // Construct an instance of gregorian calendar.
+
+ public HebrewCalendar() {
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_HEBREW);
+ }
+ }
+
+
+ /*=================================CheckHebrewYearValue==========================
+ **Action: Check if the Hebrew year value is supported in this class.
+ **Returns: None.
+ **Arguments: y Hebrew year value
+ ** ear Hebrew era value
+ **Exceptions: ArgumentOutOfRange_Range if the year value is not supported.
+ **Note:
+ ** We use a table for the Hebrew calendar calculation, so the year supported is limited.
+ ============================================================================*/
+
+ static private void CheckHebrewYearValue(int y, int era, String varName) {
+ CheckEraRange(era);
+ if (y > MaxHebrewYear || y < MinHebrewYear) {
+ throw new ArgumentOutOfRangeException(
+ varName,
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ MinHebrewYear,
+ MaxHebrewYear));
+ }
+ }
+
+ /*=================================CheckHebrewMonthValue==========================
+ **Action: Check if the Hebrew month value is valid.
+ **Returns: None.
+ **Arguments: year Hebrew year value
+ ** month Hebrew month value
+ **Exceptions: ArgumentOutOfRange_Range if the month value is not valid.
+ **Note:
+ ** Call CheckHebrewYearValue() before calling this to verify the year value is supported.
+ ============================================================================*/
+
+ private void CheckHebrewMonthValue(int year, int month, int era) {
+ int monthsInYear = GetMonthsInYear(year, era);
+ if (month < 1 || month > monthsInYear) {
+ throw new ArgumentOutOfRangeException(
+ "month",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ monthsInYear));
+ }
+ }
+
+ /*=================================CheckHebrewDayValue==========================
+ **Action: Check if the Hebrew day value is valid.
+ **Returns: None.
+ **Arguments: year Hebrew year value
+ ** month Hebrew month value
+ ** day Hebrew day value.
+ **Exceptions: ArgumentOutOfRange_Range if the day value is not valid.
+ **Note:
+ ** Call CheckHebrewYearValue()/CheckHebrewMonthValue() before calling this to verify the year/month values are valid.
+ ============================================================================*/
+
+ private void CheckHebrewDayValue(int year, int month, int day, int era) {
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth) {
+ throw new ArgumentOutOfRangeException(
+ "day",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ daysInMonth));
+ }
+ }
+
+ static internal void CheckEraRange(int era) {
+ if (era != CurrentEra && era != HebrewEra) {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+ }
+
+ static private void CheckTicksRange(long ticks) {
+ if (ticks < calendarMinValue.Ticks || ticks > calendarMaxValue.Ticks) {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ // Print out the date in Gregorian using InvariantCulture since the DateTime is based on GreograinCalendar.
+ String.Format(
+ CultureInfo.InvariantCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_CalendarRange"),
+ calendarMinValue,
+ calendarMaxValue));
+ }
+ }
+
+ static internal int GetResult(__DateBuffer result, int part) {
+ switch (part) {
+ case DatePartYear:
+ return (result.year);
+ case DatePartMonth:
+ return (result.month);
+ case DatePartDay:
+ return (result.day);
+ }
+
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_DateTimeParsing"));
+ }
+
+ /*=================================GetLunarMonthDay==========================
+ **Action: Using the Hebrew table (HebrewTable) to get the Hebrew month/day value for Gregorian January 1st
+ ** in a given Gregorian year.
+ ** Greogrian January 1st falls usually in Tevet (4th month). Tevet has always 29 days.
+ ** That's why, there no nead to specify the lunar month in the table. There are exceptions, and these
+ ** are coded by giving numbers above 29 and below 1.
+ ** Actual decoding is takenig place in the switch statement below.
+ **Returns:
+ ** The Hebrew year type. The value is from 1 to 6.
+ ** normal years : 1 = 353 days 2 = 354 days 3 = 355 days.
+ ** Leap years : 4 = 383 5 384 6 = 385 days.
+ **Arguments:
+ ** gregorianYear The year value in Gregorian calendar. The value should be between 1500 and 2239.
+ ** lunarDate Object to take the result of the Hebrew year/month/day.
+ **Exceptions:
+ ============================================================================*/
+
+ static internal int GetLunarMonthDay(int gregorianYear, __DateBuffer lunarDate) {
+ //
+ // Get the offset into the LunarMonthLen array and the lunar day
+ // for January 1st.
+ //
+ int index = gregorianYear - FirstGregorianTableYear;
+ if (index < 0 || index > TABLESIZE) {
+ throw new ArgumentOutOfRangeException("gregorianYear");
+ }
+
+ index *= 2;
+ lunarDate.day = HebrewTable[index];
+
+ // Get the type of the year. The value is from 1 to 6
+ int LunarYearType = HebrewTable[index + 1];
+
+ //
+ // Get the Lunar Month.
+ //
+ switch (lunarDate.day) {
+ case ( 0 ) : // 1/1 is on Shvat 1
+ lunarDate.month = 5;
+ lunarDate.day = 1;
+ break;
+ case ( 30 ) : // 1/1 is on Kislev 30
+ lunarDate.month = 3;
+ break;
+ case ( 31 ) : // 1/1 is on Shvat 2
+ lunarDate.month = 5;
+ lunarDate.day = 2;
+ break;
+ case ( 32 ) : // 1/1 is on Shvat 3
+ lunarDate.month = 5;
+ lunarDate.day = 3;
+ break;
+ case ( 33 ) : // 1/1 is on Kislev 29
+ lunarDate.month = 3;
+ lunarDate.day = 29;
+ break;
+ default : // 1/1 is on Tevet (This is the general case)
+ lunarDate.month = 4;
+ break;
+ }
+ return (LunarYearType);
+ }
+
+ // Returns a given date part of this DateTime. This method is used
+ // to compute the year, day-of-year, month, or day part.
+
+ internal virtual int GetDatePart(long ticks, int part) {
+ // The Gregorian year, month, day value for ticks.
+ int gregorianYear, gregorianMonth, gregorianDay;
+ int hebrewYearType; // lunar year type
+ long AbsoluteDate; // absolute date - absolute date 1/1/1600
+
+ //
+ // Make sure we have a valid Gregorian date that will fit into our
+ // Hebrew conversion limits.
+ //
+ CheckTicksRange(ticks);
+
+ DateTime time = new DateTime(ticks);
+
+ //
+ // Save the Gregorian date values.
+ //
+ gregorianYear = time.Year;
+ gregorianMonth = time.Month;
+ gregorianDay = time.Day;
+
+ __DateBuffer lunarDate = new __DateBuffer(); // lunar month and day for Jan 1
+
+ // From the table looking-up value of HebrewTable[index] (stored in lunarDate.day), we get the the
+ // lunar month and lunar day where the Gregorian date 1/1 falls.
+ lunarDate.year = gregorianYear + HebrewYearOf1AD;
+ hebrewYearType = GetLunarMonthDay(gregorianYear, lunarDate);
+
+ // This is the buffer used to store the result Hebrew date.
+ __DateBuffer result = new __DateBuffer();
+
+ //
+ // Store the values for the start of the new year - 1/1.
+ //
+ result.year = lunarDate.year;
+ result.month = lunarDate.month;
+ result.day = lunarDate.day;
+
+ //
+ // Get the absolute date from 1/1/1600.
+ //
+ AbsoluteDate = GregorianCalendar.GetAbsoluteDate(gregorianYear, gregorianMonth, gregorianDay);
+
+ //
+ // If the requested date was 1/1, then we're done.
+ //
+ if ((gregorianMonth == 1) && (gregorianDay == 1)) {
+ return (GetResult(result, part));
+ }
+
+ //
+ // Calculate the number of days between 1/1 and the requested date.
+ //
+ long NumDays; // number of days since 1/1
+ NumDays = AbsoluteDate - GregorianCalendar.GetAbsoluteDate(gregorianYear, 1, 1);
+
+ //
+ // If the requested date is within the current lunar month, then
+ // we're done.
+ //
+ if ((NumDays + (long)lunarDate.day) <= (long)(LunarMonthLen[hebrewYearType, lunarDate.month])) {
+ result.day += (int)NumDays;
+ return (GetResult(result, part));
+ }
+
+ //
+ // Adjust for the current partial month.
+ //
+ result.month++;
+ result.day = 1;
+
+ //
+ // Adjust the Lunar Month and Year (if necessary) based on the number
+ // of days between 1/1 and the requested date.
+ //
+ // Assumes Jan 1 can never translate to the last Lunar month, which
+ // is true.
+ //
+ NumDays -= (long)(LunarMonthLen[hebrewYearType, lunarDate.month] - lunarDate.day);
+ Contract.Assert(NumDays >= 1, "NumDays >= 1");
+
+ // If NumDays is 1, then we are done. Otherwise, find the correct Hebrew month
+ // and day.
+ if (NumDays > 1) {
+ //
+ // See if we're on the correct Lunar month.
+ //
+ while (NumDays > (long)(LunarMonthLen[hebrewYearType, result.month])) {
+ //
+ // Adjust the number of days and move to the next month.
+ //
+ NumDays -= (long)(LunarMonthLen[hebrewYearType, result.month++]);
+
+ //
+ // See if we need to adjust the Year.
+ // Must handle both 12 and 13 month years.
+ //
+ if ((result.month > 13) || (LunarMonthLen[hebrewYearType, result.month] == 0)) {
+ //
+ // Adjust the Year.
+ //
+ result.year++;
+ hebrewYearType = HebrewTable[(gregorianYear + 1 - FirstGregorianTableYear) * 2 + 1];
+
+ //
+ // Adjust the Month.
+ //
+ result.month = 1;
+ }
+ }
+ //
+ // Found the right Lunar month.
+ //
+ result.day += (int)(NumDays - 1);
+ }
+ return (GetResult(result, part));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+ public override DateTime AddMonths(DateTime time, int months) {
+ try {
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+
+
+ int monthsInYear;
+ int i;
+ if (months >= 0) {
+ i = m + months;
+ while (i > (monthsInYear = GetMonthsInYear(y, CurrentEra))) {
+ y++;
+ i -= monthsInYear;
+ }
+ } else {
+ if ((i = m + months) <= 0) {
+ months = -months;
+ months -= m;
+ y--;
+
+ while (months > (monthsInYear = GetMonthsInYear(y, CurrentEra))) {
+ y--;
+ months -= monthsInYear;
+ }
+ monthsInYear = GetMonthsInYear(y, CurrentEra);
+ i = monthsInYear - months;
+ }
+ }
+
+ int days = GetDaysInMonth(y, i);
+ if (d > days) {
+ d = days;
+ }
+ return (new DateTime(ToDateTime(y, i, d, 0, 0, 0, 0).Ticks + (time.Ticks % TicksPerDay)));
+ }
+ // We expect ArgumentException and ArgumentOutOfRangeException (which is subclass of ArgumentException)
+ // If exception is thrown in the calls above, we are out of the supported range of this calendar.
+ catch (ArgumentException)
+ {
+ throw new ArgumentOutOfRangeException(
+ "months",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_AddValue")));
+ }
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+ public override DateTime AddYears(DateTime time, int years) {
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+
+ y += years;
+ CheckHebrewYearValue(y, Calendar.CurrentEra, "years");
+
+ int months = GetMonthsInYear(y, CurrentEra);
+ if (m > months) {
+ m = months;
+ }
+
+ int days = GetDaysInMonth(y, m);
+ if (d > days) {
+ d = days;
+ }
+
+ long ticks = ToDateTime(y, m, d, 0, 0, 0, 0).Ticks + (time.Ticks % TicksPerDay);
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+ public override int GetDayOfMonth(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ // If we calculate back, the Hebrew day of week for Gregorian 0001/1/1 is Monday (1).
+ // Therfore, the fomula is:
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ static internal int GetHebrewYearType(int year, int era) {
+ CheckHebrewYearValue(year, era, "year");
+ // The HebrewTable is indexed by Gregorian year and starts from FirstGregorianYear.
+ // So we need to convert year (Hebrew year value) to Gregorian Year below.
+ return (HebrewTable[(year - HebrewYearOf1AD - FirstGregorianTableYear) * 2 + 1]);
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+ public override int GetDayOfYear(DateTime time) {
+ // Get Hebrew year value of the specified time.
+ int year = GetYear(time);
+ DateTime beginOfYearDate;
+ if (year == 5343)
+ {
+ // Gregorian 1583/01/01 corresponds to Hebrew 5343/04/07 (MinSupportedDateTime)
+ // To figure out the Gregorian date associated with Hebrew 5343/01/01, we need to
+ // count the days from 5343/01/01 to 5343/04/07 and subtract that from Gregorian
+ // 1583/01/01.
+ // 1. Tishri (30 days)
+ // 2. Heshvan (30 days since 5343 has 355 days)
+ // 3. Kislev (30 days since 5343 has 355 days)
+ // 96 days to get from 5343/01/01 to 5343/04/07
+ // Gregorian 1583/01/01 - 96 days = 1582/9/27
+
+ // the beginning of Hebrew year 5343 corresponds to Gregorian September 27, 1582.
+ beginOfYearDate = new DateTime(1582, 9, 27);
+ }
+ else
+ {
+ // following line will fail when year is 5343 (first supported year)
+ beginOfYearDate = ToDateTime(year, 1, 1, 0, 0, 0, 0, CurrentEra);
+ }
+ return ((int)((time.Ticks - beginOfYearDate.Ticks) / TicksPerDay) + 1);
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ CheckEraRange(era);
+ int hebrewYearType = GetHebrewYearType(year, era);
+ CheckHebrewMonthValue(year, month, era);
+
+ Contract.Assert(hebrewYearType>= 1 && hebrewYearType <= 6,
+ "hebrewYearType should be from 1 to 6, but now hebrewYearType = " + hebrewYearType + " for hebrew year " + year);
+ int monthDays = LunarMonthLen[hebrewYearType, month];
+ if (monthDays == 0) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
+ }
+ return (monthDays);
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era) {
+ CheckEraRange(era);
+ // normal years : 1 = 353 days 2 = 354 days 3 = 355 days.
+ // Leap years : 4 = 383 5 384 6 = 385 days.
+
+ // LunarYearType is from 1 to 6
+ int LunarYearType = GetHebrewYearType(year, era);
+ if (LunarYearType < 4) {
+ // common year: LunarYearType = 1, 2, 3
+ return (352 + LunarYearType);
+ }
+ return (382 + (LunarYearType - 3));
+ }
+
+ // Returns the era for the specified DateTime value.
+
+ public override int GetEra(DateTime time) {
+
+ return (HebrewEra);
+ }
+
+
+ public override int[] Eras {
+ get {
+ return (new int[] {HebrewEra});
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+ public override int GetMonth(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public override int GetMonthsInYear(int year, int era) {
+ return (IsLeapYear(year, era) ? 13 : 12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and 9999.
+ //
+
+ public override int GetYear(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartYear));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public override bool IsLeapDay(int year, int month, int day, int era) {
+ if (IsLeapMonth(year, month, era)) {
+ // Every day in a leap month is a leap day.
+ CheckHebrewDayValue(year, month, day, era);
+ return (true);
+ } else if (IsLeapYear(year, Calendar.CurrentEra)) {
+ // There is an additional day in the 6th month in the leap year (the extra day is the 30th day in the 6th month),
+ // so we should return true for 6/30 if that's in a leap year.
+ if (month == 6 && day == 30) {
+ return (true);
+ }
+ }
+ CheckHebrewDayValue(year, month, day, era);
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ // Year/era values are checked in IsLeapYear().
+ if (IsLeapYear(year, era))
+ {
+ // The 7th month in a leap year is a leap month.
+ return (7);
+ }
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public override bool IsLeapMonth(int year, int month, int era) {
+ // Year/era values are checked in IsLeapYear().
+ bool isLeapYear = IsLeapYear(year, era);
+ CheckHebrewMonthValue(year, month, era);
+ // The 7th month in a leap year is a leap month.
+ if (isLeapYear) {
+ if (month == 7) {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era) {
+ CheckHebrewYearValue(year, era, "year");
+ return (((7 * (long)year + 1) % 19) < 7);
+ }
+
+ // (month1, day1) - (month2, day2)
+ static int GetDayDifference(int lunarYearType, int month1, int day1, int month2, int day2) {
+ if (month1 == month2) {
+ return (day1 - day2);
+ }
+
+ // Make sure that (month1, day1) < (month2, day2)
+ bool swap = (month1 > month2);
+ if (swap) {
+ // (month1, day1) < (month2, day2). Swap the values.
+ // The result will be a negative number.
+ int tempMonth, tempDay;
+ tempMonth = month1; tempDay = day1;
+ month1 = month2; day1 = day2;
+ month2 = tempMonth; day2 = tempDay;
+ }
+
+ // Get the number of days from (month1,day1) to (month1, end of month1)
+ int days = LunarMonthLen[lunarYearType, month1] - day1;
+
+ // Move to next month.
+ month1++;
+
+ // Add up the days.
+ while (month1 < month2) {
+ days += LunarMonthLen[lunarYearType, month1++];
+ }
+ days += day2;
+
+ return (swap ? days : -days);
+ }
+
+ /*=================================HebrewToGregorian==========================
+ **Action: Convert Hebrew date to Gregorian date.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ** The algorithm is like this:
+ ** The hebrew year has an offset to the Gregorian year, so we can guess the Gregorian year for
+ ** the specified Hebrew year. That is, GreogrianYear = HebrewYear - FirstHebrewYearOf1AD.
+ **
+ ** From the Gregorian year and HebrewTable, we can get the Hebrew month/day value
+ ** of the Gregorian date January 1st. Let's call this month/day value [hebrewDateForJan1]
+ **
+ ** If the requested Hebrew month/day is less than [hebrewDateForJan1], we know the result
+ ** Gregorian date falls in previous year. So we decrease the Gregorian year value, and
+ ** retrieve the Hebrew month/day value of the Gregorian date january 1st again.
+ **
+ ** Now, we get the answer of the Gregorian year.
+ **
+ ** The next step is to get the number of days between the requested Hebrew month/day
+ ** and [hebrewDateForJan1]. When we get that, we can create the DateTime by adding/subtracting
+ ** the ticks value of the number of days.
+ **
+ ============================================================================*/
+
+
+ static DateTime HebrewToGregorian(int hebrewYear, int hebrewMonth, int hebrewDay, int hour, int minute, int second, int millisecond) {
+ // Get the rough Gregorian year for the specified hebrewYear.
+ //
+ int gregorianYear = hebrewYear - HebrewYearOf1AD;
+
+ __DateBuffer hebrewDateOfJan1 = new __DateBuffer(); // year value is unused.
+ int lunarYearType = GetLunarMonthDay(gregorianYear, hebrewDateOfJan1);
+
+ if ((hebrewMonth == hebrewDateOfJan1.month) && (hebrewDay == hebrewDateOfJan1.day)) {
+ return (new DateTime(gregorianYear, 1, 1, hour, minute, second, millisecond));
+ }
+
+ int days = GetDayDifference(lunarYearType, hebrewMonth, hebrewDay, hebrewDateOfJan1.month, hebrewDateOfJan1.day);
+
+ DateTime gregorianNewYear = new DateTime(gregorianYear, 1, 1);
+ return (new DateTime(gregorianNewYear.Ticks + days * TicksPerDay
+ + TimeToTicks(hour, minute, second, millisecond)));
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ CheckHebrewYearValue(year, era, "year");
+ CheckHebrewMonthValue(year, month, era);
+ CheckHebrewDayValue(year, month, day, era);
+ DateTime dt = HebrewToGregorian(year, month, day, hour, minute, second, millisecond);
+ CheckTicksRange(dt.Ticks);
+ return (dt);
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 5790;
+
+
+ public override int TwoDigitYearMax {
+ get {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value == 99)
+ {
+ // Do nothing here. Year 99 is allowed so that TwoDitYearMax is disabled.
+ }
+ else
+ {
+ CheckHebrewYearValue(value, HebrewEra, "value");
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (year < 100) {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if (year > MaxHebrewYear || year < MinHebrewYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ MinHebrewYear,
+ MaxHebrewYear));
+ }
+ return (year);
+ }
+
+ internal class __DateBuffer {
+ internal int year;
+ internal int month;
+ internal int day;
+ }
+
+ }
+
+}
+
diff --git a/src/mscorlib/src/System/Globalization/HebrewNumber.cs b/src/mscorlib/src/System/Globalization/HebrewNumber.cs
new file mode 100644
index 0000000000..cf0595585a
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/HebrewNumber.cs
@@ -0,0 +1,402 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Globalization {
+ using System;
+ using System.Text;
+ using System.Diagnostics.Contracts;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Used in HebrewNumber.ParseByChar to maintain the context information (
+ // the state in the state machine and current Hebrew number values, etc.)
+ // when parsing Hebrew number character by character.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal struct HebrewNumberParsingContext {
+ // The current state of the state machine for parsing Hebrew numbers.
+ internal HebrewNumber.HS state;
+ // The current value of the Hebrew number.
+ // The final value is determined when state is FoundEndOfHebrewNumber.
+ internal int result;
+
+ public HebrewNumberParsingContext(int result) {
+ // Set the start state of the state machine for parsing Hebrew numbers.
+ state = HebrewNumber.HS.Start;
+ this.result = result;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Please see ParseByChar() for comments about different states defined here.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal enum HebrewNumberParsingState {
+ InvalidHebrewNumber,
+ NotHebrewDigit,
+ FoundEndOfHebrewNumber,
+ ContinueParsing,
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // class HebrewNumber
+ //
+ // Provides static methods for formatting integer values into
+ // Hebrew text and parsing Hebrew number text.
+ //
+ // Limitations:
+ // Parse can only handles value 1 ~ 999.
+ // ToString() can only handles 1 ~ 999. If value is greater than 5000,
+ // 5000 will be subtracted from the value.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal class HebrewNumber {
+
+ // This class contains only static methods. Add a private ctor so that
+ // compiler won't generate a default one for us.
+ private HebrewNumber() {
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Converts the given number to Hebrew letters according to the numeric
+ // value of each Hebrew letter. Basically, this converts the lunar year
+ // and the lunar month to letters.
+ //
+ // The character of a year is described by three letters of the Hebrew
+ // alphabet, the first and third giving, respectively, the days of the
+ // weeks on which the New Year occurs and Passover begins, while the
+ // second is the initial of the Hebrew word for defective, normal, or
+ // complete.
+ //
+ // Defective Year : Both Heshvan and Kislev are defective (353 or 383 days)
+ // Normal Year : Heshvan is defective, Kislev is full (354 or 384 days)
+ // Complete Year : Both Heshvan and Kislev are full (355 or 385 days)
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal static String ToString(int Number) {
+ char cTens = '\x0';
+ char cUnits; // tens and units chars
+ int Hundreds, Tens; // hundreds and tens values
+ StringBuilder szHebrew = new StringBuilder();
+
+
+ //
+ // Adjust the number if greater than 5000.
+ //
+ if (Number > 5000) {
+ Number -= 5000;
+ }
+
+ Contract.Assert(Number > 0 && Number <= 999, "Number is out of range.");;
+
+ //
+ // Get the Hundreds.
+ //
+ Hundreds = Number / 100;
+
+ if (Hundreds > 0) {
+ Number -= Hundreds * 100;
+ // \x05e7 = 100
+ // \x05e8 = 200
+ // \x05e9 = 300
+ // \x05ea = 400
+ // If the number is greater than 400, use the multiples of 400.
+ for (int i = 0; i < (Hundreds / 4) ; i++) {
+ szHebrew.Append('\x05ea');
+ }
+
+ int remains = Hundreds % 4;
+ if (remains > 0) {
+ szHebrew.Append((char)((int)'\x05e6' + remains));
+ }
+ }
+
+ //
+ // Get the Tens.
+ //
+ Tens = Number / 10;
+ Number %= 10;
+
+ switch (Tens) {
+ case ( 0 ) :
+ cTens = '\x0';
+ break;
+ case ( 1 ) :
+ cTens = '\x05d9'; // Hebrew Letter Yod
+ break;
+ case ( 2 ) :
+ cTens = '\x05db'; // Hebrew Letter Kaf
+ break;
+ case ( 3 ) :
+ cTens = '\x05dc'; // Hebrew Letter Lamed
+ break;
+ case ( 4 ) :
+ cTens = '\x05de'; // Hebrew Letter Mem
+ break;
+ case ( 5 ) :
+ cTens = '\x05e0'; // Hebrew Letter Nun
+ break;
+ case ( 6 ) :
+ cTens = '\x05e1'; // Hebrew Letter Samekh
+ break;
+ case ( 7 ) :
+ cTens = '\x05e2'; // Hebrew Letter Ayin
+ break;
+ case ( 8 ) :
+ cTens = '\x05e4'; // Hebrew Letter Pe
+ break;
+ case ( 9 ) :
+ cTens = '\x05e6'; // Hebrew Letter Tsadi
+ break;
+ }
+
+ //
+ // Get the Units.
+ //
+ cUnits = (char)(Number > 0 ? ((int)'\x05d0' + Number - 1) : 0);
+
+ if ((cUnits == '\x05d4') && // Hebrew Letter He (5)
+ (cTens == '\x05d9')) { // Hebrew Letter Yod (10)
+ cUnits = '\x05d5'; // Hebrew Letter Vav (6)
+ cTens = '\x05d8'; // Hebrew Letter Tet (9)
+ }
+
+ if ((cUnits == '\x05d5') && // Hebrew Letter Vav (6)
+ (cTens == '\x05d9')) { // Hebrew Letter Yod (10)
+ cUnits = '\x05d6'; // Hebrew Letter Zayin (7)
+ cTens = '\x05d8'; // Hebrew Letter Tet (9)
+ }
+
+ //
+ // Copy the appropriate info to the given buffer.
+ //
+
+ if (cTens != '\x0') {
+ szHebrew.Append(cTens);
+ }
+
+ if (cUnits != '\x0') {
+ szHebrew.Append(cUnits);
+ }
+
+ if (szHebrew.Length > 1) {
+ szHebrew.Insert(szHebrew.Length - 1, '"');
+ } else {
+ szHebrew.Append('\'');
+ }
+
+ //
+ // Return success.
+ //
+ return (szHebrew.ToString());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Token used to tokenize a Hebrew word into tokens so that we can use in the
+ // state machine.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ enum HebrewToken {
+ Invalid = -1,
+ Digit400 = 0,
+ Digit200_300 = 1,
+ Digit100 = 2,
+ Digit10 = 3, // 10 ~ 90
+ Digit1 = 4, // 1, 2, 3, 4, 5, 8,
+ Digit6_7 = 5,
+ Digit7 = 6,
+ Digit9 = 7,
+ SingleQuote = 8,
+ DoubleQuote = 9,
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // This class is used to map a token into its Hebrew digit value.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ class HebrewValue {
+ internal HebrewToken token;
+ internal int value;
+ internal HebrewValue(HebrewToken token, int value) {
+ this.token = token;
+ this.value = value;
+ }
+ }
+
+ //
+ // Map a Hebrew character from U+05D0 ~ U+05EA to its digit value.
+ // The value is -1 if the Hebrew character does not have a associated value.
+ //
+ static HebrewValue[] HebrewValues = {
+ new HebrewValue(HebrewToken.Digit1, 1) , // '\x05d0
+ new HebrewValue(HebrewToken.Digit1, 2) , // '\x05d1
+ new HebrewValue(HebrewToken.Digit1, 3) , // '\x05d2
+ new HebrewValue(HebrewToken.Digit1, 4) , // '\x05d3
+ new HebrewValue(HebrewToken.Digit1, 5) , // '\x05d4
+ new HebrewValue(HebrewToken.Digit6_7,6) , // '\x05d5
+ new HebrewValue(HebrewToken.Digit6_7,7) , // '\x05d6
+ new HebrewValue(HebrewToken.Digit1, 8) , // '\x05d7
+ new HebrewValue(HebrewToken.Digit9, 9) , // '\x05d8
+ new HebrewValue(HebrewToken.Digit10, 10) , // '\x05d9; // Hebrew Letter Yod
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05da;
+ new HebrewValue(HebrewToken.Digit10, 20) , // '\x05db; // Hebrew Letter Kaf
+ new HebrewValue(HebrewToken.Digit10, 30) , // '\x05dc; // Hebrew Letter Lamed
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05dd;
+ new HebrewValue(HebrewToken.Digit10, 40) , // '\x05de; // Hebrew Letter Mem
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05df;
+ new HebrewValue(HebrewToken.Digit10, 50) , // '\x05e0; // Hebrew Letter Nun
+ new HebrewValue(HebrewToken.Digit10, 60) , // '\x05e1; // Hebrew Letter Samekh
+ new HebrewValue(HebrewToken.Digit10, 70) , // '\x05e2; // Hebrew Letter Ayin
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05e3;
+ new HebrewValue(HebrewToken.Digit10, 80) , // '\x05e4; // Hebrew Letter Pe
+ new HebrewValue(HebrewToken.Invalid, -1) , // '\x05e5;
+ new HebrewValue(HebrewToken.Digit10, 90) , // '\x05e6; // Hebrew Letter Tsadi
+ new HebrewValue(HebrewToken.Digit100, 100) , // '\x05e7;
+ new HebrewValue(HebrewToken.Digit200_300, 200) , // '\x05e8;
+ new HebrewValue(HebrewToken.Digit200_300, 300) , // '\x05e9;
+ new HebrewValue(HebrewToken.Digit400, 400) , // '\x05ea;
+ };
+
+ const int minHebrewNumberCh = 0x05d0;
+ static char maxHebrewNumberCh = (char)(minHebrewNumberCh + HebrewValues.Length - 1);
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Hebrew number parsing State
+ // The current state and the next token will lead to the next state in the state machine.
+ // DQ = Double Quote
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ internal enum HS {
+ _err = -1, // an error state
+ Start = 0,
+ S400 = 1, // a Hebrew digit 400
+ S400_400 = 2, // Two Hebrew digit 400
+ S400_X00 = 3, // Two Hebrew digit 400 and followed by 100
+ S400_X0 = 4, // Hebrew digit 400 and followed by 10 ~ 90
+ X00_DQ = 5, // A hundred number and followed by a double quote.
+ S400_X00_X0 = 6,
+ X0_DQ = 7, // A two-digit number and followed by a double quote.
+ X = 8, // A single digit Hebrew number.
+ X0 = 9, // A two-digit Hebrew number
+ X00 = 10, // A three-digit Hebrew number
+ S400_DQ = 11, // A Hebrew digit 400 and followed by a double quote.
+ S400_400_DQ = 12,
+ S400_400_100 = 13,
+ S9 = 14, // Hebrew digit 9
+ X00_S9 = 15, // A hundered number and followed by a digit 9
+ S9_DQ = 16, // Hebrew digit 9 and followed by a double quote
+ END = 100, // A terminial state is reached.
+ }
+
+ //
+ // The state machine for Hebrew number pasing.
+ //
+ readonly static HS[][] NumberPasingState = {
+ // 400 300/200 100 90~10 8~1 6, 7, 9, ' "
+ /* 0 */ new HS[] {HS.S400, HS.X00, HS.X00, HS.X0, HS.X, HS.X, HS.X, HS.S9, HS._err, HS._err},
+ /* 1: S400 */ new HS[] {HS.S400_400, HS.S400_X00, HS.S400_X00, HS.S400_X0, HS._err, HS._err, HS._err, HS.X00_S9 ,HS.END, HS.S400_DQ},
+ /* 2: S400_400 */ new HS[] {HS._err, HS._err, HS.S400_400_100,HS.S400_X0, HS._err, HS._err, HS._err, HS.X00_S9 ,HS._err, HS.S400_400_DQ},
+ /* 3: S400_X00 */ new HS[] {HS._err, HS._err, HS._err, HS.S400_X00_X0, HS._err, HS._err, HS._err, HS.X00_S9 ,HS._err, HS.X00_DQ},
+ /* 4: S400_X0 */ new HS[] {HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.X0_DQ},
+ /* 5: X00_DQ */ new HS[] {HS._err, HS._err, HS._err, HS.END, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err},
+ /* 6: S400_X00_X0 */new HS[] {HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.X0_DQ},
+ /* 7: X0_DQ */ new HS[] {HS._err, HS._err, HS._err, HS._err, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err},
+ /* 8: X */ new HS[] {HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS._err},
+ /* 9: X0 */ new HS[] {HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS.X0_DQ},
+ /* 10: X00 */ new HS[] {HS._err, HS._err, HS._err, HS.S400_X0, HS._err, HS._err, HS._err, HS.X00_S9, HS.END, HS.X00_DQ},
+ /* 11: S400_DQ */ new HS[] {HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err},
+ /* 12: S400_400_DQ*/new HS[] {HS._err, HS._err, HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err},
+ /* 13: S400_400_100*/new HS[]{HS._err, HS._err, HS._err, HS.S400_X00_X0, HS._err, HS._err, HS._err, HS.X00_S9, HS._err, HS.X00_DQ},
+ /* 14: S9 */ new HS[] {HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err,HS.END, HS.S9_DQ},
+ /* 15: X00_S9 */ new HS[] {HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.S9_DQ},
+ /* 16: S9_DQ */ new HS[] {HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS.END, HS._err, HS._err, HS._err},
+ };
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Parse the Hebrew number by passing one character at a time.
+ // The state between characters are maintained at HebrewNumberPasingContext.
+ // Returns:
+ // Return a enum of HebrewNumberParsingState.
+ // NotHebrewDigit: The specified ch is not a valid Hebrew digit.
+ // InvalidHebrewNumber: After parsing the specified ch, it will lead into
+ // an invalid Hebrew number text.
+ // FoundEndOfHebrewNumber: A terminal state is reached. This means that
+ // we find a valid Hebrew number text after the specified ch is parsed.
+ // ContinueParsing: The specified ch is a valid Hebrew digit, and
+ // it will lead into a valid state in the state machine, we should
+ // continue to parse incoming characters.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static HebrewNumberParsingState ParseByChar(char ch, ref HebrewNumberParsingContext context) {
+ HebrewToken token;
+ if (ch == '\'') {
+ token = HebrewToken.SingleQuote;
+ } else if (ch == '\"') {
+ token = HebrewToken.DoubleQuote;
+ } else {
+ int index = (int)ch - minHebrewNumberCh;
+ if (index >= 0 && index < HebrewValues.Length ) {
+ token = HebrewValues[index].token;
+ if (token == HebrewToken.Invalid) {
+ return (HebrewNumberParsingState.NotHebrewDigit);
+ }
+ context.result += HebrewValues[index].value;
+ } else {
+ // Not in valid Hebrew digit range.
+ return (HebrewNumberParsingState.NotHebrewDigit);
+ }
+ }
+ context.state = NumberPasingState[(int)context.state][(int)token];
+ if (context.state == HS._err) {
+ // Invalid Hebrew state. This indicates an incorrect Hebrew number.
+ return (HebrewNumberParsingState.InvalidHebrewNumber);
+ }
+ if (context.state == HS.END) {
+ // Reach a terminal state.
+ return (HebrewNumberParsingState.FoundEndOfHebrewNumber);
+ }
+ // We should continue to parse.
+ return (HebrewNumberParsingState.ContinueParsing);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Actions:
+ // Check if the ch is a valid Hebrew number digit.
+ // This function will return true if the specified char is a legal Hebrew
+ // digit character, single quote, or double quote.
+ // Returns:
+ // true if the specified character is a valid Hebrew number character.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static bool IsDigit(char ch) {
+ if (ch >= minHebrewNumberCh && ch <= maxHebrewNumberCh) {
+ return (HebrewValues[ch - minHebrewNumberCh].value >= 0);
+ }
+ return (ch == '\'' || ch == '\"');
+ }
+
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/HijriCalendar.cs b/src/mscorlib/src/System/Globalization/HijriCalendar.cs
new file mode 100644
index 0000000000..194d329d25
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/HijriCalendar.cs
@@ -0,0 +1,723 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+
+ using System;
+ using System.Runtime.Versioning;
+ using System.Diagnostics.Contracts;
+#if !FEATURE_WIN32_REGISTRY
+ using System.Text;
+ using Microsoft.Win32;
+#endif // FEATURE_WIN32_REGISTRY
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Rules for the Hijri calendar:
+ // - The Hijri calendar is a strictly Lunar calendar.
+ // - Days begin at sunset.
+ // - Islamic Year 1 (Muharram 1, 1 A.H.) is equivalent to absolute date
+ // 227015 (Friday, July 16, 622 C.E. - Julian).
+ // - Leap Years occur in the 2, 5, 7, 10, 13, 16, 18, 21, 24, 26, & 29th
+ // years of a 30-year cycle. Year = leap iff ((11y+14) mod 30 < 11).
+ // - There are 12 months which contain alternately 30 and 29 days.
+ // - The 12th month, Dhu al-Hijjah, contains 30 days instead of 29 days
+ // in a leap year.
+ // - Common years have 354 days. Leap years have 355 days.
+ // - There are 10,631 days in a 30-year cycle.
+ // - The Islamic months are:
+ // 1. Muharram (30 days) 7. Rajab (30 days)
+ // 2. Safar (29 days) 8. Sha'ban (29 days)
+ // 3. Rabi I (30 days) 9. Ramadan (30 days)
+ // 4. Rabi II (29 days) 10. Shawwal (29 days)
+ // 5. Jumada I (30 days) 11. Dhu al-Qada (30 days)
+ // 6. Jumada II (29 days) 12. Dhu al-Hijjah (29 days) {30}
+ //
+ // NOTENOTE
+ // The calculation of the HijriCalendar is based on the absolute date. And the
+ // absolute date means the number of days from January 1st, 1 A.D.
+ // Therefore, we do not support the days before the January 1st, 1 A.D.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 0622/07/18 9999/12/31
+ ** Hijri 0001/01/01 9666/04/03
+ */
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public class HijriCalendar : Calendar {
+
+
+ public static readonly int HijriEra = 1;
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+
+ internal const int MinAdvancedHijri = -2;
+ internal const int MaxAdvancedHijri = 2;
+
+ internal static readonly int[] HijriMonthDays = {0,30,59,89,118,148,177,207,236,266,295,325,355};
+
+ //internal static Calendar m_defaultInstance;
+
+#if FEATURE_WIN32_REGISTRY
+ private const String InternationalRegKey = "Control Panel\\International";
+ private const String HijriAdvanceRegKeyEntry = "AddHijriDate";
+#endif
+
+ private int m_HijriAdvance = Int32.MinValue;
+
+ // DateTime.MaxValue = Hijri calendar (year:9666, month: 4, day: 3).
+ internal const int MaxCalendarYear = 9666;
+ internal const int MaxCalendarMonth = 4;
+ internal const int MaxCalendarDay = 3;
+ // Hijri calendar (year: 1, month: 1, day:1 ) = Gregorian (year: 622, month: 7, day: 18)
+ // This is the minimal Gregorian date that we support in the HijriCalendar.
+ internal static readonly DateTime calendarMinValue = new DateTime(622, 7, 18);
+ internal static readonly DateTime calendarMaxValue = DateTime.MaxValue;
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (calendarMaxValue);
+ }
+ }
+
+ // Return the type of the Hijri calendar.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.LunarCalendar;
+ }
+ }
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of HijriCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+ /*
+ internal static Calendar GetDefaultInstance() {
+ if (m_defaultInstance == null) {
+ m_defaultInstance = new HijriCalendar();
+ }
+ return (m_defaultInstance);
+ }
+ */
+
+ // Construct an instance of Hijri calendar.
+
+ public HijriCalendar() {
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_HIJRI);
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // the year before the 1st year of the cycle would have been the 30th year
+ // of the previous cycle which is not a leap year. Common years have 354 days.
+ return 354;
+ }
+ }
+
+
+
+ /*=================================GetAbsoluteDateHijri==========================
+ **Action: Gets the Absolute date for the given Hijri date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ long GetAbsoluteDateHijri(int y, int m, int d) {
+ return (long)(DaysUpToHijriYear(y) + HijriMonthDays[m-1] + d - 1 - HijriAdjustment);
+ }
+
+ /*=================================DaysUpToHijriYear==========================
+ **Action: Gets the total number of days (absolute date) up to the given Hijri Year.
+ ** The absolute date means the number of days from January 1st, 1 A.D.
+ **Returns: Gets the total number of days (absolute date) up to the given Hijri Year.
+ **Arguments: HijriYear year value in Hijri calendar.
+ **Exceptions: None
+ **Notes:
+ ============================================================================*/
+
+ long DaysUpToHijriYear(int HijriYear) {
+ long NumDays; // number of absolute days
+ int NumYear30; // number of years up to current 30 year cycle
+ int NumYearsLeft; // number of years into 30 year cycle
+
+ //
+ // Compute the number of years up to the current 30 year cycle.
+ //
+ NumYear30 = ((HijriYear - 1) / 30) * 30;
+
+ //
+ // Compute the number of years left. This is the number of years
+ // into the 30 year cycle for the given year.
+ //
+ NumYearsLeft = HijriYear - NumYear30 - 1;
+
+ //
+ // Compute the number of absolute days up to the given year.
+ //
+ NumDays = ((NumYear30 * 10631L) / 30L) + 227013L;
+ while (NumYearsLeft > 0) {
+ // Common year is 354 days, and leap year is 355 days.
+ NumDays += 354 + (IsLeapYear(NumYearsLeft, CurrentEra) ? 1: 0);
+ NumYearsLeft--;
+ }
+
+ //
+ // Return the number of absolute days.
+ //
+ return (NumDays);
+ }
+
+
+ public int HijriAdjustment {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ if (m_HijriAdvance == Int32.MinValue) {
+ // Never been set before. Use the system value from registry.
+ m_HijriAdvance = GetAdvanceHijriDate();
+ }
+ return (m_HijriAdvance);
+ }
+
+ set {
+ // NOTE: Check the value of Min/MaxAdavncedHijri with Arabic speakers to see if the assumption is good.
+ if (value < MinAdvancedHijri || value > MaxAdvancedHijri) {
+ throw new ArgumentOutOfRangeException(
+ "HijriAdjustment",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Bounds_Lower_Upper"),
+ MinAdvancedHijri,
+ MaxAdvancedHijri));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+
+ m_HijriAdvance = value;
+ }
+ }
+
+ /*=================================GetAdvanceHijriDate==========================
+ **Action: Gets the AddHijriDate value from the registry.
+ **Returns:
+ **Arguments: None.
+ **Exceptions:
+ **Note:
+ ** The HijriCalendar has a user-overidable calculation. That is, use can set a value from the control
+ ** panel, so that the calculation of the Hijri Calendar can move ahead or backwards from -2 to +2 days.
+ **
+ ** The valid string values in the registry are:
+ ** "AddHijriDate-2" => Add -2 days to the current calculated Hijri date.
+ ** "AddHijriDate" => Add -1 day to the current calculated Hijri date.
+ ** "" => Add 0 day to the current calculated Hijri date.
+ ** "AddHijriDate+1" => Add +1 days to the current calculated Hijri date.
+ ** "AddHijriDate+2" => Add +2 days to the current calculated Hijri date.
+ ============================================================================*/
+ [System.Security.SecurityCritical] // auto-generated
+ static int GetAdvanceHijriDate() {
+#if FEATURE_WIN32_REGISTRY
+
+ int hijriAdvance = 0;
+ Microsoft.Win32.RegistryKey key = null;
+
+ try {
+ // Open in read-only mode.
+ // Use InternalOpenSubKey so that we avoid the security check.
+ key = Microsoft.Win32.Registry.CurrentUser.InternalOpenSubKey(InternationalRegKey, false);
+ }
+ //If this fails for any reason, we'll just return 0.
+ catch (ObjectDisposedException) { return 0; }
+ catch (ArgumentException) { return 0; }
+
+ if (key != null) {
+ try {
+ Object value = key.InternalGetValue(HijriAdvanceRegKeyEntry, null, false, false);
+ if (value == null) {
+ return (0);
+ }
+ String str = value.ToString();
+ if (String.Compare(str, 0, HijriAdvanceRegKeyEntry, 0, HijriAdvanceRegKeyEntry.Length, StringComparison.OrdinalIgnoreCase) == 0) {
+ if (str.Length == HijriAdvanceRegKeyEntry.Length)
+ hijriAdvance = -1;
+ else {
+ str = str.Substring(HijriAdvanceRegKeyEntry.Length);
+ try {
+ int advance = Int32.Parse(str.ToString(), CultureInfo.InvariantCulture);
+ if ((advance >= MinAdvancedHijri) && (advance <= MaxAdvancedHijri)) {
+ hijriAdvance = advance;
+ }
+ }
+ // If we got garbage from registry just ignore it.
+ // hijriAdvance = 0 because of declaraction assignment up above.
+ catch (ArgumentException) { }
+ catch (FormatException) { }
+ catch (OverflowException) { }
+ }
+ }
+ }
+ finally {
+ key.Close();
+ }
+
+ }
+ return (hijriAdvance);
+
+#else //FEATURE_WIN32_REGISTRY
+ return 0;
+#endif // FEATURE_WIN32_REGISTRY
+ }
+
+ static internal void CheckTicksRange(long ticks) {
+ if (ticks < calendarMinValue.Ticks || ticks > calendarMaxValue.Ticks) {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_CalendarRange"),
+ calendarMinValue,
+ calendarMaxValue));
+ }
+ }
+
+ static internal void CheckEraRange(int era) {
+ if (era != CurrentEra && era != HijriEra) {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+ }
+
+ static internal void CheckYearRange(int year, int era) {
+ CheckEraRange(era);
+ if (year < 1 || year > MaxCalendarYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ MaxCalendarYear));
+ }
+ }
+
+ static internal void CheckYearMonthRange(int year, int month, int era) {
+ CheckYearRange(year, era);
+ if (year == MaxCalendarYear) {
+ if (month > MaxCalendarMonth) {
+ throw new ArgumentOutOfRangeException(
+ "month",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ MaxCalendarMonth));
+ }
+ }
+
+ if (month < 1 || month > 12) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
+ }
+ }
+
+ /*=================================GetDatePart==========================
+ **Action: Returns a given date part of this <i>DateTime</i>. This method is used
+ ** to compute the year, day-of-year, month, or day part.
+ **Returns:
+ **Arguments:
+ **Exceptions: ArgumentException if part is incorrect.
+ **Notes:
+ ** First, we get the absolute date (the number of days from January 1st, 1 A.C) for the given ticks.
+ ** Use the formula (((AbsoluteDate - 227013) * 30) / 10631) + 1, we can a rough value for the Hijri year.
+ ** In order to get the exact Hijri year, we compare the exact absolute date for HijriYear and (HijriYear + 1).
+ ** From here, we can get the correct Hijri year.
+ ============================================================================*/
+
+ internal virtual int GetDatePart(long ticks, int part) {
+ int HijriYear; // Hijri year
+ int HijriMonth; // Hijri month
+ int HijriDay; // Hijri day
+ long NumDays; // The calculation buffer in number of days.
+
+ CheckTicksRange(ticks);
+
+ //
+ // Get the absolute date. The absolute date is the number of days from January 1st, 1 A.D.
+ // 1/1/0001 is absolute date 1.
+ //
+ NumDays = ticks / GregorianCalendar.TicksPerDay + 1;
+
+ //
+ // See how much we need to backup or advance
+ //
+ NumDays += HijriAdjustment;
+
+ //
+ // Calculate the appromixate Hijri Year from this magic formula.
+ //
+ HijriYear = (int)(((NumDays - 227013) * 30) / 10631) + 1;
+
+ long daysToHijriYear = DaysUpToHijriYear(HijriYear); // The absoulte date for HijriYear
+ long daysOfHijriYear = GetDaysInYear(HijriYear, CurrentEra); // The number of days for (HijriYear+1) year.
+
+ if (NumDays < daysToHijriYear) {
+ daysToHijriYear -= daysOfHijriYear;
+ HijriYear--;
+ } else if (NumDays == daysToHijriYear) {
+ HijriYear--;
+ daysToHijriYear -= GetDaysInYear(HijriYear, CurrentEra);
+ } else {
+ if (NumDays > daysToHijriYear + daysOfHijriYear) {
+ daysToHijriYear += daysOfHijriYear;
+ HijriYear++;
+ }
+ }
+ if (part == DatePartYear) {
+ return (HijriYear);
+ }
+
+ //
+ // Calculate the Hijri Month.
+ //
+
+ HijriMonth = 1;
+ NumDays -= daysToHijriYear;
+
+ if (part == DatePartDayOfYear) {
+ return ((int)NumDays);
+ }
+
+ while ((HijriMonth <= 12) && (NumDays > HijriMonthDays[HijriMonth - 1])) {
+ HijriMonth++;
+ }
+ HijriMonth--;
+
+ if (part == DatePartMonth) {
+ return (HijriMonth);
+ }
+
+ //
+ // Calculate the Hijri Day.
+ //
+ HijriDay = (int)(NumDays - HijriMonthDays[HijriMonth - 1]);
+
+ if (part == DatePartDay) {
+ return (HijriDay);
+ }
+ // Incorrect part value.
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_DateTimeParsing"));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+ public override DateTime AddMonths(DateTime time, int months) {
+ if (months < -120000 || months > 120000) {
+ throw new ArgumentOutOfRangeException(
+ "months",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ -120000,
+ 120000));
+ }
+ Contract.EndContractBlock();
+ // Get the date in Hijri calendar.
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+ int i = m - 1 + months;
+ if (i >= 0) {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ } else {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int days = GetDaysInMonth(y, m);
+ if (d > days) {
+ d = days;
+ }
+ long ticks = GetAbsoluteDateHijri(y, m, d)* TicksPerDay + (time.Ticks % TicksPerDay);
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+ public override DateTime AddYears(DateTime time, int years) {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+ public override int GetDayOfMonth(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+ public override int GetDayOfYear(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartDayOfYear));
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+ [Pure]
+ public override int GetDaysInMonth(int year, int month, int era) {
+ CheckYearMonthRange(year, month, era);
+ if (month == 12) {
+ // For the 12th month, leap year has 30 days, and common year has 29 days.
+ return (IsLeapYear(year, CurrentEra) ? 30 : 29);
+ }
+ // Other months contain 30 and 29 days alternatively. The 1st month has 30 days.
+ return (((month % 2) == 1) ? 30 : 29);
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era) {
+ CheckYearRange(year, era);
+ // Common years have 354 days. Leap years have 355 days.
+ return (IsLeapYear(year, CurrentEra) ? 355: 354);
+ }
+
+ // Returns the era for the specified DateTime value.
+
+ public override int GetEra(DateTime time) {
+ CheckTicksRange(time.Ticks);
+ return (HijriEra);
+ }
+
+
+ public override int[] Eras {
+ get {
+ return (new int[] {HijriEra});
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+ public override int GetMonth(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+ public override int GetMonthsInYear(int year, int era) {
+ CheckYearRange(year, era);
+ return (12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and MaxCalendarYear.
+ //
+
+ public override int GetYear(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartYear));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+ public override bool IsLeapDay(int year, int month, int day, int era) {
+ // The year/month/era value checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth) {
+ throw new ArgumentOutOfRangeException(
+ "day",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Day"),
+ daysInMonth,
+ month));
+ }
+ return (IsLeapYear(year, era) && month == 12 && day == 30);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetLeapMonth(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+ public override bool IsLeapMonth(int year, int month, int era) {
+ CheckYearMonthRange(year, month, era);
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era) {
+ CheckYearRange(year, era);
+ return ((((year * 11) + 14) % 30) < 11);
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ // The year/month/era checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth) {
+ BCLDebug.Log("year = " + year + ", month = " + month + ", day = " + day);
+ throw new ArgumentOutOfRangeException(
+ "day",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Day"),
+ daysInMonth,
+ month));
+ }
+
+ long lDate = GetAbsoluteDateHijri(year, month, day);
+
+ if (lDate >= 0) {
+ return (new DateTime(lDate * GregorianCalendar.TicksPerDay + TimeToTicks(hour, minute, second, millisecond)));
+ } else {
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 1451;
+
+
+ public override int TwoDigitYearMax {
+ get {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value < 99 || value > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "value",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 99,
+ MaxCalendarYear));
+
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (year < 100) {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if (year > MaxCalendarYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ MaxCalendarYear));
+ }
+ return (year);
+ }
+
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/IdnMapping.cs b/src/mscorlib/src/System/Globalization/IdnMapping.cs
new file mode 100644
index 0000000000..599a32ad87
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/IdnMapping.cs
@@ -0,0 +1,1189 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// This file contains the IDN functions and implementation.
+//
+// This allows encoding of non-ASCII domain names in a "punycode" form,
+// for example:
+//
+// \u5B89\u5BA4\u5948\u7F8E\u6075-with-SUPER-MONKEYS
+//
+// is encoded as:
+//
+// xn---with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n
+//
+// Additional options are provided to allow unassigned IDN characters and
+// to validate according to the Std3ASCII Rules (like DNS names).
+//
+// There are also rules regarding bidirectionality of text and the length
+// of segments.
+//
+// For additional rules see also:
+// RFC 3490 - Internationalizing Domain Names in Applications (IDNA)
+// RFC 3491 - Nameprep: A Stringprep Profile for Internationalized Domain Names (IDN)
+// RFC 3492 - Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)
+//
+
+/*
+
+The punycode implementation is based on the sample code in RFC 3492
+
+Copyright (C) The Internet Society (2003). All Rights Reserved.
+
+This document and translations of it may be copied and furnished to
+others, and derivative works that comment on or otherwise explain it
+or assist in its implementation may be prepared, copied, published
+and distributed, in whole or in part, without restriction of any
+kind, provided that the above copyright notice and this paragraph are
+included on all such copies and derivative works. However, this
+document itself may not be modified in any way, such as by removing
+the copyright notice or references to the Internet Society or other
+Internet organizations, except as needed for the purpose of
+developing Internet standards in which case the procedures for
+copyrights defined in the Internet Standards process must be
+followed, or as required to translate it into languages other than
+English.
+
+The limited permissions granted above are perpetual and will not be
+revoked by the Internet Society or its successors or assigns.
+
+This document and the information contained herein is provided on an
+"AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+namespace System.Globalization
+{
+ using System;
+ using System.Security;
+ using System.Globalization;
+ using System.Text;
+ using System.Runtime.Versioning;
+ using System.Runtime.InteropServices;
+ using System.Diagnostics.Contracts;
+
+ // IdnMapping class used to map names to Punycode
+
+ public sealed class IdnMapping
+ {
+ // Legal name lengths for domain names
+ const int M_labelLimit = 63; // Not including dots
+ const int M_defaultNameLimit = 255; // Including dots
+
+ // IDNA prefix
+ const String M_strAcePrefix = "xn--";
+
+ // Legal "dot" seperators (i.e: . in www.microsoft.com)
+ static char[] M_Dots =
+ {
+ '.', '\u3002', '\uFF0E', '\uFF61'
+ };
+
+ bool m_bAllowUnassigned;
+ bool m_bUseStd3AsciiRules;
+
+ public IdnMapping()
+ {
+ }
+
+ public bool AllowUnassigned
+ {
+ get
+ {
+ return this.m_bAllowUnassigned;
+ }
+
+ set
+ {
+ this.m_bAllowUnassigned = value;
+ }
+ }
+
+ public bool UseStd3AsciiRules
+ {
+ get
+ {
+ return this.m_bUseStd3AsciiRules;
+ }
+
+ set
+ {
+ this.m_bUseStd3AsciiRules = value;
+ }
+ }
+
+ // Gets ASCII (Punycode) version of the string
+ public String GetAscii(String unicode)
+ {
+ return GetAscii(unicode, 0);
+ }
+
+ public String GetAscii(String unicode, int index)
+ {
+ if (unicode==null) throw new ArgumentNullException("unicode");
+ Contract.EndContractBlock();
+ return GetAscii(unicode, index, unicode.Length - index);
+ }
+
+ public String GetAscii(String unicode, int index, int count)
+ {
+ throw null;
+ /*if (unicode==null) throw new ArgumentNullException("unicode");
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0) ? "index" : "count",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ if (index > unicode.Length)
+ throw new ArgumentOutOfRangeException("byteIndex",
+ Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ if (index > unicode.Length - count)
+ throw new ArgumentOutOfRangeException("unicode",
+ Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer"));
+ Contract.EndContractBlock();
+
+ // We're only using part of the string
+ unicode = unicode.Substring(index, count);
+
+ if (Environment.IsWindows8OrAbove)
+ {
+ return GetAsciiUsingOS(unicode);
+ }
+
+ // Check for ASCII only string, which will be unchanged
+ if (ValidateStd3AndAscii(unicode, UseStd3AsciiRules, true))
+ {
+ return unicode;
+ }
+
+ // Cannot be null terminated (normalization won't help us with this one, and
+ // may have returned false before checking the whole string above)
+ Contract.Assert(unicode.Length >= 1, "[IdnMapping.GetAscii]Expected 0 length strings to fail before now.");
+ if (unicode[unicode.Length - 1] <= 0x1f)
+ {
+ throw new ArgumentException(
+ Environment.GetResourceString("Argument_InvalidCharSequence", unicode.Length-1 ),
+ "unicode");
+ }
+
+ // Have to correctly IDNA normalize the string and Unassigned flags
+ bool bHasLastDot = (unicode.Length > 0) && IsDot(unicode[unicode.Length - 1]);
+ unicode = unicode.Normalize((NormalizationForm)(m_bAllowUnassigned ?
+ ExtendedNormalizationForms.FormIdna : ExtendedNormalizationForms.FormIdnaDisallowUnassigned));
+
+ // Make sure we didn't normalize away something after a last dot
+ if ((!bHasLastDot) && unicode.Length > 0 && IsDot(unicode[unicode.Length - 1]))
+ {
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "unicode");
+ }
+
+ // May need to check Std3 rules again for non-ascii
+ if (UseStd3AsciiRules)
+ {
+ ValidateStd3AndAscii(unicode, true, false);
+ }
+
+ // Go ahead and encode it
+ return punycode_encode(unicode);*/
+ }
+
+
+ [System.Security.SecuritySafeCritical]
+ private String GetAsciiUsingOS(String unicode)
+ {
+ if (unicode.Length == 0)
+ {
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "unicode");
+ }
+
+ if (unicode[unicode.Length - 1] == 0)
+ {
+ throw new ArgumentException(
+ Environment.GetResourceString("Argument_InvalidCharSequence", unicode.Length - 1),
+ "unicode");
+ }
+
+ uint flags = (uint) ((AllowUnassigned ? IDN_ALLOW_UNASSIGNED : 0) | (UseStd3AsciiRules ? IDN_USE_STD3_ASCII_RULES : 0));
+ int length = IdnToAscii(flags, unicode, unicode.Length, null, 0);
+
+ int lastError;
+
+ if (length == 0)
+ {
+ lastError = Marshal.GetLastWin32Error();
+ if (lastError == ERROR_INVALID_NAME)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_IdnIllegalName"), "unicode");
+ }
+
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidCharSequenceNoIndex"), "unicode");
+ }
+
+ char [] output = new char[length];
+
+ length = IdnToAscii(flags, unicode, unicode.Length, output, length);
+ if (length == 0)
+ {
+ lastError = Marshal.GetLastWin32Error();
+ if (lastError == ERROR_INVALID_NAME)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_IdnIllegalName"), "unicode");
+ }
+
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidCharSequenceNoIndex"), "unicode");
+ }
+
+ return new String(output, 0, length);
+ }
+
+ // Gets Unicode version of the string. Normalized and limited to IDNA characters.
+ public String GetUnicode(String ascii)
+ {
+ return GetUnicode(ascii, 0);
+ }
+
+ public String GetUnicode(String ascii, int index)
+ {
+ if (ascii==null) throw new ArgumentNullException("ascii");
+ Contract.EndContractBlock();
+ return GetUnicode(ascii, index, ascii.Length - index);
+ }
+
+ public String GetUnicode(String ascii, int index, int count)
+ {
+ if (ascii==null) throw new ArgumentNullException("ascii");
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException((index < 0) ? "index" : "count",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ if (index > ascii.Length)
+ throw new ArgumentOutOfRangeException("byteIndex",
+ Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ if (index > ascii.Length - count)
+ throw new ArgumentOutOfRangeException("ascii",
+ Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer"));
+
+ // This is a case (i.e. explicitly null-terminated input) where behavior in .NET and Win32 intentionally differ.
+ // The .NET APIs should (and did in v4.0 and earlier) throw an ArgumentException on input that includes a terminating null.
+ // The Win32 APIs fail on an embedded null, but not on a terminating null.
+ if (count > 0 && ascii[index + count - 1] == (char)0)
+ throw new ArgumentException("ascii",
+ Environment.GetResourceString("Argument_IdnBadPunycode"));
+ Contract.EndContractBlock();
+
+ // We're only using part of the string
+ ascii = ascii.Substring(index, count);
+
+ if (Environment.IsWindows8OrAbove)
+ {
+ return GetUnicodeUsingOS(ascii);
+ }
+
+ // Convert Punycode to Unicode
+ String strUnicode = punycode_decode(ascii);
+
+ // Output name MUST obey IDNA rules & round trip (casing differences are allowed)
+ if (!ascii.Equals(GetAscii(strUnicode), StringComparison.OrdinalIgnoreCase))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnIllegalName"), "ascii");
+
+ return strUnicode;
+ }
+
+
+ [System.Security.SecuritySafeCritical]
+ private string GetUnicodeUsingOS(string ascii)
+ {
+ uint flags = (uint)((AllowUnassigned ? IDN_ALLOW_UNASSIGNED : 0) | (UseStd3AsciiRules ? IDN_USE_STD3_ASCII_RULES : 0));
+ int length = IdnToUnicode(flags, ascii, ascii.Length, null, 0);
+ int lastError;
+
+ if (length == 0)
+ {
+ lastError = Marshal.GetLastWin32Error();
+ if (lastError == ERROR_INVALID_NAME)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_IdnIllegalName"), "ascii");
+ }
+
+ throw new ArgumentException(Environment.GetResourceString("Argument_IdnBadPunycode"), "ascii");
+ }
+
+ char [] output = new char[length];
+
+ length = IdnToUnicode(flags, ascii, ascii.Length, output, length);
+ if (length == 0)
+ {
+ lastError = Marshal.GetLastWin32Error();
+ if (lastError == ERROR_INVALID_NAME)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_IdnIllegalName"), "ascii");
+ }
+
+ throw new ArgumentException(Environment.GetResourceString("Argument_IdnBadPunycode"), "ascii");
+ }
+
+ return new String(output, 0, length);
+ }
+
+ public override bool Equals(Object obj)
+ {
+ IdnMapping that = obj as IdnMapping;
+
+ if (that != null)
+ {
+ return this.m_bAllowUnassigned == that.m_bAllowUnassigned &&
+ this.m_bUseStd3AsciiRules == that.m_bUseStd3AsciiRules;
+ }
+
+ return (false);
+ }
+
+ public override int GetHashCode()
+ {
+ return (this.m_bAllowUnassigned ? 100 : 200) + (this.m_bUseStd3AsciiRules ? 1000 : 2000);
+ }
+
+ // Helpers
+ static bool IsSupplementary(int cTest)
+ {
+ return cTest >= 0x10000;
+ }
+
+ // Is it a dot?
+ // are we U+002E (., full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), or
+ // U+FF61 (halfwidth ideographic full stop).
+ // Note: IDNA Normalization gets rid of dots now, but testing for last dot is before normalization
+ static bool IsDot(char c)
+ {
+ return c == '.' || c == '\u3002' || c == '\uFF0E' || c == '\uFF61';
+ }
+
+
+ // See if we're only ASCII
+ static bool ValidateStd3AndAscii(string unicode, bool bUseStd3, bool bCheckAscii)
+ {
+ // If its empty, then its too small
+ if (unicode.Length == 0)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "unicode");
+ Contract.EndContractBlock();
+
+ int iLastDot = -1;
+
+ // Loop the whole string
+ for (int i = 0; i < unicode.Length; i++)
+ {
+ // Aren't allowing control chars (or 7f, but idn tables catch that, they don't catch \0 at end though)
+ if (unicode[i] <= 0x1f)
+ {
+ throw new ArgumentException(
+ Environment.GetResourceString("Argument_InvalidCharSequence", i ),
+ "unicode");
+ }
+
+ // If its Unicode or a control character, return false (non-ascii)
+ if (bCheckAscii && unicode[i] >= 0x7f)
+ return false;
+
+ // Check for dots
+ if (IsDot(unicode[i]))
+ {
+ // Can't have 2 dots in a row
+ if (i == iLastDot + 1)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "unicode");
+
+ // If its too far between dots then fail
+ if (i - iLastDot > M_labelLimit + 1)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "Unicode");
+
+ // If validating Std3, then char before dot can't be - char
+ if (bUseStd3 && i > 0)
+ ValidateStd3(unicode[i-1], true);
+
+ // Remember where the last dot is
+ iLastDot = i;
+ continue;
+ }
+
+ // If necessary, make sure its a valid std3 character
+ if (bUseStd3)
+ {
+ ValidateStd3(unicode[i], (i == iLastDot + 1));
+ }
+ }
+
+ // If we never had a dot, then we need to be shorter than the label limit
+ if (iLastDot == -1 && unicode.Length > M_labelLimit)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "unicode");
+
+ // Need to validate entire string length, 1 shorter if last char wasn't a dot
+ if (unicode.Length > M_defaultNameLimit - (IsDot(unicode[unicode.Length-1])? 0 : 1))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadNameSize",
+ M_defaultNameLimit - (IsDot(unicode[unicode.Length-1]) ? 0 : 1)),
+ "unicode");
+
+ // If last char wasn't a dot we need to check for trailing -
+ if (bUseStd3 && !IsDot(unicode[unicode.Length-1]))
+ ValidateStd3(unicode[unicode.Length-1], true);
+
+ return true;
+ }
+
+ // Validate Std3 rules for a character
+ static void ValidateStd3(char c, bool bNextToDot)
+ {
+ // Check for illegal characters
+ if ((c <= ',' || c == '/' || (c >= ':' && c <= '@') || // Lots of characters not allowed
+ (c >= '[' && c <= '`') || (c >= '{' && c <= (char)0x7F)) ||
+ (c == '-' && bNextToDot))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadStd3", c), "Unicode");
+ }
+
+ //
+ // The following punycode implementation is ported from the sample punycode.c in RFC 3492
+ // Original sample code was written by Adam M. Costello.
+ //
+
+ // Return whether a punycode code point is flagged as being upper case.
+
+ static bool HasUpperCaseFlag(char punychar)
+ {
+ return (punychar >= 'A' && punychar <= 'Z');
+ }
+
+
+ /**********************************************************/
+ /* Implementation (would normally go in its own .c file): */
+
+ /*** Bootstring parameters for Punycode ***/
+ const int punycodeBase = 36;
+ const int tmin = 1;
+ const int tmax = 26;
+ const int skew = 38;
+ const int damp = 700;
+ const int initial_bias = 72;
+ const int initial_n = 0x80;
+ const char delimiter = '-';
+
+ /* basic(cp) tests whether cp is a basic code point: */
+ static bool basic(uint cp)
+ {
+ // Is it in ASCII range?
+ return cp < 0x80;
+ }
+
+ // decode_digit(cp) returns the numeric value of a basic code */
+ // point (for use in representing integers) in the range 0 to */
+ // punycodeBase-1, or <0 if cp is does not represent a value. */
+
+ static int decode_digit(char cp)
+ {
+ if (cp >= '0' && cp <= '9')
+ return cp - '0' + 26;
+
+ // Two flavors for case differences
+ if (cp >= 'a' && cp <= 'z')
+ return cp - 'a';
+
+ if (cp >= 'A' && cp <= 'Z')
+ return cp - 'A';
+
+ // Expected 0-9, A-Z or a-z, everything else is illegal
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "ascii");
+ }
+
+ /* encode_digit(d,flag) returns the basic code point whose value */
+ /* (when used for representing integers) is d, which needs to be in */
+ /* the range 0 to punycodeBase-1. The lowercase form is used unless flag is */
+ /* true, in which case the uppercase form is used. */
+
+ static char encode_digit(int d)
+ {
+ Contract.Assert(d >= 0 && d < punycodeBase, "[IdnMapping.encode_digit]Expected 0 <= d < punycodeBase");
+ // 26-35 map to ASCII 0-9
+ if (d > 25) return (char)(d - 26 + '0');
+
+ // 0-25 map to a-z or A-Z
+ return (char)(d + 'a');
+ }
+
+
+
+ /* encode_basic(bcp,flag) forces a basic code point to lowercase */
+ /* if flag is false, uppercase if flag is true, and returns */
+ /* the resulting code point. The code point is unchanged if it */
+ /* is caseless. The behavior is undefined if bcp is not a basic */
+ /* code point. */
+
+ static char encode_basic(char bcp)
+ {
+ if (HasUpperCaseFlag(bcp))
+ bcp += (char)('a' - 'A');
+
+ return bcp;
+ }
+
+ /*** Platform-specific constants ***/
+
+ /* maxint is the maximum value of a uint variable: */
+ const int maxint = 0x7ffffff;
+
+ /*** Bias adaptation function ***/
+
+ static int adapt(
+ int delta, int numpoints, bool firsttime )
+ {
+ uint k;
+
+ delta = firsttime ? delta / damp : delta / 2;
+ Contract.Assert(numpoints != 0, "[IdnMapping.adapt]Expected non-zero numpoints.");
+ delta += delta / numpoints;
+
+ for (k = 0; delta > ((punycodeBase - tmin) * tmax) / 2; k += punycodeBase)
+ {
+ delta /= punycodeBase - tmin;
+ }
+
+ Contract.Assert(delta + skew != 0, "[IdnMapping.adapt]Expected non-zero delta+skew.");
+ return (int)(k + (punycodeBase - tmin + 1) * delta / (delta + skew));
+ }
+
+ /*** Main encode function ***/
+
+ /* punycode_encode() converts Unicode to Punycode. The input */
+ /* is represented as an array of Unicode code points (not code */
+ /* units; surrogate pairs are not allowed), and the output */
+ /* will be represented as an array of ASCII code points. The */
+ /* output string is *not* null-terminated; it will contain */
+ /* zeros if and only if the input contains zeros. (Of course */
+ /* the caller can leave room for a terminator and add one if */
+ /* needed.) The input_length is the number of code points in */
+ /* the input. The output_length is an in/out argument: the */
+ /* caller passes in the maximum number of code points that it */
+
+ /* can receive, and on successful return it will contain the */
+ /* number of code points actually output. The case_flags array */
+ /* holds input_length boolean values, where nonzero suggests that */
+ /* the corresponding Unicode character be forced to uppercase */
+ /* after being decoded (if possible), and zero suggests that */
+ /* it be forced to lowercase (if possible). ASCII code points */
+ /* are encoded literally, except that ASCII letters are forced */
+ /* to uppercase or lowercase according to the corresponding */
+ /* uppercase flags. If case_flags is a null pointer then ASCII */
+ /* letters are left as they are, and other code points are */
+ /* treated as if their uppercase flags were zero. The return */
+ /* value can be any of the punycode_status values defined above */
+ /* except punycode_bad_input; if not punycode_success, then */
+ /* output_size and output might contain garbage. */
+
+ static String punycode_encode(String unicode)
+ {
+ // 0 length strings aren't allowed
+ if (unicode.Length == 0)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "unicode");
+ Contract.EndContractBlock();
+
+ StringBuilder output = new StringBuilder(unicode.Length);
+ int iNextDot = 0;
+ int iAfterLastDot = 0;
+ int iOutputAfterLastDot = 0;
+
+ // Find the next dot
+ while (iNextDot < unicode.Length)
+ {
+ // Find end of this segment
+ iNextDot = unicode.IndexOfAny(M_Dots, iAfterLastDot);
+ Contract.Assert(iNextDot <= unicode.Length, "[IdnMapping.punycode_encode]IndexOfAny is broken");
+ if (iNextDot < 0)
+ iNextDot = unicode.Length;
+
+ // Only allowed to have empty . section at end (www.microsoft.com.)
+ if (iNextDot == iAfterLastDot)
+ {
+ // Only allowed to have empty sections as trailing .
+ if (iNextDot != unicode.Length)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "unicode");
+ // Last dot, stop
+ break;
+ }
+
+ // We'll need an Ace prefix
+ output.Append(M_strAcePrefix);
+
+ // Everything resets every segment.
+ bool bRightToLeft = false;
+
+ // Check for RTL. If right-to-left, then 1st & last chars must be RTL
+ BidiCategory eBidi = CharUnicodeInfo.GetBidiCategory(unicode, iAfterLastDot);
+ if (eBidi == BidiCategory.RightToLeft || eBidi == BidiCategory.RightToLeftArabic)
+ {
+ // It has to be right to left.
+ bRightToLeft = true;
+
+ // Check last char
+ int iTest = iNextDot - 1;
+ if (Char.IsLowSurrogate(unicode, iTest))
+ {
+ iTest--;
+ }
+
+ eBidi = CharUnicodeInfo.GetBidiCategory(unicode, iTest);
+ if (eBidi != BidiCategory.RightToLeft && eBidi != BidiCategory.RightToLeftArabic)
+ {
+ // Oops, last wasn't RTL, last should be RTL if first is RTL
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadBidi"), "unicode");
+ }
+ }
+
+ // Handle the basic code points
+ int basicCount;
+ int numProcessed = 0; // Num code points that have been processed so far (this segment)
+ for (basicCount = iAfterLastDot; basicCount < iNextDot; basicCount++)
+ {
+ // Can't be lonely surrogate because it would've thrown in normalization
+ Contract.Assert(Char.IsLowSurrogate(unicode, basicCount) == false,
+ "[IdnMapping.punycode_encode]Unexpected low surrogate");
+
+ // Double check our bidi rules
+ BidiCategory testBidi = CharUnicodeInfo.GetBidiCategory(unicode, basicCount);
+
+ // If we're RTL, we can't have LTR chars
+ if (bRightToLeft && testBidi == BidiCategory.LeftToRight)
+ {
+ // Oops, throw error
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadBidi"), "unicode");
+ }
+
+ // If we're not RTL we can't have RTL chars
+ if (!bRightToLeft && (testBidi == BidiCategory.RightToLeft ||
+ testBidi == BidiCategory.RightToLeftArabic))
+ {
+ // Oops, throw error
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadBidi"), "unicode");
+ }
+
+ // If its basic then add it
+ if (basic(unicode[basicCount]))
+ {
+ output.Append(encode_basic(unicode[basicCount]));
+ numProcessed++;
+ }
+ // If its a surrogate, skip the next since our bidi category tester doesn't handle it.
+ else if (Char.IsSurrogatePair(unicode, basicCount))
+ basicCount++;
+ }
+
+ int numBasicCodePoints = numProcessed; // number of basic code points
+
+ // Stop if we ONLY had basic code points
+ if (numBasicCodePoints == iNextDot - iAfterLastDot)
+ {
+ // Get rid of xn-- and this segments done
+ output.Remove(iOutputAfterLastDot, M_strAcePrefix.Length);
+ }
+ else
+ {
+ // If it has some non-basic code points the input cannot start with xn--
+ if (unicode.Length - iAfterLastDot >= M_strAcePrefix.Length &&
+ unicode.Substring(iAfterLastDot, M_strAcePrefix.Length).Equals(
+ M_strAcePrefix, StringComparison.OrdinalIgnoreCase))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "unicode");
+
+ // Need to do ACE encoding
+ int numSurrogatePairs = 0; // number of surrogate pairs so far
+
+ // Add a delimiter (-) if we had any basic code points (between basic and encoded pieces)
+ if (numBasicCodePoints > 0)
+ {
+ output.Append(delimiter);
+ }
+
+ // Initialize the state
+ int n = initial_n;
+ int delta = 0;
+ int bias = initial_bias;
+
+ // Main loop
+ while (numProcessed < (iNextDot - iAfterLastDot))
+ {
+ /* All non-basic code points < n have been */
+ /* handled already. Find the next larger one: */
+ int j;
+ int m;
+ int test = 0;
+ for (m = maxint, j = iAfterLastDot;
+ j < iNextDot;
+ j += IsSupplementary(test) ? 2 : 1)
+ {
+ test = Char.ConvertToUtf32(unicode, j);
+ if (test >= n && test < m) m = test;
+ }
+
+ /* Increase delta enough to advance the decoder's */
+ /* <n,i> state to <m,0>, but guard against overflow: */
+ delta += (int)((m - n) * ((numProcessed - numSurrogatePairs) + 1));
+ Contract.Assert(delta > 0, "[IdnMapping.cs]1 punycode_encode - delta overflowed int");
+ n = m;
+
+ for (j = iAfterLastDot; j < iNextDot; j+= IsSupplementary(test) ? 2 : 1)
+ {
+ // Make sure we're aware of surrogates
+ test = Char.ConvertToUtf32(unicode, j);
+
+ // Adjust for character position (only the chars in our string already, some
+ // haven't been processed.
+
+ if (test < n)
+ {
+ delta++;
+ Contract.Assert(delta > 0, "[IdnMapping.cs]2 punycode_encode - delta overflowed int");
+ }
+
+ if (test == n)
+ {
+ // Represent delta as a generalized variable-length integer:
+ int q, k;
+ for (q = delta, k = punycodeBase; ; k += punycodeBase)
+ {
+ int t = k <= bias ? tmin :
+ k >= bias + tmax ? tmax : k - bias;
+ if (q < t) break;
+ Contract.Assert(punycodeBase != t, "[IdnMapping.punycode_encode]Expected punycodeBase (36) to be != t");
+ output.Append(encode_digit(t + (q - t) % (punycodeBase - t)));
+ q = (q - t) / (punycodeBase - t);
+ }
+
+ output.Append(encode_digit(q));
+ bias = adapt(delta, (numProcessed - numSurrogatePairs) + 1, numProcessed == numBasicCodePoints);
+ delta = 0;
+ numProcessed++;
+
+ if (IsSupplementary(m))
+ {
+ numProcessed++;
+ numSurrogatePairs++;
+ }
+ }
+ }
+ ++delta;
+ ++n;
+ Contract.Assert(delta > 0, "[IdnMapping.cs]3 punycode_encode - delta overflowed int");
+ }
+ }
+
+ // Make sure its not too big
+ if (output.Length - iOutputAfterLastDot > M_labelLimit)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "unicode");
+
+ // Done with this segment, add dot if necessary
+ if (iNextDot != unicode.Length)
+ output.Append('.');
+
+ iAfterLastDot = iNextDot + 1;
+ iOutputAfterLastDot = output.Length;
+ }
+
+ // Throw if we're too long
+ if (output.Length > M_defaultNameLimit - (IsDot(unicode[unicode.Length-1]) ? 0 : 1))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadNameSize",
+ M_defaultNameLimit - (IsDot(unicode[unicode.Length-1]) ? 0 : 1)),
+ "unicode");
+
+ // Return our output string
+ return output.ToString();
+ }
+
+ /*** Main decode function ***/
+
+ /* punycode_decode() converts Punycode to Unicode. The input is */
+ /* represented as an array of ASCII code points, and the output */
+ /* will be represented as an array of Unicode code points. The */
+ /* input_length is the number of code points in the input. The */
+ /* output_length is an in/out argument: the caller passes in */
+ /* the maximum number of code points that it can receive, and */
+ /* on successful return it will contain the actual number of */
+ /* code points output. The case_flags array needs room for at */
+ /* least output_length values, or it can be a null pointer if the */
+ /* case information is not needed. A nonzero flag suggests that */
+ /* the corresponding Unicode character be forced to uppercase */
+ /* by the caller (if possible), while zero suggests that it be */
+ /* forced to lowercase (if possible). ASCII code points are */
+ /* output already in the proper case, but their flags will be set */
+ /* appropriately so that applying the flags would be harmless. */
+ /* The return value can be any of the punycode_status values */
+ /* defined above; if not punycode_success, then output_length, */
+ /* output, and case_flags might contain garbage. On success, the */
+ /* decoder will never need to write an output_length greater than */
+ /* input_length, because of how the encoding is defined. */
+
+ static String punycode_decode( String ascii )
+ {
+ // 0 length strings aren't allowed
+ if (ascii.Length == 0)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "ascii");
+ Contract.EndContractBlock();
+
+ // Throw if we're too long
+ if (ascii.Length > M_defaultNameLimit - (IsDot(ascii[ascii.Length-1]) ? 0 : 1))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadNameSize",
+ M_defaultNameLimit - (IsDot(ascii[ascii.Length-1]) ? 0 : 1)), "ascii");
+
+ // output stringbuilder
+ StringBuilder output = new StringBuilder(ascii.Length);
+
+ // Dot searching
+ int iNextDot = 0;
+ int iAfterLastDot = 0;
+ int iOutputAfterLastDot = 0;
+
+ while (iNextDot < ascii.Length)
+ {
+ // Find end of this segment
+ iNextDot = ascii.IndexOf('.', iAfterLastDot);
+ if (iNextDot < 0 || iNextDot > ascii.Length)
+ iNextDot = ascii.Length;
+
+ // Only allowed to have empty . section at end (www.microsoft.com.)
+ if (iNextDot == iAfterLastDot)
+ {
+ // Only allowed to have empty sections as trailing .
+ if (iNextDot != ascii.Length)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "ascii");
+
+ // Last dot, stop
+ break;
+ }
+
+ // In either case it can't be bigger than segment size
+ if (iNextDot - iAfterLastDot > M_labelLimit)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "ascii");
+
+ // See if this section's ASCII or ACE
+ if (ascii.Length < M_strAcePrefix.Length + iAfterLastDot ||
+ !ascii.Substring(iAfterLastDot, M_strAcePrefix.Length).Equals(
+ M_strAcePrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ // Its supposed to be just ASCII
+ // Actually, for non xn-- stuff do we want to allow Unicode?
+ // for (int i = iAfterLastDot; i < iNextDot; i++)
+ // {
+ // // Only ASCII is allowed
+ // if (ascii[i] >= 0x80)
+ // throw new ArgumentException(Environment.GetResourceString(
+ // "Argument_IdnBadPunycode"), "ascii");
+// }
+
+ // Its ASCII, copy it
+ output.Append(ascii.Substring(iAfterLastDot, iNextDot - iAfterLastDot));
+
+ // ASCII doesn't have BIDI issues
+ }
+ else
+ {
+ // Not ASCII, bump up iAfterLastDot to be after ACE Prefix
+ iAfterLastDot += M_strAcePrefix.Length;
+
+ // Get number of basic code points (where delimiter is)
+ // numBasicCodePoints < 0 if there're no basic code points
+ int iTemp = ascii.LastIndexOf(delimiter, iNextDot - 1);
+
+ // Trailing - not allowed
+ if (iTemp == iNextDot - 1)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "ascii");
+
+ int numBasicCodePoints;
+ if (iTemp <= iAfterLastDot)
+ numBasicCodePoints = 0;
+ else
+ {
+ numBasicCodePoints = iTemp - iAfterLastDot;
+
+ // Copy all the basic code points, making sure they're all in the allowed range,
+ // and losing the casing for all of them.
+ for (int copyAscii = iAfterLastDot;
+ copyAscii < iAfterLastDot + numBasicCodePoints;
+ copyAscii++)
+ {
+ // Make sure we don't allow unicode in the ascii part
+ if (ascii[copyAscii] > 0x7f)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "ascii");
+
+ // When appending make sure they get lower cased
+ output.Append((char)(ascii[copyAscii] >= 'A' && ascii[copyAscii] <='Z' ?
+ ascii[copyAscii] - 'A' + 'a' :
+ ascii[copyAscii]));
+ }
+ }
+
+ // Get ready for main loop. Start at beginning if we didn't have any
+ // basic code points, otherwise start after the -.
+ // asciiIndex will be next character to read from ascii
+ int asciiIndex = iAfterLastDot +
+ ( numBasicCodePoints > 0 ? numBasicCodePoints + 1 : 0);
+
+ // initialize our state
+ int n = initial_n;
+ int bias = initial_bias;
+ int i = 0;
+
+ int w, k;
+
+ // no Supplementary characters yet
+ int numSurrogatePairs = 0;
+
+ // Main loop, read rest of ascii
+ while (asciiIndex < iNextDot)
+ {
+ /* Decode a generalized variable-length integer into delta, */
+ /* which gets added to i. The overflow checking is easier */
+ /* if we increase i as we go, then subtract off its starting */
+ /* value at the end to obtain delta. */
+ int oldi = i;
+
+ for (w = 1, k = punycodeBase; ; k += punycodeBase)
+ {
+ // Check to make sure we aren't overrunning our ascii string
+ if (asciiIndex >= iNextDot)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "ascii");
+
+ // decode the digit from the next char
+ int digit = decode_digit(ascii[asciiIndex++]);
+
+ Contract.Assert(w > 0, "[IdnMapping.punycode_decode]Expected w > 0");
+ if (digit > (maxint - i) / w)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "ascii");
+
+ i += (int)(digit * w);
+ int t = k <= bias ? tmin :
+ k >= bias + tmax ? tmax : k - bias;
+ if (digit < t) break;
+ Contract.Assert(punycodeBase != t, "[IdnMapping.punycode_decode]Expected t != punycodeBase (36)");
+ if (w > maxint / (punycodeBase - t))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "ascii");
+ w *= (punycodeBase - t);
+ }
+
+ bias = adapt(i - oldi,
+ (output.Length - iOutputAfterLastDot - numSurrogatePairs) + 1, oldi == 0);
+
+ /* i was supposed to wrap around from output.Length to 0, */
+ /* incrementing n each time, so we'll fix that now: */
+ Contract.Assert((output.Length - iOutputAfterLastDot - numSurrogatePairs) + 1 > 0,
+ "[IdnMapping.punycode_decode]Expected to have added > 0 characters this segment");
+ if (i / ((output.Length - iOutputAfterLastDot - numSurrogatePairs) + 1) > maxint - n)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "ascii");
+ n += (int)(i / (output.Length - iOutputAfterLastDot - numSurrogatePairs + 1));
+ i %= (output.Length - iOutputAfterLastDot - numSurrogatePairs + 1);
+
+ // If it was flagged it needs to be capitalized
+ // if (HasUpperCaseFlag(ascii[asciiIndex - 1]))
+ // {
+ // /* Case of last character determines uppercase flag: */
+ // // Any casing stuff need to happen last.
+ // If we wanted to reverse the IDNA casing data
+ // n = MakeNUpperCase(n)
+ // }
+
+ // Make sure n is legal
+ if ((n < 0 || n > 0x10ffff) || (n >= 0xD800 && n <= 0xDFFF))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "ascii");
+
+ // insert n at position i of the output: Really tricky if we have surrogates
+ int iUseInsertLocation;
+ String strTemp = Char.ConvertFromUtf32(n);
+
+ // If we have supplimentary characters
+ if (numSurrogatePairs > 0)
+ {
+ // Hard way, we have supplimentary characters
+ int iCount;
+ for (iCount = i, iUseInsertLocation = iOutputAfterLastDot;
+ iCount > 0;
+ iCount--, iUseInsertLocation++)
+ {
+ // If its a surrogate, we have to go one more
+ if (iUseInsertLocation >= output.Length)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadPunycode"), "ascii");
+ if (Char.IsSurrogate(output[iUseInsertLocation]))
+ iUseInsertLocation++;
+ }
+ }
+ else
+ {
+ // No Supplementary chars yet, just add i
+ iUseInsertLocation = iOutputAfterLastDot + i;
+ }
+
+ // Insert it
+ output.Insert(iUseInsertLocation, strTemp);
+
+ // If it was a surrogate increment our counter
+ if (IsSupplementary(n))
+ numSurrogatePairs++;
+
+ // Index gets updated
+ i++;
+ }
+
+ // Do BIDI testing
+ bool bRightToLeft = false;
+
+ // Check for RTL. If right-to-left, then 1st & last chars must be RTL
+ BidiCategory eBidi = CharUnicodeInfo.GetBidiCategory(output.ToString(), iOutputAfterLastDot);
+ if (eBidi == BidiCategory.RightToLeft || eBidi == BidiCategory.RightToLeftArabic)
+ {
+ // It has to be right to left.
+ bRightToLeft = true;
+ }
+
+ // Check the rest of them to make sure RTL/LTR is consistent
+ for (int iTest = iOutputAfterLastDot; iTest < output.Length; iTest++)
+ {
+ // This might happen if we run into a pair
+ if (Char.IsLowSurrogate(output.ToString(), iTest)) continue;
+
+ // Check to see if its LTR
+ eBidi = CharUnicodeInfo.GetBidiCategory(output.ToString(), iTest);
+ if ((bRightToLeft && eBidi == BidiCategory.LeftToRight) ||
+ (!bRightToLeft && (eBidi == BidiCategory.RightToLeft ||
+ eBidi == BidiCategory.RightToLeftArabic)))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadBidi"), "ascii");
+
+ // Make it lower case if we must (so we can test IsNormalized later)
+ // if (output[iTest] >= 'A' && output[iTest] <= 'Z')
+ // output[iTest] = (char)(output[iTest] + (char)('a' - 'A'));
+ }
+
+ // Its also a requirement that the last one be RTL if 1st is RTL
+ if (bRightToLeft && eBidi != BidiCategory.RightToLeft && eBidi != BidiCategory.RightToLeftArabic)
+ {
+ // Oops, last wasn't RTL, last should be RTL if first is RTL
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadBidi"), "ascii");
+ }
+ }
+
+ // See if this label was too long
+ if (iNextDot - iAfterLastDot > M_labelLimit)
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadLabelSize"), "ascii");
+
+ // Done with this segment, add dot if necessary
+ if (iNextDot != ascii.Length)
+ output.Append('.');
+
+ iAfterLastDot = iNextDot + 1;
+ iOutputAfterLastDot = output.Length;
+ }
+
+ // Throw if we're too long
+ if (output.Length > M_defaultNameLimit - (IsDot(output[output.Length-1]) ? 0 : 1))
+ throw new ArgumentException(Environment.GetResourceString(
+ "Argument_IdnBadNameSize",
+ M_defaultNameLimit -(IsDot(output[output.Length-1]) ? 0 : 1)), "ascii");
+
+ // Return our output string
+ return output.ToString();
+ }
+
+ /*
+ The previous punycode implimentation is based on the sample code in RFC 3492
+
+ Full Copyright Statement
+
+ Copyright (C) The Internet Society (2003). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implementation may be prepared, copied, published
+ and distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+
+ private const int IDN_ALLOW_UNASSIGNED = 0x1;
+ private const int IDN_USE_STD3_ASCII_RULES = 0x2;
+
+ private const int ERROR_INVALID_NAME = 123;
+
+
+ [System.Security.SecurityCritical]
+ [SuppressUnmanagedCodeSecurityAttribute()]
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
+ private static extern int IdnToAscii(
+ uint dwFlags,
+ [InAttribute()]
+ [MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
+ String lpUnicodeCharStr,
+ int cchUnicodeChar,
+ [System.Runtime.InteropServices.OutAttribute()]
+
+ char [] lpASCIICharStr,
+ int cchASCIIChar);
+
+ [System.Security.SecurityCritical]
+ [SuppressUnmanagedCodeSecurityAttribute()]
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
+ private static extern int IdnToUnicode(
+ uint dwFlags,
+ [InAttribute()]
+ [MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
+ string lpASCIICharStr,
+ int cchASCIIChar,
+ [System.Runtime.InteropServices.OutAttribute()]
+
+ char [] lpUnicodeCharStr,
+ int cchUnicodeChar);
+ }
+}
+
diff --git a/src/mscorlib/src/System/Globalization/JapaneseCalendar.cs b/src/mscorlib/src/System/Globalization/JapaneseCalendar.cs
new file mode 100644
index 0000000000..2984d08ab3
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/JapaneseCalendar.cs
@@ -0,0 +1,594 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using Microsoft.Win32;
+ using PermissionSet = System.Security.PermissionSet;
+ using System.Security.Permissions;
+
+ /*=================================JapaneseCalendar==========================
+ **
+ ** 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
+ ** 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 Heisei. It started on 1989/1/8 A.D. Therefore, Gregorian year 1989 is also Heisei 1st.
+ ** 1989/1/8 A.D. is also Heisei 1st 1/8.
+ **
+ ** Any date in the year during which era is changed can be reckoned in either era. For example,
+ ** 1989/1/1 can be 1/1 Heisei 1st year or 1/1 Showa 64th year.
+ **
+ ** Note:
+ ** The DateTime can be represented by the JapaneseCalendar are limited to two factors:
+ ** 1. The min value and max value of DateTime class.
+ ** 2. The available era information.
+ **
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1868/09/08 9999/12/31
+ ** Japanese Meiji 01/01 Heisei 8011/12/31
+ ============================================================================*/
+
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public class JapaneseCalendar : Calendar
+ {
+ internal static readonly DateTime calendarMinValue = new DateTime(1868, 9, 8);
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ // Return the type of the Japanese calendar.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ //
+ // Using a field initializer rather than a static constructor so that the whole class can be lazy
+ // init.
+ static internal volatile EraInfo[] japaneseEraInfo;
+
+ //
+ // Read our era info
+ //
+ // m_EraInfo must be listed in reverse chronological order. The most recent era
+ // should be the first element.
+ // That is, m_EraInfo[0] contains the most recent era.
+ //
+ // 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
+ //
+ // Registry values look like:
+ // yyyy.mm.dd=era_abbrev_english_englishabbrev
+ //
+ // Where yyyy.mm.dd is the registry value name, and also the date of the era start.
+ // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long)
+ // era is the Japanese Era name
+ // abbrev is the Abbreviated Japanese Era Name
+ // english is the English name for the Era (unused)
+ // englishabbrev is the Abbreviated English name for the era.
+ // . is a delimiter, but the value of . doesn't matter.
+ // '_' marks the space between the japanese era name, japanese abbreviated era name
+ // english name, and abbreviated english names.
+ //
+ internal static EraInfo[] GetEraInfo()
+ {
+ // See if we need to build it
+ if (japaneseEraInfo == null)
+ {
+ // See if we have any eras from the registry
+ japaneseEraInfo = GetErasFromRegistry();
+
+ // See if we have to use the built-in eras
+ if (japaneseEraInfo == null)
+ {
+ // We know about some built-in ranges
+ EraInfo[] defaultEraRanges = new EraInfo[4];
+ defaultEraRanges[0] = new EraInfo( 4, 1989, 1, 8, 1988, 1, GregorianCalendar.MaxYear - 1988,
+ "\x5e73\x6210", "\x5e73", "H"); // era #4 start year/month/day, yearOffset, minEraYear
+ defaultEraRanges[1] = new EraInfo( 3, 1926, 12, 25, 1925, 1, 1989-1925,
+ "\x662d\x548c", "\x662d", "S"); // era #3,start year/month/day, yearOffset, minEraYear
+ defaultEraRanges[2] = new EraInfo( 2, 1912, 7, 30, 1911, 1, 1926-1911,
+ "\x5927\x6b63", "\x5927", "T"); // era #2,start year/month/day, yearOffset, minEraYear
+ defaultEraRanges[3] = new EraInfo( 1, 1868, 1, 1, 1867, 1, 1912-1867,
+ "\x660e\x6cbb", "\x660e", "M"); // era #1,start year/month/day, yearOffset, minEraYear
+
+ // Remember the ranges we built
+ japaneseEraInfo = defaultEraRanges;
+ }
+ }
+
+ // return the era we found/made
+ return japaneseEraInfo;
+ }
+
+
+#if FEATURE_WIN32_REGISTRY
+ private const string c_japaneseErasHive = @"System\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras";
+ private const string c_japaneseErasHivePermissionList = @"HKEY_LOCAL_MACHINE\" + c_japaneseErasHive;
+
+ //
+ // GetErasFromRegistry()
+ //
+ // 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
+ //
+ // Registry values look like:
+ // yyyy.mm.dd=era_abbrev_english_englishabbrev
+ //
+ // Where yyyy.mm.dd is the registry value name, and also the date of the era start.
+ // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long)
+ // era is the Japanese Era name
+ // abbrev is the Abbreviated Japanese Era Name
+ // english is the English name for the Era (unused)
+ // englishabbrev is the Abbreviated English name for the era.
+ // . is a delimiter, but the value of . doesn't matter.
+ // '_' marks the space between the japanese era name, japanese abbreviated era name
+ // english name, and abbreviated english names.
+ [System.Security.SecuritySafeCritical] // auto-generated
+ private static EraInfo[] GetErasFromRegistry()
+ {
+ // Look in the registry key and see if we can find any ranges
+ int iFoundEras = 0;
+ EraInfo[] registryEraRanges = null;
+
+ try
+ {
+ // Need to access registry
+ PermissionSet permSet = new PermissionSet(PermissionState.None);
+ permSet.AddPermission(new RegistryPermission(RegistryPermissionAccess.Read, c_japaneseErasHivePermissionList));
+ permSet.Assert();
+ RegistryKey key = Registry.LocalMachine.OpenSubKey(c_japaneseErasHive, writable: false);
+
+ // Abort if we didn't find anything
+ if (key == null) return null;
+
+ // Look up the values in our reg key
+ String[] valueNames = key.GetValueNames();
+ if (valueNames != null && valueNames.Length > 0)
+ {
+ registryEraRanges = new EraInfo[valueNames.Length];
+
+ // Loop through the registry and read in all the values
+ for (int i = 0; i < valueNames.Length; i++)
+ {
+ // See if the era is a valid date
+ EraInfo era = GetEraFromValue(valueNames[i], key.GetValue(valueNames[i]).ToString());
+
+ // continue if not valid
+ if (era == null) continue;
+
+ // Remember we found one.
+ registryEraRanges[iFoundEras] = era;
+ iFoundEras++;
+ }
+ }
+ }
+ catch (System.Security.SecurityException)
+ {
+ // If we weren't allowed to read, then just ignore the error
+ return null;
+ }
+ catch (System.IO.IOException)
+ {
+ // If key is being deleted just ignore the error
+ return null;
+ }
+ catch (System.UnauthorizedAccessException)
+ {
+ // Registry access rights permissions, just ignore the error
+ return null;
+ }
+
+ //
+ // If we didn't have valid eras, then fail
+ // should have at least 4 eras
+ //
+ if (iFoundEras < 4) return null;
+
+ //
+ // Now we have eras, clean them up.
+ //
+ // Clean up array length
+ Array.Resize(ref registryEraRanges, iFoundEras);
+
+ // Sort them
+ Array.Sort(registryEraRanges, CompareEraRanges);
+
+ // Clean up era information
+ for (int i = 0; i < registryEraRanges.Length; i++)
+ {
+ // eras count backwards from length to 1 (and are 1 based indexes into string arrays)
+ registryEraRanges[i].era = registryEraRanges.Length - i;
+
+ // update max era year
+ if (i == 0)
+ {
+ // First range is 'til the end of the calendar
+ registryEraRanges[0].maxEraYear = GregorianCalendar.MaxYear - registryEraRanges[0].yearOffset;
+ }
+ else
+ {
+ // Rest are until the next era (remember most recent era is first in array)
+ registryEraRanges[i].maxEraYear = registryEraRanges[i-1].yearOffset + 1 - registryEraRanges[i].yearOffset;
+ }
+ }
+
+ // Return our ranges
+ return registryEraRanges;
+ }
+#else
+ private static EraInfo[] GetErasFromRegistry()
+ {
+ return null;
+ }
+#endif
+
+ //
+ // Compare two era ranges, eg just the ticks
+ // Remember the era array is supposed to be in reverse chronological order
+ //
+ private static int CompareEraRanges(EraInfo a, EraInfo b)
+ {
+ return b.ticks.CompareTo(a.ticks);
+ }
+
+ //
+ // GetEraFromValue
+ //
+ // Parse the registry value name/data pair into an era
+ //
+ // Registry values look like:
+ // yyyy.mm.dd=era_abbrev_english_englishabbrev
+ //
+ // Where yyyy.mm.dd is the registry value name, and also the date of the era start.
+ // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long)
+ // era is the Japanese Era name
+ // abbrev is the Abbreviated Japanese Era Name
+ // english is the English name for the Era (unused)
+ // englishabbrev is the Abbreviated English name for the era.
+ // . is a delimiter, but the value of . doesn't matter.
+ // '_' marks the space between the japanese era name, japanese abbreviated era name
+ // english name, and abbreviated english names.
+ private static EraInfo GetEraFromValue(String value, String data)
+ {
+ // Need inputs
+ if (value == null || data == null) return null;
+
+ //
+ // Get Date
+ //
+ // Need exactly 10 characters in name for date
+ // yyyy.mm.dd although the . can be any character
+ if (value.Length != 10) return null;
+
+ int year;
+ int month;
+ int day;
+
+ if (!Number.TryParseInt32(value.Substring(0,4), NumberStyles.None, NumberFormatInfo.InvariantInfo, out year) ||
+ !Number.TryParseInt32(value.Substring(5,2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out month) ||
+ !Number.TryParseInt32(value.Substring(8,2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out day))
+ {
+ // Couldn't convert integer, fail
+ return null;
+ }
+
+ //
+ // Get Strings
+ //
+ // Needs to be a certain length e_a_E_A at least (7 chars, exactly 4 groups)
+ String[] names = data.Split(new char[] {'_'});
+
+ // Should have exactly 4 parts
+ // 0 - Era Name
+ // 1 - Abbreviated Era Name
+ // 2 - English Era Name
+ // 3 - Abbreviated English Era Name
+ if (names.Length != 4) return null;
+
+ // Each part should have data in it
+ if (names[0].Length == 0 ||
+ names[1].Length == 0 ||
+ names[2].Length == 0 ||
+ names[3].Length == 0)
+ return null;
+
+ //
+ // Now we have an era we can build
+ // Note that the era # and max era year need cleaned up after sorting
+ // Don't use the full English Era Name (names[2])
+ //
+ return new EraInfo( 0, year, month, day, year - 1, 1, 0,
+ names[0], names[1], names[3]);
+ }
+
+ internal static volatile Calendar s_defaultInstance;
+ internal GregorianCalendarHelper helper;
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of JapaneseCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ internal static Calendar GetDefaultInstance() {
+ if (s_defaultInstance == null) {
+ s_defaultInstance = new JapaneseCalendar();
+ }
+ return (s_defaultInstance);
+ }
+
+
+ public JapaneseCalendar() {
+ try {
+ new CultureInfo("ja-JP");
+ } catch (ArgumentException e) {
+ throw new TypeInitializationException(this.GetType().FullName, e);
+ }
+ helper = new GregorianCalendarHelper(this, GetEraInfo());
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_JAPAN);
+ }
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months) {
+ return (helper.AddMonths(time, months));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years) {
+ return (helper.AddYears(time, years));
+ }
+
+ /*=================================GetDaysInMonth==========================
+ **Action: Returns the number of days in the month given by the year and month arguments.
+ **Returns: The number of days in the given month.
+ **Arguments:
+ ** year The year in Japanese calendar.
+ ** month The month
+ ** era The Japanese era value.
+ **Exceptions
+ ** ArgumentException If month is less than 1 or greater * than 12.
+ ============================================================================*/
+
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ return (helper.GetDaysInMonth(year, month, era));
+ }
+
+
+ public override int GetDaysInYear(int year, int era) {
+ return (helper.GetDaysInYear(year, era));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time) {
+ return (helper.GetDayOfMonth(time));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ return (helper.GetDayOfWeek(time));
+ }
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (helper.GetDayOfYear(time));
+ }
+
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ return (helper.GetMonthsInYear(year, era));
+ }
+
+
+ [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ return (helper.GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+ /*=================================GetEra==========================
+ **Action: Get the era value of the specified time.
+ **Returns: The era value for the specified time.
+ **Arguments:
+ ** time the specified date time.
+ **Exceptions: ArgumentOutOfRangeException if time is out of the valid era ranges.
+ ============================================================================*/
+
+
+ public override int GetEra(DateTime time) {
+ return (helper.GetEra(time));
+ }
+
+
+ public override int GetMonth(DateTime time) {
+ return (helper.GetMonth(time));
+ }
+
+
+ public override int GetYear(DateTime time) {
+ return (helper.GetYear(time));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ return (helper.IsLeapDay(year, month, day, era));
+ }
+
+
+ public override bool IsLeapYear(int year, int era) {
+ return (helper.IsLeapYear(year, era));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetLeapMonth(int year, int era)
+ {
+ return (helper.GetLeapMonth(year, era));
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era) {
+ return (helper.IsLeapMonth(year, month, era));
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ return (helper.ToDateTime(year, month, day, hour, minute, second, millisecond, era));
+ }
+
+ // For Japanese calendar, four digit year is not used. Few emperors will live for more than one hundred years.
+ // Therefore, for any two digit number, we just return the original number.
+
+ public override int ToFourDigitYear(int year) {
+ if (year <= 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (year > helper.MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ helper.MaxYear));
+ }
+ return (year);
+ }
+
+
+ public override int[] Eras {
+ get {
+ return (helper.Eras);
+ }
+ }
+
+ //
+ // Return the various era strings
+ // Note: The arrays are backwards of the eras
+ //
+ internal static String[] EraNames()
+ {
+ EraInfo[] eras = GetEraInfo();
+ String[] eraNames = new String[eras.Length];
+
+ for (int i = 0; i < eras.Length; i++)
+ {
+ // Strings are in chronological order, eras are backwards order.
+ eraNames[i] = eras[eras.Length - i - 1].eraName;
+ }
+
+ return eraNames;
+ }
+
+ internal static String[] AbbrevEraNames()
+ {
+ EraInfo[] eras = GetEraInfo();
+ String[] erasAbbrev = new String[eras.Length];
+
+ for (int i = 0; i < eras.Length; i++)
+ {
+ // Strings are in chronological order, eras are backwards order.
+ erasAbbrev[i] = eras[eras.Length - i - 1].abbrevEraName;
+ }
+
+ return erasAbbrev;
+ }
+
+ internal static String[] EnglishEraNames()
+ {
+ EraInfo[] eras = GetEraInfo();
+ String[] erasEnglish = new String[eras.Length];
+
+ for (int i = 0; i < eras.Length; i++)
+ {
+ // Strings are in chronological order, eras are backwards order.
+ erasEnglish[i] = eras[eras.Length - i - 1].englishEraName;
+ }
+
+ return erasEnglish;
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 99;
+
+ internal override bool IsValidYear(int year, int era) {
+ return helper.IsValidYear(year, era);
+ }
+
+ public override int TwoDigitYearMax {
+ get {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value < 99 || value > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 99,
+ helper.MaxYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/JapaneseLunisolarCalendar.cs b/src/mscorlib/src/System/Globalization/JapaneseLunisolarCalendar.cs
new file mode 100644
index 0000000000..67cfb23833
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/JapaneseLunisolarCalendar.cs
@@ -0,0 +1,293 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+ using System.Diagnostics.Contracts;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Notes about JapaneseLunisolarCalendar
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1960/01/28 2050/01/22
+ ** JapaneseLunisolar 1960/01/01 2049/12/29
+ */
+
+ [Serializable]
+ public class JapaneseLunisolarCalendar : EastAsianLunisolarCalendar {
+
+ //
+ // The era value for the current era.
+ //
+
+ public const int JapaneseEra = 1;
+
+ internal GregorianCalendarHelper helper;
+
+ internal const int MIN_LUNISOLAR_YEAR = 1960;
+ internal const int MAX_LUNISOLAR_YEAR = 2049;
+
+ internal const int MIN_GREGORIAN_YEAR = 1960;
+ internal const int MIN_GREGORIAN_MONTH = 1;
+ internal const int MIN_GREGORIAN_DAY = 28;
+
+ internal const int MAX_GREGORIAN_YEAR = 2050;
+ internal const int MAX_GREGORIAN_MONTH = 1;
+ internal const int MAX_GREGORIAN_DAY = 22;
+
+ internal static DateTime minDate = new DateTime(MIN_GREGORIAN_YEAR, MIN_GREGORIAN_MONTH, MIN_GREGORIAN_DAY);
+ internal static DateTime maxDate = new DateTime((new DateTime(MAX_GREGORIAN_YEAR, MAX_GREGORIAN_MONTH, MAX_GREGORIAN_DAY, 23, 59, 59, 999)).Ticks + 9999);
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // 1959 from ChineseLunisolarCalendar
+ return 354;
+ }
+ }
+
+ static readonly int [,] yinfo =
+ {
+/*Y LM Lmon Lday DaysPerMonth D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 #Days
+1960 */{ 6 , 1 , 28 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1961 */{ 0 , 2 , 15 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1962 */{ 0 , 2 , 5 , 19808 },/* 29 30 29 29 30 30 29 30 29 30 30 29 0 354
+1963 */{ 4 , 1 , 25 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1964 */{ 0 , 2 , 13 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1965 */{ 0 , 2 , 2 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 0 354
+1966 */{ 3 , 1 , 22 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+1967 */{ 0 , 2 , 9 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1968 */{ 7 , 1 , 30 , 27304 },/* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1969 */{ 0 , 2 , 17 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1970 */{ 0 , 2 , 6 , 39632 },/* 30 29 29 30 30 29 30 29 30 30 29 30 0 355
+1971 */{ 5 , 1 , 27 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1972 */{ 0 , 2 , 15 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1973 */{ 0 , 2 , 3 , 42208 },/* 30 29 30 29 29 30 29 29 30 30 30 29 0 354
+1974 */{ 4 , 1 , 23 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1975 */{ 0 , 2 , 11 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1976 */{ 8 , 1 , 31 , 54600 },/* 30 30 29 30 29 30 29 30 29 30 29 29 30 384
+1977 */{ 0 , 2 , 18 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1978 */{ 0 , 2 , 7 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 0 355
+1979 */{ 6 , 1 , 28 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1980 */{ 0 , 2 , 16 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1981 */{ 0 , 2 , 5 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1982 */{ 4 , 1 , 25 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1983 */{ 0 , 2 , 13 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1984 */{ 10 , 2 , 2 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1985 */{ 0 , 2 , 20 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1986 */{ 0 , 2 , 9 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1987 */{ 6 , 1 , 29 , 46504 },/* 30 29 30 30 29 30 29 30 30 29 30 29 30 385
+1988 */{ 0 , 2 , 18 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1989 */{ 0 , 2 , 6 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1990 */{ 5 , 1 , 27 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1991 */{ 0 , 2 , 15 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1992 */{ 0 , 2 , 4 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1993 */{ 3 , 1 , 23 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1994 */{ 0 , 2 , 10 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1995 */{ 8 , 1 , 31 , 27976 },/* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1996 */{ 0 , 2 , 19 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1997 */{ 0 , 2 , 8 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1998 */{ 5 , 1 , 28 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1999 */{ 0 , 2 , 16 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+2000 */{ 0 , 2 , 5 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+2001 */{ 4 , 1 , 24 , 58536 },/* 30 30 30 29 29 30 29 29 30 29 30 29 30 384
+2002 */{ 0 , 2 , 12 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+2003 */{ 0 , 2 , 1 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+2004 */{ 2 , 1 , 22 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2005 */{ 0 , 2 , 9 , 22208 },/* 29 30 29 30 29 30 30 29 30 30 29 29 0 354
+2006 */{ 7 , 1 , 29 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+2007 */{ 0 , 2 , 18 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+2008 */{ 0 , 2 , 7 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+2009 */{ 5 , 1 , 26 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+2010 */{ 0 , 2 , 14 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2011 */{ 0 , 2 , 3 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+2012 */{ 3 , 1 , 23 , 47696 },/* 30 29 30 30 30 29 30 29 29 30 29 30 29 384
+2013 */{ 0 , 2 , 10 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+2014 */{ 9 , 1 , 31 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2015 */{ 0 , 2 , 19 , 19360 },/* 29 30 29 29 30 29 30 30 30 29 30 29 0 354
+2016 */{ 0 , 2 , 8 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+2017 */{ 5 , 1 , 28 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+2018 */{ 0 , 2 , 16 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+2019 */{ 0 , 2 , 5 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2020 */{ 4 , 1 , 25 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+2021 */{ 0 , 2 , 12 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+2022 */{ 0 , 2 , 1 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2023 */{ 2 , 1 , 22 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2024 */{ 0 , 2 , 10 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+2025 */{ 6 , 1 , 29 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+2026 */{ 0 , 2 , 17 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+2027 */{ 0 , 2 , 7 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 0 354
+2028 */{ 5 , 1 , 27 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+2029 */{ 0 , 2 , 13 , 55600 },/* 30 30 29 30 30 29 29 30 29 29 30 30 0 355
+2030 */{ 0 , 2 , 3 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+2031 */{ 3 , 1 , 23 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+2032 */{ 0 , 2 , 11 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2033 */{ 11 , 1 , 31 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+2034 */{ 0 , 2 , 19 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+2035 */{ 0 , 2 , 8 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+2036 */{ 6 , 1 , 28 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+2037 */{ 0 , 2 , 15 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+2038 */{ 0 , 2 , 4 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+2039 */{ 5 , 1 , 24 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 29 384
+2040 */{ 0 , 2 , 12 , 46752 },/* 30 29 30 30 29 30 30 29 30 29 30 29 0 355
+2041 */{ 0 , 2 , 1 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2042 */{ 2 , 1 , 22 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+2043 */{ 0 , 2 , 10 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+2044 */{ 7 , 1 , 30 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+2045 */{ 0 , 2 , 17 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+2046 */{ 0 , 2 , 6 , 45648 },/* 30 29 30 30 29 29 30 29 29 30 29 30 0 354
+2047 */{ 5 , 1 , 26 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+2048 */{ 0 , 2 , 14 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+2049 */{ 0 , 2 , 2 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+ */ };
+
+ internal override int MinCalendarYear {
+ get
+ {
+ return (MIN_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override int MaxCalendarYear {
+ get
+ {
+ return (MAX_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override DateTime MinDate {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ internal override DateTime MaxDate {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ internal override EraInfo[] CalEraInfo {
+ get
+ {
+ return (JapaneseCalendar.GetEraInfo());
+ }
+ }
+
+ internal override int GetYearInfo(int LunarYear, int Index) {
+ if ((LunarYear < MIN_LUNISOLAR_YEAR) || (LunarYear > MAX_LUNISOLAR_YEAR)) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ MIN_LUNISOLAR_YEAR,
+ MAX_LUNISOLAR_YEAR ));
+ }
+ Contract.EndContractBlock();
+
+ return yinfo[LunarYear - MIN_LUNISOLAR_YEAR, Index];
+ }
+
+ internal override int GetYear(int year, DateTime time) {
+ return helper.GetYear(year, time);
+ }
+
+ internal override int GetGregorianYear(int year, int era) {
+ return helper.GetGregorianYear(year, era);
+ }
+
+ // Trim off the eras that are before our date range
+ private static EraInfo[] TrimEras(EraInfo[] baseEras)
+ {
+ EraInfo[] newEras = new EraInfo[baseEras.Length];
+ int newIndex = 0;
+
+ // Eras have most recent first, so start with that
+ for (int i = 0; i < baseEras.Length; i++)
+ {
+ // If this one's minimum year is bigger than our maximum year
+ // then we can't use it.
+ if (baseEras[i].yearOffset + baseEras[i].minEraYear >= MAX_LUNISOLAR_YEAR)
+ {
+ // skip this one.
+ continue;
+ }
+
+ // If this one's maximum era is less than our minimum era
+ // then we've gotten too low in the era #s, so we're done
+ if (baseEras[i].yearOffset + baseEras[i].maxEraYear < MIN_LUNISOLAR_YEAR)
+ {
+ break;
+ }
+
+ // Wasn't too large or too small, can use this one
+ newEras[newIndex] = baseEras[i];
+ newIndex++;
+ }
+
+ // If we didn't copy any then something was wrong, just return base
+ if (newIndex == 0) return baseEras;
+
+ // Resize the output array
+ Array.Resize(ref newEras, newIndex);
+ return newEras;
+ }
+
+
+ // Construct an instance of JapaneseLunisolar calendar.
+ public JapaneseLunisolarCalendar()
+ {
+ helper = new GregorianCalendarHelper(this, TrimEras(JapaneseCalendar.GetEraInfo()));
+ }
+
+
+ public override int GetEra(DateTime time) {
+ return (helper.GetEra(time));
+ }
+
+ internal override int BaseCalendarID {
+ get {
+ return (CAL_JAPAN);
+ }
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_JAPANESELUNISOLAR);
+ }
+ }
+
+
+ public override int[] Eras {
+ get {
+ return (helper.Eras);
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/JulianCalendar.cs b/src/mscorlib/src/System/Globalization/JulianCalendar.cs
new file mode 100644
index 0000000000..9c8db3415c
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/JulianCalendar.cs
@@ -0,0 +1,441 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+
+ using System;
+ using System.Diagnostics.Contracts;
+ //
+ // This class implements the Julian calendar. In 48 B.C. Julius Caesar ordered a calendar reform, and this calendar
+ // is called Julian calendar. It consisted of a solar year of twelve months and of 365 days with an extra day
+ // every fourth year.
+ //*
+ //* Calendar support range:
+ //* Calendar Minimum Maximum
+ //* ========== ========== ==========
+ //* Gregorian 0001/01/01 9999/12/31
+ //* Julia 0001/01/03 9999/10/19
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public class JulianCalendar : Calendar {
+
+
+ public static readonly int JulianEra = 1;
+
+ private const int DatePartYear = 0;
+ private const int DatePartDayOfYear = 1;
+ private const int DatePartMonth = 2;
+ private const int DatePartDay = 3;
+
+ // Number of days in a non-leap year
+ private const int JulianDaysPerYear = 365;
+ // Number of days in 4 years
+ private const int JulianDaysPer4Years = JulianDaysPerYear * 4 + 1;
+
+ //internal static Calendar m_defaultInstance;
+
+ private static readonly int[] DaysToMonth365 =
+ {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
+ };
+
+ private static readonly int[] DaysToMonth366 =
+ {
+ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
+ };
+
+ // Gregorian Calendar 9999/12/31 = Julian Calendar 9999/10/19
+ // keep it as variable field for serialization compat.
+ internal int MaxYear = 9999;
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ // Return the type of the Julian calendar.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of JulianCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+ /*
+ internal static Calendar GetDefaultInstance() {
+ if (m_defaultInstance == null) {
+ m_defaultInstance = new JulianCalendar();
+ }
+ return (m_defaultInstance);
+ }
+ */
+
+ // Construct an instance of gregorian calendar.
+
+ public JulianCalendar() {
+ // There is no system setting of TwoDigitYear max, so set the value here.
+ twoDigitYearMax = 2029;
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_JULIAN);
+ }
+ }
+
+ static internal void CheckEraRange(int era) {
+ if (era != CurrentEra && era != JulianEra) {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+ }
+
+ internal void CheckYearEraRange(int year, int era) {
+ CheckEraRange(era);
+ if (year <= 0 || year > MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ MaxYear));
+ }
+ }
+
+ static internal void CheckMonthRange(int month) {
+ if (month < 1 || month > 12) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
+ }
+ }
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Check for if the day value is valid.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ **Notes:
+ ** Before calling this method, call CheckYearEraRange()/CheckMonthRange() to make
+ ** sure year/month values are correct.
+ ============================================================================*/
+
+ static internal void CheckDayRange(int year, int month, int day) {
+ if (year == 1 && month == 1)
+ {
+ // The mimimum supported Julia date is Julian 0001/01/03.
+ if (day < 3) {
+ throw new ArgumentOutOfRangeException(null,
+ Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
+ }
+ }
+ bool isLeapYear = (year % 4) == 0;
+ int[] days = isLeapYear ? DaysToMonth366 : DaysToMonth365;
+ int monthDays = days[month] - days[month - 1];
+ if (day < 1 || day > monthDays) {
+ throw new ArgumentOutOfRangeException(
+ "day",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ monthDays));
+ }
+ }
+
+
+ // Returns a given date part of this DateTime. This method is used
+ // to compute the year, day-of-year, month, or day part.
+ static internal int GetDatePart(long ticks, int part)
+ {
+ // Gregorian 1/1/0001 is Julian 1/3/0001. Remember DateTime(0) is refered to Gregorian 1/1/0001.
+ // The following line convert Gregorian ticks to Julian ticks.
+ long julianTicks = ticks + TicksPerDay * 2;
+ // n = number of days since 1/1/0001
+ int n = (int)(julianTicks / TicksPerDay);
+ // y4 = number of whole 4-year periods within 100-year period
+ int y4 = n / JulianDaysPer4Years;
+ // n = day number within 4-year period
+ n -= y4 * JulianDaysPer4Years;
+ // y1 = number of whole years within 4-year period
+ int y1 = n / JulianDaysPerYear;
+ // Last year has an extra day, so decrement result if 4
+ if (y1 == 4) y1 = 3;
+ // If year was requested, compute and return it
+ if (part == DatePartYear)
+ {
+ return (y4 * 4 + y1 + 1);
+ }
+ // n = day number within year
+ n -= y1 * JulianDaysPerYear;
+ // If day-of-year was requested, return it
+ if (part == DatePartDayOfYear)
+ {
+ return (n + 1);
+ }
+ // Leap year calculation looks different from IsLeapYear since y1, y4,
+ // and y100 are relative to year 1, not year 0
+ bool leapYear = (y1 == 3);
+ int[] days = leapYear? DaysToMonth366: DaysToMonth365;
+ // All months have less than 32 days, so n >> 5 is a good conservative
+ // estimate for the month
+ int m = n >> 5 + 1;
+ // m = 1-based month number
+ while (n >= days[m]) m++;
+ // If month was requested, return it
+ if (part == DatePartMonth) return (m);
+ // Return 1-based day-of-month
+ return (n - days[m - 1] + 1);
+ }
+
+ // Returns the tick count corresponding to the given year, month, and day.
+ static internal long DateToTicks(int year, int month, int day)
+ {
+ int[] days = (year % 4 == 0)? DaysToMonth366: DaysToMonth365;
+ int y = year - 1;
+ int n = y * 365 + y / 4 + days[month - 1] + day - 1;
+ // Gregorian 1/1/0001 is Julian 1/3/0001. n * TicksPerDay is the ticks in JulianCalendar.
+ // Therefore, we subtract two days in the following to convert the ticks in JulianCalendar
+ // to ticks in Gregorian calendar.
+ return ((n - 2) * TicksPerDay);
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months)
+ {
+ if (months < -120000 || months > 120000) {
+ throw new ArgumentOutOfRangeException(
+ "months",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ -120000,
+ 120000));
+ }
+ Contract.EndContractBlock();
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+ int i = m - 1 + months;
+ if (i >= 0) {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ }
+ else {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int[] daysArray = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? DaysToMonth366: DaysToMonth365;
+ int days = (daysArray[m] - daysArray[m - 1]);
+
+ if (d > days) {
+ d = days;
+ }
+ long ticks = DateToTicks(y, m, d) + time.Ticks % TicksPerDay;
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years) {
+ return (AddMonths(time, years * 12));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+
+ public override int GetDayOfYear(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartDayOfYear));
+ }
+
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ CheckYearEraRange(year, era);
+ CheckMonthRange(month);
+ int[] days = (year % 4 == 0) ? DaysToMonth366: DaysToMonth365;
+ return (days[month] - days[month - 1]);
+ }
+
+
+ public override int GetDaysInYear(int year, int era) {
+ // Year/Era range is done in IsLeapYear().
+ return (IsLeapYear(year, era) ? 366:365);
+ }
+
+
+ public override int GetEra(DateTime time)
+ {
+ return (JulianEra);
+ }
+
+
+ public override int GetMonth(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+
+ public override int[] Eras {
+ get {
+ return (new int[] {JulianEra});
+ }
+ }
+
+
+ public override int GetMonthsInYear(int year, int era)
+ {
+ CheckYearEraRange(year, era);
+ return (12);
+ }
+
+
+ public override int GetYear(DateTime time)
+ {
+ return (GetDatePart(time.Ticks, DatePartYear));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ CheckMonthRange(month);
+ // Year/Era range check is done in IsLeapYear().
+ if (IsLeapYear(year, era)) {
+ CheckDayRange(year, month, day);
+ return (month == 2 && day == 29);
+ }
+ CheckDayRange(year, month, day);
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetLeapMonth(int year, int era)
+ {
+ CheckYearEraRange(year, era);
+ return (0);
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era)
+ {
+ CheckYearEraRange(year, era);
+ CheckMonthRange(month);
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ CheckYearEraRange(year, era);
+ return (year % 4 == 0);
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era)
+ {
+ CheckYearEraRange(year, era);
+ CheckMonthRange(month);
+ CheckDayRange(year, month, day);
+ if (millisecond < 0 || millisecond >= MillisPerSecond) {
+ throw new ArgumentOutOfRangeException(
+ "millisecond",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ MillisPerSecond - 1));
+ }
+
+ if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >=0 && second < 60)
+ {
+ return new DateTime(DateToTicks(year, month, day) + (new TimeSpan(0, hour, minute, second, millisecond)).Ticks);
+ } else
+ {
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadHourMinuteSecond"));
+ }
+ }
+
+
+ public override int TwoDigitYearMax {
+ get {
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value < 99 || value > MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 99,
+ MaxYear));
+
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (year > MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Bounds_Lower_Upper"),
+ 1,
+ MaxYear));
+ }
+ return (base.ToFourDigitYear(year));
+ }
+ }
+
+}
diff --git a/src/mscorlib/src/System/Globalization/KoreanCalendar.cs b/src/mscorlib/src/System/Globalization/KoreanCalendar.cs
new file mode 100644
index 0000000000..9343884445
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/KoreanCalendar.cs
@@ -0,0 +1,265 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+
+ /*=================================KoreanCalendar==========================
+ **
+ ** Korean calendar is based on the Gregorian calendar. And the year is an offset to Gregorian calendar.
+ ** That is,
+ ** Korean year = Gregorian year + 2333. So 2000/01/01 A.D. is Korean 4333/01/01
+ **
+ ** 0001/1/1 A.D. is Korean year 2334.
+ **
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 0001/01/01 9999/12/31
+ ** Korean 2334/01/01 12332/12/31
+ ============================================================================*/
+
+
+[System.Runtime.InteropServices.ComVisible(true)]
+ [Serializable] public class KoreanCalendar: Calendar {
+ //
+ // The era value for the current era.
+ //
+
+ public const int KoreanEra = 1;
+
+ // Since
+ // Gregorian Year = Era Year + yearOffset
+ // Gregorian Year 1 is Korean year 2334, so that
+ // 1 = 2334 + yearOffset
+ // So yearOffset = -2333
+ // Gregorian year 2001 is Korean year 4334.
+
+ //m_EraInfo[0] = new EraInfo(1, new DateTime(1, 1, 1).Ticks, -2333, 2334, GregorianCalendar.MaxYear + 2333);
+
+ // Initialize our era info.
+ static internal EraInfo[] koreanEraInfo = new EraInfo[] {
+ new EraInfo( 1, 1, 1, 1, -2333, 2334, GregorianCalendar.MaxYear + 2333) // era #, start year/month/day, yearOffset, minEraYear
+ };
+
+ internal GregorianCalendarHelper helper;
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ // Return the type of the Korean calendar.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of KoreanCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+ /*
+ internal static Calendar GetDefaultInstance() {
+ if (m_defaultInstance == null) {
+ m_defaultInstance = new KoreanCalendar();
+ }
+ return (m_defaultInstance);
+ }
+ */
+
+
+ public KoreanCalendar() {
+ try {
+ new CultureInfo("ko-KR");
+ } catch (ArgumentException e) {
+ throw new TypeInitializationException(this.GetType().FullName, e);
+ }
+ helper = new GregorianCalendarHelper(this, koreanEraInfo);
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_KOREA);
+ }
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months) {
+ return (helper.AddMonths(time, months));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years) {
+ return (helper.AddYears(time, years));
+ }
+
+ /*=================================GetDaysInMonth==========================
+ **Action: Returns the number of days in the month given by the year and month arguments.
+ **Returns: The number of days in the given month.
+ **Arguments:
+ ** year The year in Korean calendar.
+ ** month The month
+ ** era The Japanese era value.
+ **Exceptions
+ ** ArgumentException If month is less than 1 or greater * than 12.
+ ============================================================================*/
+
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ return (helper.GetDaysInMonth(year, month, era));
+ }
+
+
+ public override int GetDaysInYear(int year, int era) {
+ return (helper.GetDaysInYear(year, era));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time) {
+ return (helper.GetDayOfMonth(time));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ return (helper.GetDayOfWeek(time));
+ }
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (helper.GetDayOfYear(time));
+ }
+
+
+ public override int GetMonthsInYear(int year, int era) {
+ return (helper.GetMonthsInYear(year, era));
+ }
+
+
+ [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ return (helper.GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+
+ public override int GetEra(DateTime time) {
+ return (helper.GetEra(time));
+ }
+
+ public override int GetMonth(DateTime time) {
+ return (helper.GetMonth(time));
+ }
+
+
+ public override int GetYear(DateTime time) {
+ return (helper.GetYear(time));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ return (helper.IsLeapDay(year, month, day, era));
+ }
+
+
+ public override bool IsLeapYear(int year, int era) {
+ return (helper.IsLeapYear(year, era));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetLeapMonth(int year, int era)
+ {
+ return (helper.GetLeapMonth(year, era));
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era) {
+ return (helper.IsLeapMonth(year, month, era));
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ return (helper.ToDateTime(year, month, day, hour, minute, second, millisecond, era));
+ }
+
+
+ public override int[] Eras {
+ get {
+ return (helper.Eras);
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 4362;
+
+
+ public override int TwoDigitYearMax {
+ get {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value < 99 || value > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 99,
+ helper.MaxYear));
+
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ return (helper.ToFourDigitYear(year, this.TwoDigitYearMax));
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/KoreanLunisolarCalendar.cs b/src/mscorlib/src/System/Globalization/KoreanLunisolarCalendar.cs
new file mode 100644
index 0000000000..eb0a810864
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/KoreanLunisolarCalendar.cs
@@ -0,0 +1,1334 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+ using System.Diagnostics.Contracts;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Notes about KoreanLunisolarCalendar
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 918/02/14 2051/02/10
+ ** KoreanLunisolar 918/01/01 2050/13/29
+ */
+
+ [Serializable]
+ public class KoreanLunisolarCalendar : EastAsianLunisolarCalendar {
+
+
+ //
+ // The era value for the current era.
+ //
+
+ public const int GregorianEra = 1;
+
+ //internal static Calendar m_defaultInstance;
+
+ internal const int MIN_LUNISOLAR_YEAR = 918;
+ internal const int MAX_LUNISOLAR_YEAR = 2050;
+
+ internal const int MIN_GREGORIAN_YEAR = 918;
+ internal const int MIN_GREGORIAN_MONTH = 2;
+ internal const int MIN_GREGORIAN_DAY = 14;
+
+ internal const int MAX_GREGORIAN_YEAR = 2051;
+ internal const int MAX_GREGORIAN_MONTH = 2;
+ internal const int MAX_GREGORIAN_DAY = 10;
+
+ internal static DateTime minDate = new DateTime(MIN_GREGORIAN_YEAR, MIN_GREGORIAN_MONTH, MIN_GREGORIAN_DAY);
+ internal static DateTime maxDate = new DateTime((new DateTime(MAX_GREGORIAN_YEAR, MAX_GREGORIAN_MONTH, MAX_GREGORIAN_DAY, 23, 59, 59, 999)).Ticks + 9999);
+
+ public override DateTime MinSupportedDateTime {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+
+ public override DateTime MaxSupportedDateTime {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // 917 -- From http://emr.cs.iit.edu/home/reingold/calendar-book/Calendrica.html
+ // using ChineseLunisolar
+ return 384;
+ }
+ }
+
+ static readonly int [,] yinfo =
+ {
+/*Y LM Lmon Lday DaysPerMonth D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 #Days
+918 */{ 0 , 2 , 14 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 0 355
+919 */{ 0 , 2 , 4 , 17872 },/* 29 30 29 29 29 30 29 30 30 30 29 30 0 354
+920 */{ 6 , 1 , 24 , 41688 },/* 30 29 30 29 29 29 30 29 30 30 29 30 30 384
+921 */{ 0 , 2 , 11 , 41648 },/* 30 29 30 29 29 29 30 29 30 29 30 30 0 354
+922 */{ 0 , 1 , 31 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+923 */{ 4 , 1 , 20 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+924 */{ 0 , 2 , 8 , 27936 },/* 29 30 30 29 30 30 29 30 29 29 30 29 0 354
+925 */{ 12 , 1 , 27 , 44384 },/* 30 29 30 29 30 30 29 30 29 30 30 29 29 384
+926 */{ 0 , 2 , 15 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+927 */{ 0 , 2 , 5 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 0 355
+928 */{ 8 , 1 , 26 , 17848 },/* 29 30 29 29 29 30 29 30 30 29 30 30 30 384
+929 */{ 0 , 2 , 13 , 17776 },/* 29 30 29 29 29 30 29 30 29 30 30 30 0 354
+930 */{ 0 , 2 , 2 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+931 */{ 5 , 1 , 22 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 29 383
+932 */{ 0 , 2 , 9 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 0 355
+933 */{ 0 , 1 , 29 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+934 */{ 1 , 1 , 18 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+935 */{ 0 , 2 , 6 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+936 */{ 11 , 1 , 27 , 21344 },/* 29 30 29 30 29 29 30 30 29 30 30 29 29 383
+937 */{ 0 , 2 , 13 , 51904 },/* 30 30 29 29 30 29 30 29 30 30 29 29 0 354
+938 */{ 0 , 2 , 2 , 58720 },/* 30 30 30 29 29 30 29 30 29 30 30 29 0 355
+939 */{ 7 , 1 , 23 , 53928 },/* 30 30 29 30 29 29 30 29 30 29 30 29 30 384
+940 */{ 0 , 2 , 11 , 53920 },/* 30 30 29 30 29 29 30 29 30 29 30 29 0 354
+941 */{ 0 , 1 , 30 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+942 */{ 3 , 1 , 20 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+943 */{ 0 , 2 , 8 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+944 */{ 12 , 1 , 28 , 42704 },/* 30 29 30 29 29 30 30 29 30 30 29 30 29 384
+945 */{ 0 , 2 , 15 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 0 355
+946 */{ 0 , 2 , 5 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+947 */{ 7 , 1 , 25 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+948 */{ 0 , 2 , 13 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+949 */{ 0 , 2 , 1 , 45664 },/* 30 29 30 30 29 29 30 29 29 30 30 29 0 354
+950 */{ 5 , 1 , 21 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+951 */{ 0 , 2 , 9 , 45936 },/* 30 29 30 30 29 30 29 30 29 30 29 0 0 325
+952 */{ 0 , 12 , 31 , 43728 },/* 30 29 30 29 30 29 30 29 30 30 29 30 29 384
+953 */{ 1 , 1 , 18 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+954 */{ 0 , 2 , 6 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+955 */{ 9 , 1 , 27 , 19128 },/* 29 30 29 29 30 29 30 29 30 29 30 30 30 384
+956 */{ 0 , 2 , 15 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+957 */{ 0 , 2 , 3 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+958 */{ 7 , 1 , 23 , 43672 },/* 30 29 30 29 30 29 30 29 30 29 29 30 30 384
+959 */{ 0 , 2 , 11 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+960 */{ 0 , 1 , 31 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+961 */{ 3 , 1 , 20 , 19880 },/* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+962 */{ 0 , 2 , 8 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+963 */{ 12 , 1 , 28 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+964 */{ 0 , 2 , 16 , 41840 },/* 30 29 30 29 29 29 30 30 29 30 30 30 0 355
+965 */{ 0 , 2 , 5 , 20848 },/* 29 30 29 30 29 29 29 30 29 30 30 30 0 354
+966 */{ 8 , 1 , 25 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+967 */{ 0 , 2 , 12 , 54448 },/* 30 30 29 30 29 30 29 29 30 29 30 30 0 355
+968 */{ 0 , 2 , 2 , 23184 },/* 29 30 29 30 30 29 30 29 30 29 29 30 0 354
+969 */{ 5 , 1 , 21 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+970 */{ 0 , 2 , 9 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+971 */{ 0 , 1 , 30 , 10976 },/* 29 29 30 29 30 29 30 29 30 30 30 29 0 354
+972 */{ 2 , 1 , 19 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+973 */{ 0 , 2 , 6 , 41696 },/* 30 29 30 29 29 29 30 29 30 30 30 29 0 354
+974 */{ 10 , 1 , 26 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+975 */{ 0 , 2 , 14 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+976 */{ 0 , 2 , 3 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+977 */{ 7 , 1 , 22 , 55952 },/* 30 30 29 30 30 29 30 29 30 29 29 30 29 384
+978 */{ 0 , 2 , 10 , 46496 },/* 30 29 30 30 29 30 29 30 30 29 30 29 0 355
+979 */{ 0 , 1 , 31 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+980 */{ 3 , 1 , 21 , 10968 },/* 29 29 30 29 30 29 30 29 30 30 29 30 30 384
+981 */{ 0 , 2 , 8 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+982 */{ 12 , 1 , 28 , 37592 },/* 30 29 29 30 29 29 30 29 30 30 29 30 30 384
+983 */{ 0 , 2 , 16 , 37552 },/* 30 29 29 30 29 29 30 29 30 29 30 30 0 354
+984 */{ 0 , 2 , 5 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+985 */{ 9 , 1 , 24 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+986 */{ 0 , 2 , 12 , 44192 },/* 30 29 30 29 30 30 29 29 30 29 30 29 0 354
+987 */{ 0 , 2 , 1 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+988 */{ 5 , 1 , 22 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+989 */{ 0 , 2 , 9 , 19376 },/* 29 30 29 29 30 29 30 30 30 29 30 30 0 355
+990 */{ 0 , 1 , 30 , 9648 },/* 29 29 30 29 29 30 29 30 30 29 30 30 0 354
+991 */{ 2 , 1 , 19 , 37560 },/* 30 29 29 30 29 29 30 29 30 29 30 30 30 384
+992 */{ 0 , 2 , 7 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+993 */{ 10 , 1 , 26 , 26968 },/* 29 30 30 29 30 29 29 30 29 30 29 30 30 384
+994 */{ 0 , 2 , 14 , 22864 },/* 29 30 29 30 30 29 29 30 29 30 29 30 0 354
+995 */{ 0 , 2 , 3 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+996 */{ 7 , 1 , 23 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+997 */{ 0 , 2 , 10 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+998 */{ 0 , 1 , 31 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+999 */{ 3 , 1 , 20 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1000 */{ 0 , 2 , 8 , 9584 },/* 29 29 30 29 29 30 29 30 29 30 30 30 0 354
+1001 */{ 12 , 1 , 28 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 29 383
+1002 */{ 0 , 2 , 15 , 53920 },/* 30 30 29 30 29 29 30 29 30 29 30 29 0 354
+1003 */{ 0 , 2 , 4 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1004 */{ 9 , 1 , 25 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1005 */{ 0 , 2 , 12 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1006 */{ 0 , 2 , 1 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+1007 */{ 5 , 1 , 22 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1008 */{ 0 , 2 , 10 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+1009 */{ 0 , 1 , 29 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1010 */{ 2 , 1 , 18 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1011 */{ 0 , 2 , 6 , 45664 },/* 30 29 30 30 29 29 30 29 29 30 30 29 0 354
+1012 */{ 10 , 1 , 26 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1013 */{ 0 , 2 , 13 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+1014 */{ 0 , 2 , 3 , 13728 },/* 29 29 30 30 29 30 29 30 30 29 30 29 0 354
+1015 */{ 6 , 1 , 23 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+1016 */{ 0 , 2 , 11 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1017 */{ 0 , 1 , 31 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1018 */{ 4 , 1 , 20 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1019 */{ 0 , 2 , 8 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1020 */{ 12 , 1 , 28 , 43608 },/* 30 29 30 29 30 29 30 29 29 30 29 30 30 384
+1021 */{ 0 , 2 , 15 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1022 */{ 0 , 2 , 4 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+1023 */{ 9 , 1 , 25 , 11688 },/* 29 29 30 29 30 30 29 30 30 29 30 29 30 384
+1024 */{ 0 , 2 , 13 , 11088 },/* 29 29 30 29 30 29 30 30 29 30 29 30 0 354
+1025 */{ 0 , 2 , 1 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 0 355
+1026 */{ 5 , 1 , 22 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 29 383
+1027 */{ 0 , 2 , 9 , 51568 },/* 30 30 29 29 30 29 29 30 29 30 30 30 0 355
+1028 */{ 0 , 1 , 30 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1029 */{ 2 , 1 , 18 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1030 */{ 0 , 2 , 5 , 55952 },/* 30 30 29 30 30 29 30 29 30 29 29 30 0 355
+1031 */{ 10 , 1 , 26 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+1032 */{ 0 , 2 , 14 , 26320 },/* 29 30 30 29 29 30 30 29 30 30 29 30 0 355
+1033 */{ 0 , 2 , 3 , 9952 },/* 29 29 30 29 29 30 30 29 30 30 30 29 0 354
+1034 */{ 6 , 1 , 23 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1035 */{ 0 , 2 , 11 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1036 */{ 0 , 1 , 31 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+1037 */{ 4 , 1 , 19 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1038 */{ 0 , 2 , 7 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1039 */{ 12 , 1 , 27 , 54928 },/* 30 30 29 30 29 30 30 29 30 29 29 30 29 384
+1040 */{ 0 , 2 , 15 , 46464 },/* 30 29 30 30 29 30 29 30 30 29 29 29 0 354
+1041 */{ 0 , 2 , 3 , 54960 },/* 30 30 29 30 29 30 30 29 30 29 30 30 0 356
+1042 */{ 9 , 1 , 25 , 9944 },/* 29 29 30 29 29 30 30 29 30 30 29 30 30 384
+1043 */{ 0 , 2 , 13 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1044 */{ 0 , 2 , 2 , 37552 },/* 30 29 29 30 29 29 30 29 30 29 30 30 0 354
+1045 */{ 5 , 1 , 21 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1046 */{ 0 , 2 , 9 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1047 */{ 0 , 1 , 29 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+1048 */{ 1 , 1 , 18 , 46424 },/* 30 29 30 30 29 30 29 30 29 30 29 30 30 385
+1049 */{ 0 , 2 , 6 , 11600 },/* 29 29 30 29 30 30 29 30 29 30 29 30 0 354
+1050 */{ 11 , 1 , 26 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1051 */{ 0 , 2 , 14 , 19376 },/* 29 30 29 29 30 29 30 30 30 29 30 30 0 355
+1052 */{ 0 , 2 , 4 , 9648 },/* 29 29 30 29 29 30 29 30 30 29 30 30 0 354
+1053 */{ 7 , 1 , 23 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1054 */{ 0 , 2 , 11 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1055 */{ 0 , 1 , 31 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 0 354
+1056 */{ 3 , 1 , 20 , 27304 },/* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1057 */{ 0 , 2 , 7 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1058 */{ 12 , 1 , 27 , 43864 },/* 30 29 30 29 30 29 30 30 29 30 29 30 30 385
+1059 */{ 0 , 2 , 16 , 10064 },/* 29 29 30 29 29 30 30 30 29 30 29 30 0 354
+1060 */{ 0 , 2 , 5 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1061 */{ 8 , 1 , 24 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1062 */{ 0 , 2 , 12 , 42336 },/* 30 29 30 29 29 30 29 30 29 30 30 29 0 354
+1063 */{ 0 , 2 , 1 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+1064 */{ 5 , 1 , 21 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1065 */{ 0 , 2 , 8 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1066 */{ 0 , 1 , 29 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1067 */{ 1 , 1 , 18 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+1068 */{ 0 , 2 , 6 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+1069 */{ 11 , 1 , 26 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1070 */{ 0 , 2 , 14 , 18896 },/* 29 30 29 29 30 29 29 30 30 30 29 30 0 354
+1071 */{ 0 , 2 , 3 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1072 */{ 7 , 1 , 23 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1073 */{ 0 , 2 , 10 , 43616 },/* 30 29 30 29 30 29 30 29 29 30 30 29 0 354
+1074 */{ 0 , 1 , 30 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1075 */{ 4 , 1 , 20 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1076 */{ 0 , 2 , 8 , 13728 },/* 29 29 30 30 29 30 29 30 30 29 30 29 0 354
+1077 */{ 0 , 1 , 27 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 0 355
+1078 */{ 1 , 1 , 17 , 19352 },/* 29 30 29 29 30 29 30 30 30 29 29 30 30 384
+1079 */{ 0 , 2 , 5 , 17840 },/* 29 30 29 29 29 30 29 30 30 29 30 30 0 354
+1080 */{ 9 , 1 , 25 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1081 */{ 0 , 2 , 12 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1082 */{ 0 , 2 , 1 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+1083 */{ 6 , 1 , 21 , 46408 },/* 30 29 30 30 29 30 29 30 29 30 29 29 30 384
+1084 */{ 0 , 2 , 9 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 0 355
+1085 */{ 0 , 1 , 29 , 11680 },/* 29 29 30 29 30 30 29 30 30 29 30 29 0 354
+1086 */{ 2 , 1 , 18 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+1087 */{ 0 , 2 , 6 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+1088 */{ 12 , 1 , 27 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 29 383
+1089 */{ 0 , 2 , 13 , 51568 },/* 30 30 29 29 30 29 29 30 29 30 30 30 0 355
+1090 */{ 0 , 2 , 3 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1091 */{ 8 , 1 , 23 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1092 */{ 0 , 2 , 10 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+1093 */{ 0 , 1 , 30 , 23360 },/* 29 30 29 30 30 29 30 30 29 30 29 29 0 354
+1094 */{ 4 , 1 , 19 , 43880 },/* 30 29 30 29 30 29 30 30 29 30 30 29 30 385
+1095 */{ 0 , 2 , 8 , 10976 },/* 29 29 30 29 30 29 30 29 30 30 30 29 0 354
+1096 */{ 0 , 1 , 28 , 58896 },/* 30 30 30 29 29 30 30 29 29 29 29 30 0 354
+1097 */{ 2 , 1 , 16 , 51568 },/* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+1098 */{ 0 , 2 , 4 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+1099 */{ 9 , 1 , 24 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1100 */{ 0 , 2 , 12 , 21664 },/* 29 30 29 30 29 30 29 29 30 29 30 29 0 353
+1101 */{ 0 , 1 , 31 , 54864 },/* 30 30 29 30 29 30 30 29 29 30 29 30 0 355
+1102 */{ 6 , 1 , 21 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1103 */{ 0 , 2 , 9 , 21968 },/* 29 30 29 30 29 30 29 30 30 30 29 30 0 355
+1104 */{ 0 , 1 , 30 , 9936 },/* 29 29 30 29 29 30 30 29 30 30 29 30 0 354
+1105 */{ 2 , 1 , 18 , 37608 },/* 30 29 29 30 29 29 30 29 30 30 30 29 30 384
+1106 */{ 0 , 2 , 6 , 37552 },/* 30 29 29 30 29 29 30 29 30 29 30 30 0 354
+1107 */{ 10 , 1 , 26 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1108 */{ 0 , 2 , 14 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1109 */{ 0 , 2 , 2 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+1110 */{ 8 , 1 , 22 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1111 */{ 0 , 2 , 10 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+1112 */{ 0 , 1 , 31 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 0 355
+1113 */{ 4 , 1 , 20 , 9656 },/* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1114 */{ 0 , 2 , 8 , 17776 },/* 29 30 29 29 29 30 29 30 29 30 30 30 0 354
+1115 */{ 0 , 1 , 28 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1116 */{ 1 , 1 , 17 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1117 */{ 0 , 2 , 4 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 0 354
+1118 */{ 9 , 1 , 24 , 29352 },/* 29 30 30 30 29 29 30 29 30 29 30 29 30 384
+1119 */{ 0 , 2 , 12 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1120 */{ 0 , 2 , 1 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1121 */{ 5 , 1 , 21 , 19304 },/* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1122 */{ 0 , 2 , 9 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1123 */{ 0 , 1 , 29 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1124 */{ 3 , 1 , 19 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 29 383
+1125 */{ 0 , 2 , 5 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+1126 */{ 11 , 1 , 25 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1127 */{ 0 , 2 , 13 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1128 */{ 0 , 2 , 3 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1129 */{ 8 , 1 , 22 , 39824 },/* 30 29 29 30 30 29 30 30 30 29 29 30 29 384
+1130 */{ 0 , 2 , 10 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+1131 */{ 0 , 1 , 31 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1132 */{ 4 , 1 , 20 , 42216 },/* 30 29 30 29 29 30 29 29 30 30 30 29 30 384
+1133 */{ 0 , 2 , 7 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1134 */{ 0 , 1 , 27 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1135 */{ 2 , 1 , 16 , 55592 },/* 30 30 29 30 30 29 29 30 29 29 30 29 30 384
+1136 */{ 0 , 2 , 4 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1137 */{ 10 , 1 , 23 , 54952 },/* 30 30 29 30 29 30 30 29 30 29 30 29 30 385
+1138 */{ 0 , 2 , 12 , 11680 },/* 29 29 30 29 30 30 29 30 30 29 30 29 0 354
+1139 */{ 0 , 2 , 1 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 0 355
+1140 */{ 6 , 1 , 22 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+1141 */{ 0 , 2 , 9 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1142 */{ 0 , 1 , 29 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1143 */{ 4 , 1 , 18 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1144 */{ 0 , 2 , 6 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1145 */{ 11 , 1 , 25 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1146 */{ 0 , 2 , 13 , 27456 },/* 29 30 30 29 30 29 30 30 29 30 29 29 0 354
+1147 */{ 0 , 2 , 2 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1148 */{ 8 , 1 , 23 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+1149 */{ 0 , 2 , 10 , 39280 },/* 30 29 29 30 30 29 29 30 29 30 30 30 0 355
+1150 */{ 0 , 1 , 31 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1151 */{ 4 , 1 , 20 , 25784 },/* 29 30 30 29 29 30 29 29 30 29 30 30 30 384
+1152 */{ 0 , 2 , 8 , 21680 },/* 29 30 29 30 29 30 29 29 30 29 30 30 0 354
+1153 */{ 12 , 1 , 27 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1154 */{ 0 , 2 , 14 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+1155 */{ 0 , 2 , 4 , 23232 },/* 29 30 29 30 30 29 30 29 30 30 29 29 0 354
+1156 */{ 10 , 1 , 24 , 43880 },/* 30 29 30 29 30 29 30 30 29 30 30 29 30 385
+1157 */{ 0 , 2 , 12 , 9952 },/* 29 29 30 29 29 30 30 29 30 30 30 29 0 354
+1158 */{ 0 , 2 , 1 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1159 */{ 6 , 1 , 21 , 51568 },/* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+1160 */{ 0 , 2 , 9 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+1161 */{ 0 , 1 , 28 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1162 */{ 2 , 1 , 17 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1163 */{ 0 , 2 , 5 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1164 */{ 11 , 1 , 26 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1165 */{ 0 , 2 , 13 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 0 355
+1166 */{ 0 , 2 , 3 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1167 */{ 7 , 1 , 23 , 37608 },/* 30 29 29 30 29 29 30 29 30 30 30 29 30 384
+1168 */{ 0 , 2 , 11 , 37488 },/* 30 29 29 30 29 29 30 29 29 30 30 30 0 354
+1169 */{ 0 , 1 , 30 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1170 */{ 5 , 1 , 19 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1171 */{ 0 , 2 , 7 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+1172 */{ 0 , 1 , 27 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1173 */{ 1 , 1 , 16 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1174 */{ 0 , 2 , 4 , 19888 },/* 29 30 29 29 30 30 29 30 30 29 30 30 0 355
+1175 */{ 9 , 1 , 25 , 9648 },/* 29 29 30 29 29 30 29 30 30 29 30 30 29 383
+1176 */{ 0 , 2 , 12 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1177 */{ 0 , 2 , 1 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1178 */{ 6 , 1 , 21 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1179 */{ 0 , 2 , 9 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 0 354
+1180 */{ 0 , 1 , 29 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1181 */{ 3 , 1 , 17 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1182 */{ 0 , 2 , 5 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1183 */{ 11 , 1 , 26 , 19304 },/* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1184 */{ 0 , 2 , 14 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1185 */{ 0 , 2 , 2 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1186 */{ 7 , 1 , 23 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 29 383
+1187 */{ 0 , 2 , 10 , 53392 },/* 30 30 29 30 29 29 30 29 29 30 30 0 0 325
+1188 */{ 0 , 1 , 1 , 29848 },/* 29 30 30 30 29 30 29 29 30 29 29 30 30 384
+1189 */{ 5 , 1 , 19 , 27304 },/* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1190 */{ 0 , 2 , 7 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1191 */{ 0 , 1 , 27 , 39760 },/* 30 29 29 30 30 29 30 30 29 30 29 30 0 355
+1192 */{ 2 , 1 , 17 , 19304 },/* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1193 */{ 0 , 2 , 4 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1194 */{ 10 , 1 , 24 , 42216 },/* 30 29 30 29 29 30 29 29 30 30 30 29 30 384
+1195 */{ 0 , 2 , 12 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1196 */{ 0 , 2 , 1 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+1197 */{ 6 , 1 , 20 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1198 */{ 0 , 2 , 8 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1199 */{ 0 , 1 , 28 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 0 355
+1200 */{ 2 , 1 , 18 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1201 */{ 0 , 2 , 5 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 0 355
+1202 */{ 12 , 1 , 26 , 18904 },/* 29 30 29 29 30 29 29 30 30 30 29 30 30 384
+1203 */{ 0 , 2 , 14 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1204 */{ 0 , 2 , 3 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1205 */{ 8 , 1 , 22 , 43608 },/* 30 29 30 29 30 29 30 29 29 30 29 30 30 384
+1206 */{ 0 , 2 , 10 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1207 */{ 0 , 1 , 30 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1208 */{ 4 , 1 , 19 , 46496 },/* 30 29 30 30 29 30 29 30 30 29 30 29 29 384
+1209 */{ 0 , 2 , 6 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1210 */{ 0 , 1 , 27 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1211 */{ 2 , 1 , 17 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1212 */{ 0 , 2 , 5 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1213 */{ 9 , 1 , 24 , 25784 },/* 29 30 30 29 29 30 29 29 30 29 30 30 30 384
+1214 */{ 0 , 2 , 12 , 21680 },/* 29 30 29 30 29 30 29 29 30 29 30 30 0 354
+1215 */{ 0 , 2 , 1 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1216 */{ 7 , 1 , 21 , 27944 },/* 29 30 30 29 30 30 29 30 29 29 30 29 30 384
+1217 */{ 0 , 2 , 8 , 23232 },/* 29 30 29 30 30 29 30 29 30 30 29 29 0 354
+1218 */{ 0 , 1 , 28 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1219 */{ 3 , 1 , 18 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1220 */{ 0 , 2 , 6 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1221 */{ 12 , 1 , 25 , 51568 },/* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+1222 */{ 0 , 2 , 13 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+1223 */{ 0 , 2 , 2 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1224 */{ 8 , 1 , 22 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1225 */{ 0 , 2 , 9 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1226 */{ 0 , 1 , 30 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1227 */{ 5 , 1 , 19 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+1228 */{ 0 , 2 , 8 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1229 */{ 0 , 1 , 27 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1230 */{ 2 , 1 , 16 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+1231 */{ 0 , 2 , 4 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1232 */{ 9 , 1 , 24 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1233 */{ 0 , 2 , 11 , 45728 },/* 30 29 30 30 29 29 30 29 30 29 30 29 0 354
+1234 */{ 0 , 1 , 31 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1235 */{ 7 , 1 , 21 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1236 */{ 0 , 2 , 9 , 19872 },/* 29 30 29 29 30 30 29 30 30 29 30 29 0 354
+1237 */{ 0 , 1 , 28 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+1238 */{ 4 , 1 , 18 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1239 */{ 0 , 2 , 6 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1240 */{ 12 , 1 , 26 , 43320 },/* 30 29 30 29 30 29 29 30 29 29 30 30 30 384
+1241 */{ 0 , 2 , 13 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 0 354
+1242 */{ 0 , 2 , 2 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1243 */{ 8 , 1 , 22 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1244 */{ 0 , 2 , 10 , 44624 },/* 30 29 30 29 30 30 30 29 29 30 29 30 0 355
+1245 */{ 0 , 1 , 30 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1246 */{ 4 , 1 , 19 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1247 */{ 0 , 2 , 7 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1248 */{ 0 , 1 , 28 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 0 354
+1249 */{ 2 , 1 , 16 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+1250 */{ 0 , 2 , 3 , 58672 },/* 30 30 30 29 29 30 29 30 29 29 30 30 0 355
+1251 */{ 10 , 1 , 24 , 27800 },/* 29 30 30 29 30 30 29 29 30 29 29 30 30 384
+1252 */{ 0 , 2 , 12 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1253 */{ 0 , 1 , 31 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1254 */{ 6 , 1 , 21 , 19304 },/* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1255 */{ 0 , 2 , 9 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1256 */{ 0 , 1 , 29 , 42208 },/* 30 29 30 29 29 30 29 29 30 30 30 29 0 354
+1257 */{ 4 , 1 , 17 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1258 */{ 0 , 2 , 5 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+1259 */{ 11 , 1 , 25 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1260 */{ 0 , 2 , 13 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1261 */{ 0 , 2 , 1 , 46752 },/* 30 29 30 30 29 30 30 29 30 29 30 29 0 355
+1262 */{ 9 , 1 , 22 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1263 */{ 0 , 2 , 10 , 21872 },/* 29 30 29 30 29 30 29 30 29 30 30 30 0 355
+1264 */{ 0 , 1 , 31 , 18896 },/* 29 30 29 29 30 29 29 30 30 30 29 30 0 354
+1265 */{ 5 , 1 , 19 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1266 */{ 0 , 2 , 7 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1267 */{ 0 , 1 , 27 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+1268 */{ 1 , 1 , 16 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1269 */{ 0 , 2 , 3 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1270 */{ 11 , 1 , 23 , 46528 },/* 30 29 30 30 29 30 29 30 30 30 29 29 29 384
+1271 */{ 0 , 2 , 11 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1272 */{ 0 , 2 , 1 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1273 */{ 6 , 1 , 21 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1274 */{ 0 , 2 , 9 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1275 */{ 0 , 1 , 29 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1276 */{ 3 , 1 , 18 , 27224 },/* 29 30 30 29 30 29 30 29 29 30 29 30 30 384
+1277 */{ 0 , 2 , 5 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1278 */{ 11 , 1 , 25 , 27432 },/* 29 30 30 29 30 29 30 30 29 29 30 29 30 384
+1279 */{ 0 , 2 , 13 , 23232 },/* 29 30 29 30 30 29 30 29 30 30 29 29 0 354
+1280 */{ 0 , 2 , 2 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1281 */{ 8 , 1 , 22 , 10984 },/* 29 29 30 29 30 29 30 29 30 30 30 29 30 384
+1282 */{ 0 , 2 , 10 , 18912 },/* 29 30 29 29 30 29 29 30 30 30 30 29 0 354
+1283 */{ 0 , 1 , 30 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1284 */{ 5 , 1 , 19 , 53848 },/* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+1285 */{ 0 , 2 , 6 , 45648 },/* 30 29 30 30 29 29 30 29 29 30 29 30 0 354
+1286 */{ 0 , 1 , 26 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1287 */{ 2 , 1 , 15 , 62096 },/* 30 30 30 30 29 29 30 29 30 29 29 30 29 384
+1288 */{ 0 , 2 , 3 , 46496 },/* 30 29 30 30 29 30 29 30 30 29 30 29 0 355
+1289 */{ 10 , 1 , 23 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+1290 */{ 0 , 2 , 11 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1291 */{ 0 , 2 , 1 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1292 */{ 6 , 1 , 21 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1293 */{ 0 , 2 , 8 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1294 */{ 0 , 1 , 28 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+1295 */{ 4 , 1 , 17 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1296 */{ 0 , 2 , 5 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1297 */{ 12 , 1 , 24 , 44384 },/* 30 29 30 29 30 30 29 30 29 30 30 29 29 384
+1298 */{ 0 , 2 , 12 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1299 */{ 0 , 2 , 2 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+1300 */{ 8 , 1 , 23 , 2424 },/* 29 29 29 29 30 29 29 30 29 30 30 30 30 383
+1301 */{ 0 , 2 , 10 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1302 */{ 0 , 1 , 30 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1303 */{ 5 , 1 , 19 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1304 */{ 0 , 2 , 6 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+1305 */{ 0 , 1 , 26 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1306 */{ 1 , 1 , 15 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 29 384
+1307 */{ 0 , 2 , 3 , 42720 },/* 30 29 30 29 29 30 30 29 30 30 30 29 0 355
+1308 */{ 11 , 1 , 24 , 37608 },/* 30 29 29 30 29 29 30 29 30 30 30 29 30 384
+1309 */{ 0 , 2 , 11 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1310 */{ 0 , 1 , 31 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+1311 */{ 7 , 1 , 20 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1312 */{ 0 , 2 , 8 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1313 */{ 0 , 1 , 27 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1314 */{ 3 , 1 , 17 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1315 */{ 0 , 2 , 5 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1316 */{ 0 , 1 , 25 , 42704 },/* 30 29 30 29 29 30 30 29 30 30 29 30 0 355
+1317 */{ 1 , 1 , 14 , 37608 },/* 30 29 29 30 29 29 30 29 30 30 30 29 30 384
+1318 */{ 0 , 2 , 2 , 37552 },/* 30 29 29 30 29 29 30 29 30 29 30 30 0 354
+1319 */{ 8 , 1 , 22 , 42328 },/* 30 29 30 29 29 30 29 30 29 30 29 30 30 384
+1320 */{ 0 , 2 , 10 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1321 */{ 0 , 1 , 29 , 45728 },/* 30 29 30 30 29 29 30 29 30 29 30 29 0 354
+1322 */{ 5 , 1 , 18 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1323 */{ 0 , 2 , 6 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+1324 */{ 0 , 1 , 27 , 19872 },/* 29 30 29 29 30 30 29 30 30 29 30 29 0 354
+1325 */{ 1 , 1 , 15 , 42448 },/* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+1326 */{ 0 , 2 , 3 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1327 */{ 9 , 1 , 24 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1328 */{ 0 , 2 , 12 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 0 354
+1329 */{ 0 , 1 , 31 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 0 354
+1330 */{ 7 , 1 , 20 , 27288 },/* 29 30 30 29 30 29 30 29 30 29 29 30 30 384
+1331 */{ 0 , 2 , 8 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1332 */{ 0 , 1 , 28 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1333 */{ 3 , 1 , 17 , 19368 },/* 29 30 29 29 30 29 30 30 30 29 30 29 30 384
+1334 */{ 0 , 2 , 5 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1335 */{ 12 , 1 , 25 , 42608 },/* 30 29 30 29 29 30 30 29 29 30 30 30 29 384
+1336 */{ 0 , 2 , 13 , 41696 },/* 30 29 30 29 29 29 30 29 30 30 30 29 0 354
+1337 */{ 0 , 2 , 1 , 53600 },/* 30 30 29 30 29 29 29 30 29 30 30 29 0 354
+1338 */{ 8 , 1 , 21 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1339 */{ 0 , 2 , 9 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1340 */{ 0 , 1 , 29 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 0 355
+1341 */{ 5 , 1 , 18 , 23376 },/* 29 30 29 30 30 29 30 30 29 30 29 30 29 384
+1342 */{ 0 , 2 , 6 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+1343 */{ 0 , 1 , 27 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1344 */{ 2 , 1 , 16 , 41704 },/* 30 29 30 29 29 29 30 29 30 30 30 29 30 384
+1345 */{ 0 , 2 , 3 , 41680 },/* 30 29 30 29 29 29 30 29 30 30 29 30 0 354
+1346 */{ 10 , 1 , 23 , 53592 },/* 30 30 29 30 29 29 29 30 29 30 29 30 30 384
+1347 */{ 0 , 2 , 11 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+1348 */{ 0 , 1 , 31 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1349 */{ 7 , 1 , 19 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 29 384
+1350 */{ 0 , 2 , 7 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+1351 */{ 0 , 1 , 28 , 21968 },/* 29 30 29 30 29 30 29 30 30 30 29 30 0 355
+1352 */{ 3 , 1 , 18 , 18904 },/* 29 30 29 29 30 29 29 30 30 30 29 30 30 384
+1353 */{ 0 , 2 , 5 , 17840 },/* 29 30 29 29 29 30 29 30 30 29 30 30 0 354
+1354 */{ 0 , 1 , 25 , 41648 },/* 30 29 30 29 29 29 30 29 30 29 30 30 0 354
+1355 */{ 1 , 1 , 14 , 53592 },/* 30 30 29 30 29 29 29 30 29 30 29 30 30 384
+1356 */{ 0 , 2 , 2 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+1357 */{ 9 , 1 , 21 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1358 */{ 0 , 2 , 9 , 27424 },/* 29 30 30 29 30 29 30 30 29 29 30 29 0 354
+1359 */{ 0 , 1 , 29 , 44384 },/* 30 29 30 29 30 30 29 30 29 30 30 29 0 355
+1360 */{ 5 , 1 , 19 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1361 */{ 0 , 2 , 6 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+1362 */{ 0 , 1 , 27 , 17776 },/* 29 30 29 29 29 30 29 30 29 30 30 30 0 354
+1363 */{ 3 , 1 , 16 , 41656 },/* 30 29 30 29 29 29 30 29 30 29 30 30 30 384
+1364 */{ 0 , 2 , 4 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1365 */{ 10 , 1 , 23 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 29 383
+1366 */{ 0 , 2 , 10 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1367 */{ 0 , 1 , 31 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1368 */{ 7 , 1 , 20 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 29 384
+1369 */{ 0 , 2 , 7 , 42720 },/* 30 29 30 29 29 30 30 29 30 30 30 29 0 355
+1370 */{ 0 , 1 , 28 , 21216 },/* 29 30 29 30 29 29 30 29 30 30 30 29 0 354
+1371 */{ 3 , 1 , 17 , 50544 },/* 30 30 29 29 29 30 29 30 29 30 30 30 29 384
+1372 */{ 0 , 2 , 5 , 42336 },/* 30 29 30 29 29 30 29 30 29 30 30 29 0 354
+1373 */{ 11 , 1 , 24 , 53928 },/* 30 30 29 30 29 29 30 29 30 29 30 29 30 384
+1374 */{ 0 , 2 , 12 , 53920 },/* 30 30 29 30 29 29 30 29 30 29 30 29 0 354
+1375 */{ 0 , 2 , 1 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1376 */{ 9 , 1 , 22 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1377 */{ 0 , 2 , 9 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1378 */{ 0 , 1 , 29 , 42704 },/* 30 29 30 29 29 30 30 29 30 30 29 30 0 355
+1379 */{ 5 , 1 , 19 , 21224 },/* 29 30 29 30 29 29 30 29 30 30 30 29 30 384
+1380 */{ 0 , 2 , 7 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1381 */{ 0 , 1 , 26 , 43216 },/* 30 29 30 29 30 29 29 29 30 30 29 30 0 354
+1382 */{ 2 , 1 , 15 , 53928 },/* 30 30 29 30 29 29 30 29 30 29 30 29 30 384
+1383 */{ 0 , 2 , 3 , 45728 },/* 30 29 30 30 29 29 30 29 30 29 30 29 0 354
+1384 */{ 10 , 1 , 23 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1385 */{ 0 , 2 , 10 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+1386 */{ 0 , 1 , 31 , 19872 },/* 29 30 29 29 30 30 29 30 30 29 30 29 0 354
+1387 */{ 6 , 1 , 20 , 42448 },/* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+1388 */{ 0 , 2 , 8 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1389 */{ 0 , 1 , 28 , 20912 },/* 29 30 29 30 29 29 29 30 30 29 30 30 0 354
+1390 */{ 4 , 1 , 17 , 43192 },/* 30 29 30 29 30 29 29 29 30 29 30 30 30 384
+1391 */{ 0 , 2 , 5 , 25904 },/* 29 30 30 29 29 30 29 30 29 29 30 30 0 354
+1392 */{ 12 , 1 , 25 , 27288 },/* 29 30 30 29 30 29 30 29 30 29 29 30 30 384
+1393 */{ 0 , 2 , 12 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1394 */{ 0 , 2 , 1 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1395 */{ 9 , 1 , 22 , 11176 },/* 29 29 30 29 30 29 30 30 30 29 30 29 30 384
+1396 */{ 0 , 2 , 10 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1397 */{ 0 , 1 , 29 , 50032 },/* 30 30 29 29 29 29 30 30 29 30 30 30 0 355
+1398 */{ 5 , 1 , 19 , 20848 },/* 29 30 29 30 29 29 29 30 29 30 30 30 29 383
+1399 */{ 0 , 2 , 6 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+1400 */{ 0 , 1 , 26 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1401 */{ 3 , 1 , 15 , 27280 },/* 29 30 30 29 30 29 30 29 30 29 29 30 29 383
+1402 */{ 0 , 2 , 2 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 0 355
+1403 */{ 11 , 1 , 23 , 23376 },/* 29 30 29 30 30 29 30 30 29 30 29 30 29 384
+1404 */{ 0 , 2 , 11 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+1405 */{ 0 , 1 , 31 , 10976 },/* 29 29 30 29 30 29 30 29 30 30 30 29 0 354
+1406 */{ 7 , 1 , 20 , 41704 },/* 30 29 30 29 29 29 30 29 30 30 30 29 30 384
+1407 */{ 0 , 2 , 8 , 41680 },/* 30 29 30 29 29 29 30 29 30 30 29 30 0 354
+1408 */{ 0 , 1 , 28 , 53584 },/* 30 30 29 30 29 29 29 30 29 30 29 30 0 354
+1409 */{ 4 , 1 , 16 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1410 */{ 0 , 2 , 4 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1411 */{ 12 , 1 , 24 , 46736 },/* 30 29 30 30 29 30 30 29 30 29 29 30 29 384
+1412 */{ 0 , 2 , 12 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+1413 */{ 0 , 2 , 1 , 21968 },/* 29 30 29 30 29 30 29 30 30 30 29 30 0 355
+1414 */{ 9 , 1 , 22 , 9688 },/* 29 29 30 29 29 30 29 30 30 30 29 30 30 384
+1415 */{ 0 , 2 , 10 , 17840 },/* 29 30 29 29 29 30 29 30 30 29 30 30 0 354
+1416 */{ 0 , 1 , 30 , 41648 },/* 30 29 30 29 29 29 30 29 30 29 30 30 0 354
+1417 */{ 5 , 1 , 18 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1418 */{ 0 , 2 , 6 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1419 */{ 0 , 1 , 26 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1420 */{ 1 , 1 , 15 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1421 */{ 0 , 2 , 2 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1422 */{ 12 , 1 , 23 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1423 */{ 0 , 2 , 11 , 19312 },/* 29 30 29 29 30 29 30 30 29 30 30 30 0 355
+1424 */{ 0 , 2 , 1 , 17776 },/* 29 30 29 29 29 30 29 30 29 30 30 30 0 354
+1425 */{ 7 , 1 , 20 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1426 */{ 0 , 2 , 8 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1427 */{ 0 , 1 , 28 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 0 354
+1428 */{ 4 , 1 , 17 , 27816 },/* 29 30 30 29 30 30 29 29 30 29 30 29 30 384
+1429 */{ 0 , 2 , 4 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1430 */{ 12 , 1 , 24 , 39760 },/* 30 29 29 30 30 29 30 30 29 30 29 30 29 384
+1431 */{ 0 , 2 , 12 , 42720 },/* 30 29 30 29 29 30 30 29 30 30 30 29 0 355
+1432 */{ 0 , 2 , 2 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1433 */{ 8 , 1 , 21 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1434 */{ 0 , 2 , 9 , 42336 },/* 30 29 30 29 29 30 29 30 29 30 30 29 0 354
+1435 */{ 0 , 1 , 29 , 53920 },/* 30 30 29 30 29 29 30 29 30 29 30 29 0 354
+1436 */{ 6 , 1 , 18 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 29 384
+1437 */{ 0 , 2 , 5 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1438 */{ 0 , 1 , 26 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1439 */{ 2 , 1 , 15 , 43728 },/* 30 29 30 29 30 29 30 29 30 30 29 30 29 384
+1440 */{ 0 , 2 , 3 , 38368 },/* 30 29 29 30 29 30 29 30 30 30 30 29 0 355
+1441 */{ 11 , 1 , 23 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1442 */{ 0 , 2 , 11 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1443 */{ 0 , 1 , 31 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1444 */{ 7 , 1 , 20 , 53872 },/* 30 30 29 30 29 29 30 29 29 30 30 30 29 384
+1445 */{ 0 , 2 , 7 , 45728 },/* 30 29 30 30 29 29 30 29 30 29 30 29 0 354
+1446 */{ 0 , 1 , 27 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1447 */{ 4 , 1 , 17 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1448 */{ 0 , 2 , 5 , 11680 },/* 29 29 30 29 30 30 29 30 30 29 30 29 0 354
+1449 */{ 0 , 1 , 24 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1450 */{ 1 , 1 , 14 , 19128 },/* 29 30 29 29 30 29 30 29 30 29 30 30 30 384
+1451 */{ 0 , 2 , 2 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1452 */{ 9 , 1 , 22 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1453 */{ 0 , 2 , 9 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1454 */{ 0 , 1 , 29 , 27280 },/* 29 30 30 29 30 29 30 29 30 29 29 30 0 354
+1455 */{ 6 , 1 , 18 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1456 */{ 0 , 2 , 6 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 0 355
+1457 */{ 0 , 1 , 26 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1458 */{ 2 , 1 , 15 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+1459 */{ 0 , 2 , 3 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+1460 */{ 11 , 1 , 24 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 29 383
+1461 */{ 0 , 2 , 10 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+1462 */{ 0 , 1 , 30 , 58544 },/* 30 30 30 29 29 30 29 29 30 29 30 30 0 355
+1463 */{ 7 , 1 , 20 , 27280 },/* 29 30 30 29 30 29 30 29 30 29 29 30 29 383
+1464 */{ 0 , 2 , 7 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 0 355
+1465 */{ 0 , 1 , 27 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1466 */{ 3 , 1 , 17 , 11112 },/* 29 29 30 29 30 29 30 30 29 30 30 29 30 384
+1467 */{ 0 , 2 , 5 , 9952 },/* 29 29 30 29 29 30 30 29 30 30 30 29 0 354
+1468 */{ 0 , 1 , 25 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1469 */{ 2 , 1 , 13 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1470 */{ 0 , 2 , 1 , 51536 },/* 30 30 29 29 30 29 29 30 29 30 29 30 0 354
+1471 */{ 9 , 1 , 21 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1472 */{ 0 , 2 , 9 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+1473 */{ 0 , 1 , 28 , 46736 },/* 30 29 30 30 29 30 30 29 30 29 29 30 0 355
+1474 */{ 6 , 1 , 18 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1475 */{ 0 , 2 , 6 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 0 355
+1476 */{ 0 , 1 , 27 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1477 */{ 2 , 1 , 15 , 37592 },/* 30 29 29 30 29 29 30 29 30 30 29 30 30 384
+1478 */{ 0 , 2 , 3 , 37552 },/* 30 29 29 30 29 29 30 29 30 29 30 30 0 354
+1479 */{ 10 , 1 , 23 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1480 */{ 0 , 2 , 11 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 0 354
+1481 */{ 0 , 1 , 30 , 29856 },/* 29 30 30 30 29 30 29 29 30 29 30 29 0 354
+1482 */{ 8 , 1 , 19 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1483 */{ 0 , 2 , 7 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1484 */{ 0 , 1 , 28 , 21424 },/* 29 30 29 30 29 29 30 30 30 29 30 30 0 355
+1485 */{ 4 , 1 , 17 , 9656 },/* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1486 */{ 0 , 2 , 5 , 9584 },/* 29 29 30 29 29 30 29 30 29 30 30 30 0 354
+1487 */{ 0 , 1 , 25 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1488 */{ 1 , 1 , 14 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1489 */{ 0 , 2 , 1 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 0 354
+1490 */{ 9 , 1 , 21 , 27304 },/* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1491 */{ 0 , 2 , 9 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1492 */{ 0 , 1 , 29 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1493 */{ 5 , 1 , 18 , 19304 },/* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1494 */{ 0 , 2 , 6 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1495 */{ 0 , 1 , 26 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1496 */{ 3 , 1 , 16 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 29 383
+1497 */{ 0 , 2 , 2 , 53920 },/* 30 30 29 30 29 29 30 29 30 29 30 29 0 354
+1498 */{ 11 , 1 , 22 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 29 384
+1499 */{ 0 , 2 , 10 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1500 */{ 0 , 1 , 31 , 5792 },/* 29 29 29 30 29 30 30 29 30 29 30 29 0 353
+1501 */{ 7 , 1 , 19 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1502 */{ 0 , 2 , 7 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 0 355
+1503 */{ 0 , 1 , 28 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1504 */{ 4 , 1 , 17 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1505 */{ 0 , 2 , 4 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1506 */{ 0 , 1 , 24 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1507 */{ 1 , 1 , 13 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 29 384
+1508 */{ 0 , 2 , 1 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1509 */{ 9 , 1 , 21 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1510 */{ 0 , 2 , 9 , 11680 },/* 29 29 30 29 30 30 29 30 30 29 30 29 0 354
+1511 */{ 0 , 1 , 29 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1512 */{ 5 , 1 , 19 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1513 */{ 0 , 2 , 6 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1514 */{ 0 , 1 , 26 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1515 */{ 4 , 1 , 15 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1516 */{ 0 , 2 , 3 , 27280 },/* 29 30 30 29 30 29 30 29 30 29 29 30 0 354
+1517 */{ 12 , 1 , 22 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1518 */{ 0 , 2 , 10 , 23376 },/* 29 30 29 30 30 29 30 30 29 30 29 30 0 355
+1519 */{ 0 , 1 , 31 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1520 */{ 8 , 1 , 20 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+1521 */{ 0 , 2 , 7 , 37616 },/* 30 29 29 30 29 29 30 29 30 30 30 30 0 355
+1522 */{ 0 , 1 , 28 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1523 */{ 4 , 1 , 17 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1524 */{ 0 , 2 , 4 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1525 */{ 12 , 1 , 23 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 29 384
+1526 */{ 0 , 2 , 11 , 54928 },/* 30 30 29 30 29 30 30 29 30 29 29 30 0 355
+1527 */{ 0 , 2 , 1 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+1528 */{ 10 , 1 , 22 , 11112 },/* 29 29 30 29 30 29 30 30 29 30 30 29 30 384
+1529 */{ 0 , 2 , 9 , 9952 },/* 29 29 30 29 29 30 30 29 30 30 30 29 0 354
+1530 */{ 0 , 1 , 29 , 21216 },/* 29 30 29 30 29 29 30 29 30 30 30 29 0 354
+1531 */{ 6 , 1 , 18 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1532 */{ 0 , 2 , 6 , 51536 },/* 30 30 29 29 30 29 29 30 29 30 29 30 0 354
+1533 */{ 0 , 1 , 25 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1534 */{ 2 , 1 , 14 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1535 */{ 0 , 2 , 2 , 46480 },/* 30 29 30 30 29 30 29 30 30 29 29 30 0 355
+1536 */{ 12 , 1 , 23 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1537 */{ 0 , 2 , 10 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 0 355
+1538 */{ 0 , 1 , 31 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1539 */{ 7 , 1 , 20 , 37592 },/* 30 29 29 30 29 29 30 29 30 30 29 30 30 384
+1540 */{ 0 , 2 , 8 , 37552 },/* 30 29 29 30 29 29 30 29 30 29 30 30 0 354
+1541 */{ 0 , 1 , 27 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1542 */{ 5 , 1 , 16 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+1543 */{ 0 , 2 , 4 , 27808 },/* 29 30 30 29 30 30 29 29 30 29 30 29 0 354
+1544 */{ 0 , 1 , 24 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+1545 */{ 1 , 1 , 13 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1546 */{ 0 , 2 , 1 , 19376 },/* 29 30 29 29 30 29 30 30 30 29 30 30 0 355
+1547 */{ 9 , 1 , 22 , 9656 },/* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1548 */{ 0 , 2 , 10 , 9584 },/* 29 29 30 29 29 30 29 30 29 30 30 30 0 354
+1549 */{ 0 , 1 , 29 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1550 */{ 6 , 1 , 18 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1551 */{ 0 , 2 , 5 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 0 355
+1552 */{ 0 , 1 , 26 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1553 */{ 3 , 1 , 14 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1554 */{ 0 , 2 , 2 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1555 */{ 11 , 1 , 23 , 19304 },/* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1556 */{ 0 , 2 , 11 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1557 */{ 0 , 1 , 30 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1558 */{ 7 , 1 , 20 , 21096 },/* 29 30 29 30 29 29 30 29 29 30 30 29 30 383
+1559 */{ 0 , 2 , 7 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+1560 */{ 0 , 1 , 27 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1561 */{ 5 , 1 , 16 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1562 */{ 0 , 2 , 4 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1563 */{ 0 , 1 , 24 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+1564 */{ 2 , 1 , 14 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1565 */{ 0 , 2 , 1 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1566 */{ 10 , 1 , 21 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1567 */{ 0 , 2 , 9 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1568 */{ 0 , 1 , 29 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1569 */{ 6 , 1 , 17 , 54600 },/* 30 30 29 30 29 30 29 30 29 30 29 29 30 384
+1570 */{ 0 , 2 , 5 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1571 */{ 0 , 1 , 26 , 13728 },/* 29 29 30 30 29 30 29 30 30 29 30 29 0 354
+1572 */{ 2 , 1 , 15 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+1573 */{ 0 , 2 , 2 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1574 */{ 12 , 1 , 23 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1575 */{ 0 , 2 , 11 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1576 */{ 0 , 1 , 31 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1577 */{ 8 , 1 , 19 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1578 */{ 0 , 2 , 7 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1579 */{ 0 , 1 , 27 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1580 */{ 4 , 1 , 16 , 44456 },/* 30 29 30 29 30 30 29 30 30 29 30 29 30 385
+1581 */{ 0 , 2 , 4 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1582 */{ 0 , 1 , 24 , 39024 },/* 30 29 29 30 29 29 30 30 39 30 30 30 0 365
+1583 */{ 2 , 1 , 24 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1584 */{ 0 , 2 , 12 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1585 */{ 9 , 1 , 31 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1586 */{ 0 , 2 , 18 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1587 */{ 0 , 3 , 9 , 53968 },/* 30 30 29 30 29 30 29 29 30 29 30 0 0 325
+1588 */{ 6 , 1 , 28 , 27464 },/* 29 30 30 29 30 29 30 30 29 30 29 29 30 384
+1589 */{ 0 , 2 , 15 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+1590 */{ 0 , 2 , 5 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1591 */{ 3 , 1 , 25 , 37616 },/* 30 29 29 30 29 29 30 29 30 30 30 30 29 384
+1592 */{ 0 , 2 , 13 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1593 */{ 11 , 2 , 1 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1594 */{ 0 , 2 , 20 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1595 */{ 0 , 2 , 9 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1596 */{ 8 , 1 , 29 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1597 */{ 0 , 2 , 16 , 46288 },/* 30 29 30 30 29 30 29 29 30 30 29 30 0 355
+1598 */{ 0 , 2 , 6 , 22192 },/* 29 30 29 30 29 30 30 29 30 29 30 30 0 355
+1599 */{ 4 , 1 , 27 , 9944 },/* 29 29 30 29 29 30 30 29 30 30 29 30 30 384
+1600 */{ 0 , 2 , 15 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1601 */{ 0 , 2 , 3 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+1602 */{ 2 , 1 , 23 , 51608 },/* 30 30 29 29 30 29 29 30 30 29 29 30 30 384
+1603 */{ 0 , 2 , 11 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1604 */{ 9 , 1 , 31 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+1605 */{ 0 , 2 , 18 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1606 */{ 0 , 2 , 7 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+1607 */{ 6 , 1 , 28 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+1608 */{ 0 , 2 , 16 , 19376 },/* 29 30 29 29 30 29 30 30 30 29 30 30 0 355
+1609 */{ 0 , 2 , 5 , 9648 },/* 29 29 30 29 29 30 29 30 30 29 30 30 0 354
+1610 */{ 3 , 1 , 25 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1611 */{ 0 , 2 , 13 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1612 */{ 11 , 2 , 2 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1613 */{ 0 , 2 , 19 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 0 355
+1614 */{ 0 , 2 , 9 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1615 */{ 8 , 1 , 29 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1616 */{ 0 , 2 , 17 , 39760 },/* 30 29 29 30 30 29 30 30 29 30 29 30 0 355
+1617 */{ 0 , 2 , 6 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1618 */{ 4 , 1 , 26 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1619 */{ 0 , 2 , 14 , 42224 },/* 30 29 30 29 29 30 29 29 30 30 30 30 0 355
+1620 */{ 0 , 2 , 4 , 21088 },/* 29 30 29 30 29 29 30 29 29 30 30 29 0 353
+1621 */{ 2 , 1 , 22 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1622 */{ 0 , 2 , 10 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1623 */{ 10 , 1 , 31 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1624 */{ 0 , 2 , 19 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1625 */{ 0 , 2 , 7 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+1626 */{ 6 , 1 , 28 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1627 */{ 0 , 2 , 16 , 18912 },/* 29 30 29 29 30 29 29 30 30 30 30 29 0 354
+1628 */{ 0 , 2 , 5 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1629 */{ 4 , 1 , 24 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1630 */{ 0 , 2 , 12 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1631 */{ 11 , 2 , 1 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1632 */{ 0 , 2 , 20 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1633 */{ 0 , 2 , 8 , 46496 },/* 30 29 30 30 29 30 29 30 30 29 30 29 0 355
+1634 */{ 8 , 1 , 29 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 29 384
+1635 */{ 0 , 2 , 17 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1636 */{ 0 , 2 , 7 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1637 */{ 4 , 1 , 26 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1638 */{ 0 , 2 , 14 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1639 */{ 0 , 2 , 3 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+1640 */{ 1 , 1 , 23 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1641 */{ 0 , 2 , 10 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1642 */{ 11 , 1 , 30 , 44456 },/* 30 29 30 29 30 30 29 30 30 29 30 29 30 385
+1643 */{ 0 , 2 , 19 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1644 */{ 0 , 2 , 8 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+1645 */{ 6 , 1 , 28 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1646 */{ 0 , 2 , 16 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1647 */{ 0 , 2 , 5 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1648 */{ 3 , 1 , 24 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1649 */{ 0 , 2 , 11 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+1650 */{ 11 , 2 , 1 , 27464 },/* 29 30 30 29 30 29 30 30 29 30 29 29 30 384
+1651 */{ 0 , 2 , 20 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+1652 */{ 0 , 2 , 10 , 11168 },/* 29 29 30 29 30 29 30 30 30 29 30 29 0 354
+1653 */{ 7 , 1 , 29 , 37616 },/* 30 29 29 30 29 29 30 29 30 30 30 30 29 384
+1654 */{ 0 , 2 , 17 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1655 */{ 0 , 2 , 6 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+1656 */{ 5 , 1 , 26 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1657 */{ 0 , 2 , 13 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1658 */{ 0 , 2 , 2 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+1659 */{ 3 , 1 , 23 , 39592 },/* 30 29 29 30 30 29 30 29 30 29 30 29 30 384
+1660 */{ 0 , 2 , 11 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1661 */{ 7 , 1 , 30 , 42704 },/* 30 29 30 29 29 30 30 29 30 30 29 30 29 384
+1662 */{ 0 , 2 , 18 , 42448 },/* 30 29 30 29 29 30 29 30 30 30 29 30 0 355
+1663 */{ 0 , 2 , 8 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+1664 */{ 6 , 1 , 28 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1665 */{ 0 , 2 , 15 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1666 */{ 0 , 2 , 4 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+1667 */{ 4 , 1 , 24 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1668 */{ 0 , 2 , 12 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+1669 */{ 0 , 2 , 1 , 21920 },/* 29 30 29 30 29 30 29 30 30 29 30 29 0 354
+1670 */{ 2 , 1 , 21 , 42448 },/* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+1671 */{ 0 , 2 , 9 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+1672 */{ 7 , 1 , 30 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1673 */{ 0 , 2 , 17 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1674 */{ 0 , 2 , 6 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 0 354
+1675 */{ 5 , 1 , 26 , 29864 },/* 29 30 30 30 29 30 29 29 30 29 30 29 30 384
+1676 */{ 0 , 2 , 14 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1677 */{ 0 , 2 , 2 , 44432 },/* 30 29 30 29 30 30 29 30 30 29 29 30 0 355
+1678 */{ 3 , 1 , 23 , 19880 },/* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+1679 */{ 0 , 2 , 11 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1680 */{ 8 , 1 , 31 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1681 */{ 0 , 2 , 18 , 42208 },/* 30 29 30 29 29 30 29 29 30 30 30 29 0 354
+1682 */{ 0 , 2 , 7 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+1683 */{ 6 , 1 , 27 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1684 */{ 0 , 2 , 15 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+1685 */{ 0 , 2 , 3 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 0 355
+1686 */{ 4 , 1 , 24 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+1687 */{ 0 , 2 , 12 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+1688 */{ 0 , 2 , 2 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1689 */{ 3 , 1 , 21 , 42216 },/* 30 29 30 29 29 30 29 29 30 30 30 29 30 384
+1690 */{ 0 , 2 , 9 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1691 */{ 7 , 1 , 29 , 53848 },/* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+1692 */{ 0 , 2 , 17 , 45136 },/* 30 29 29 32 29 29 29 29 29 30 29 30 0 354
+1693 */{ 0 , 2 , 5 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+1694 */{ 5 , 1 , 25 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 29 384
+1695 */{ 0 , 2 , 13 , 46496 },/* 30 29 30 30 29 30 29 30 30 29 30 29 0 355
+1696 */{ 0 , 2 , 3 , 21968 },/* 29 30 29 30 29 30 29 30 30 30 29 30 0 355
+1697 */{ 3 , 1 , 23 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+1698 */{ 0 , 2 , 11 , 18896 },/* 29 30 29 29 30 29 29 30 30 30 29 30 0 354
+1699 */{ 7 , 1 , 31 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1700 */{ 0 , 2 , 19 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1701 */{ 0 , 2 , 8 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+1702 */{ 6 , 1 , 28 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+1703 */{ 0 , 2 , 16 , 27936 },/* 29 30 30 29 30 30 29 30 29 29 30 29 0 354
+1704 */{ 0 , 2 , 5 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+1705 */{ 4 , 1 , 25 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1706 */{ 0 , 2 , 13 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+1707 */{ 0 , 2 , 3 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1708 */{ 3 , 1 , 23 , 25784 },/* 29 30 30 29 29 30 29 29 30 29 30 30 30 384
+1709 */{ 0 , 2 , 10 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1710 */{ 7 , 1 , 30 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1711 */{ 0 , 2 , 17 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+1712 */{ 0 , 2 , 7 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1713 */{ 5 , 1 , 26 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 29 384
+1714 */{ 0 , 2 , 14 , 43744 },/* 30 29 30 29 30 29 30 29 30 30 30 29 0 355
+1715 */{ 0 , 2 , 4 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1716 */{ 3 , 1 , 24 , 51568 },/* 30 30 29 29 30 29 29 30 29 30 30 30 29 384
+1717 */{ 0 , 2 , 11 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+1718 */{ 8 , 1 , 31 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1719 */{ 0 , 2 , 19 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1720 */{ 0 , 2 , 8 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1721 */{ 6 , 1 , 28 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1722 */{ 0 , 2 , 16 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1723 */{ 0 , 2 , 5 , 42704 },/* 30 29 30 29 29 30 30 29 30 30 29 30 0 355
+1724 */{ 4 , 1 , 26 , 21224 },/* 29 30 29 30 29 29 30 29 30 30 30 29 30 384
+1725 */{ 0 , 2 , 13 , 21200 },/* 29 30 29 30 29 29 30 29 30 30 29 30 0 354
+1726 */{ 0 , 2 , 2 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1727 */{ 3 , 1 , 22 , 58536 },/* 30 30 30 29 29 30 29 29 30 29 30 29 30 384
+1728 */{ 0 , 2 , 10 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+1729 */{ 7 , 1 , 29 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1730 */{ 0 , 2 , 17 , 40272 },/* 30 29 29 30 30 30 29 30 29 30 29 30 0 355
+1731 */{ 0 , 2 , 7 , 21920 },/* 29 30 29 30 29 30 29 30 30 29 30 29 0 354
+1732 */{ 5 , 1 , 27 , 42448 },/* 30 29 30 29 29 30 29 30 30 30 29 30 29 384
+1733 */{ 0 , 2 , 14 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+1734 */{ 0 , 2 , 4 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1735 */{ 4 , 1 , 24 , 43192 },/* 30 29 30 29 30 29 29 29 30 29 30 30 30 384
+1736 */{ 0 , 2 , 12 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 0 354
+1737 */{ 9 , 1 , 31 , 27288 },/* 29 30 30 29 30 29 30 29 30 29 29 30 30 384
+1738 */{ 0 , 2 , 19 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1739 */{ 0 , 2 , 8 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1740 */{ 6 , 1 , 29 , 19880 },/* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+1741 */{ 0 , 2 , 16 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1742 */{ 0 , 2 , 5 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1743 */{ 4 , 1 , 26 , 20848 },/* 29 30 29 30 29 29 29 30 29 30 30 30 29 383
+1744 */{ 0 , 2 , 13 , 53600 },/* 30 30 29 30 29 29 29 30 29 30 30 29 0 354
+1745 */{ 0 , 2 , 1 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 0 355
+1746 */{ 3 , 1 , 22 , 27280 },/* 29 30 30 29 30 29 30 29 30 29 29 30 29 383
+1747 */{ 0 , 2 , 9 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 0 355
+1748 */{ 7 , 1 , 30 , 23376 },/* 29 30 29 30 30 29 30 30 29 30 29 30 29 384
+1749 */{ 0 , 2 , 17 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+1750 */{ 0 , 2 , 7 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1751 */{ 5 , 1 , 27 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1752 */{ 0 , 2 , 15 , 41680 },/* 30 29 30 29 29 29 30 29 30 30 29 30 0 354
+1753 */{ 0 , 2 , 3 , 53592 },/* 30 30 29 30 29 29 29 30 29 30 29 30 30 384
+1754 */{ 4 , 2 , 22 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+1755 */{ 0 , 2 , 11 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1756 */{ 9 , 1 , 31 , 54928 },/* 30 30 29 30 29 30 30 29 30 29 29 30 29 384
+1757 */{ 0 , 2 , 18 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+1758 */{ 0 , 2 , 8 , 21968 },/* 29 30 29 30 29 30 29 30 30 30 29 30 0 355
+1759 */{ 6 , 1 , 29 , 10968 },/* 29 29 30 29 30 29 30 29 30 30 29 30 30 384
+1760 */{ 0 , 2 , 17 , 17840 },/* 29 30 29 29 29 30 29 30 30 29 30 30 0 354
+1761 */{ 0 , 2 , 5 , 41648 },/* 30 29 30 29 29 29 30 29 30 29 30 30 0 354
+1762 */{ 5 , 1 , 25 , 45400 },/* 30 29 30 30 29 29 29 30 29 30 29 30 30 384
+1763 */{ 0 , 2 , 13 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1764 */{ 0 , 2 , 2 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1765 */{ 2 , 1 , 21 , 46480 },/* 30 29 30 30 29 30 29 30 30 29 29 30 29 384
+1766 */{ 0 , 2 , 9 , 44384 },/* 30 29 30 29 30 30 29 30 29 30 30 29 0 355
+1767 */{ 7 , 1 , 30 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+1768 */{ 0 , 2 , 18 , 21360 },/* 29 30 29 30 29 29 30 30 29 30 30 30 0 355
+1769 */{ 0 , 2 , 7 , 17776 },/* 29 30 29 29 29 30 29 30 29 30 30 30 0 354
+1770 */{ 5 , 1 , 27 , 25272 },/* 29 30 30 29 29 29 30 29 30 29 30 30 30 384
+1771 */{ 0 , 2 , 15 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1772 */{ 0 , 2 , 4 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 0 354
+1773 */{ 3 , 1 , 23 , 27816 },/* 29 30 30 29 30 30 29 29 30 29 30 29 30 384
+1774 */{ 0 , 2 , 11 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1775 */{ 10 , 1 , 31 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+1776 */{ 0 , 2 , 19 , 42704 },/* 30 29 30 29 29 30 30 29 30 30 29 30 0 355
+1777 */{ 0 , 2 , 8 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1778 */{ 6 , 1 , 28 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+1779 */{ 0 , 2 , 16 , 42336 },/* 30 29 30 29 29 30 29 30 29 30 30 29 0 354
+1780 */{ 0 , 2 , 5 , 53920 },/* 30 30 29 30 29 29 30 29 30 29 30 29 0 354
+1781 */{ 5 , 1 , 24 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 29 384
+1782 */{ 0 , 2 , 12 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1783 */{ 0 , 2 , 2 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1784 */{ 3 , 1 , 22 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+1785 */{ 0 , 2 , 9 , 42704 },/* 30 29 30 29 29 30 30 29 30 30 29 30 0 355
+1786 */{ 7 , 1 , 30 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1787 */{ 0 , 2 , 18 , 19120 },/* 29 30 29 29 30 29 30 29 30 29 30 30 0 354
+1788 */{ 0 , 2 , 7 , 43216 },/* 30 29 30 29 30 29 29 29 30 30 29 30 0 354
+1789 */{ 5 , 1 , 26 , 53928 },/* 30 30 29 30 29 29 30 29 30 29 30 29 30 384
+1790 */{ 0 , 2 , 14 , 45728 },/* 30 29 30 30 29 29 30 29 30 29 30 29 0 354
+1791 */{ 0 , 2 , 3 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1792 */{ 4 , 1 , 24 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1793 */{ 0 , 2 , 11 , 19872 },/* 29 30 29 29 30 30 29 30 30 29 30 29 0 354
+1794 */{ 0 , 1 , 31 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 0 355
+1795 */{ 2 , 1 , 21 , 19128 },/* 29 30 29 29 30 29 30 29 30 29 30 30 30 384
+1796 */{ 0 , 2 , 9 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1797 */{ 6 , 1 , 28 , 43192 },/* 30 29 30 29 30 29 29 29 30 29 30 30 30 384
+1798 */{ 0 , 2 , 16 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1799 */{ 0 , 2 , 5 , 27280 },/* 29 30 30 29 30 29 30 29 30 29 29 30 0 354
+1800 */{ 4 , 1 , 25 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1801 */{ 0 , 2 , 13 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 0 355
+1802 */{ 0 , 2 , 3 , 11168 },/* 29 29 30 29 30 29 30 30 30 29 30 29 0 354
+1803 */{ 2 , 1 , 23 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+1804 */{ 0 , 2 , 11 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+1805 */{ 6 , 1 , 31 , 20848 },/* 29 30 29 30 29 29 29 30 29 30 30 30 29 383
+1806 */{ 0 , 2 , 18 , 53600 },/* 30 30 29 30 29 29 29 30 29 30 30 29 0 354
+1807 */{ 0 , 2 , 7 , 58544 },/* 30 30 30 29 29 30 29 29 30 29 30 30 0 355
+1808 */{ 5 , 1 , 28 , 27280 },/* 29 30 30 29 30 29 30 29 30 29 29 30 29 383
+1809 */{ 0 , 2 , 14 , 55952 },/* 30 30 29 30 30 29 30 29 30 29 29 30 0 355
+1810 */{ 0 , 2 , 4 , 23376 },/* 29 30 29 30 30 29 30 30 29 30 29 30 0 355
+1811 */{ 3 , 1 , 25 , 11112 },/* 29 29 30 29 30 29 30 30 29 30 30 29 30 384
+1812 */{ 0 , 2 , 13 , 10976 },/* 29 29 30 29 30 29 30 29 30 30 30 29 0 354
+1813 */{ 0 , 2 , 1 , 41696 },/* 30 29 30 29 29 29 30 29 30 30 30 29 0 354
+1814 */{ 2 , 1 , 21 , 53608 },/* 30 30 29 30 29 29 29 30 29 30 30 29 30 384
+1815 */{ 0 , 2 , 9 , 51536 },/* 30 30 29 29 30 29 29 30 29 30 29 30 0 354
+1816 */{ 6 , 1 , 29 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1817 */{ 0 , 2 , 16 , 46368 },/* 30 29 30 30 29 30 29 30 29 29 30 29 0 354
+1818 */{ 0 , 2 , 5 , 46736 },/* 30 29 30 30 29 30 30 29 30 29 29 30 0 355
+1819 */{ 4 , 1 , 26 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1820 */{ 0 , 2 , 14 , 21968 },/* 29 30 29 30 29 30 29 30 30 30 29 30 0 355
+1821 */{ 0 , 2 , 3 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1822 */{ 3 , 1 , 23 , 41688 },/* 30 29 30 29 29 29 30 29 30 30 29 30 30 384
+1823 */{ 0 , 2 , 11 , 41648 },/* 30 29 30 29 29 29 30 29 30 29 30 30 0 354
+1824 */{ 7 , 1 , 31 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1825 */{ 0 , 2 , 18 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1826 */{ 0 , 2 , 7 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+1827 */{ 5 , 1 , 27 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+1828 */{ 0 , 2 , 15 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+1829 */{ 0 , 2 , 4 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 0 355
+1830 */{ 4 , 1 , 25 , 9656 },/* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1831 */{ 0 , 2 , 13 , 9584 },/* 29 29 30 29 29 30 29 30 29 30 30 30 0 354
+1832 */{ 9 , 2 , 2 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1833 */{ 0 , 2 , 20 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1834 */{ 0 , 2 , 9 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 0 354
+1835 */{ 6 , 1 , 29 , 27816 },/* 29 30 30 29 30 30 29 29 30 29 30 29 30 384
+1836 */{ 0 , 2 , 17 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1837 */{ 0 , 2 , 5 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1838 */{ 4 , 1 , 26 , 21352 },/* 29 30 29 30 29 29 30 30 29 30 30 29 30 384
+1839 */{ 0 , 2 , 14 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1840 */{ 0 , 2 , 3 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1841 */{ 3 , 1 , 23 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 29 383
+1842 */{ 0 , 2 , 10 , 53920 },/* 30 30 29 30 29 29 30 29 30 29 30 29 0 354
+1843 */{ 7 , 1 , 30 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 29 384
+1844 */{ 0 , 2 , 18 , 54608 },/* 30 30 29 30 29 30 29 30 29 30 29 30 0 355
+1845 */{ 0 , 2 , 7 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+1846 */{ 5 , 1 , 27 , 43728 },/* 30 29 30 29 30 29 30 29 30 30 29 30 29 384
+1847 */{ 0 , 2 , 15 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 0 355
+1848 */{ 0 , 2 , 5 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1849 */{ 4 , 1 , 24 , 42328 },/* 30 29 30 29 29 30 29 30 29 30 29 30 30 384
+1850 */{ 0 , 2 , 12 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1851 */{ 8 , 2 , 1 , 53848 },/* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+1852 */{ 0 , 2 , 20 , 45712 },/* 30 29 30 30 29 29 30 29 30 29 29 30 0 354
+1853 */{ 0 , 2 , 8 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1854 */{ 7 , 1 , 29 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1855 */{ 0 , 2 , 17 , 11680 },/* 29 29 30 29 30 30 29 30 30 29 30 29 0 354
+1856 */{ 0 , 2 , 6 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 0 355
+1857 */{ 5 , 1 , 26 , 19128 },/* 29 30 29 29 30 29 30 29 30 29 30 30 30 384
+1858 */{ 0 , 2 , 14 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1859 */{ 0 , 2 , 3 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1860 */{ 3 , 1 , 23 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1861 */{ 0 , 2 , 10 , 27280 },/* 29 30 30 29 30 29 30 29 30 29 29 30 0 354
+1862 */{ 8 , 1 , 30 , 44360 },/* 30 29 30 29 30 30 29 30 29 30 29 29 30 384
+1863 */{ 0 , 2 , 18 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 0 355
+1864 */{ 0 , 2 , 8 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1865 */{ 5 , 1 , 27 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+1866 */{ 0 , 2 , 15 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 0 355
+1867 */{ 0 , 2 , 5 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1868 */{ 4 , 1 , 25 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1869 */{ 0 , 2 , 11 , 58528 },/* 30 30 30 29 29 30 29 29 30 29 30 29 0 354
+1870 */{ 10 , 1 , 31 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 29 384
+1871 */{ 0 , 2 , 19 , 55952 },/* 30 30 29 30 30 29 30 29 30 29 29 30 0 355
+1872 */{ 0 , 2 , 9 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1873 */{ 6 , 1 , 29 , 11112 },/* 29 29 30 29 30 29 30 30 29 30 30 29 30 384
+1874 */{ 0 , 2 , 17 , 10976 },/* 29 29 30 29 30 29 30 29 30 30 30 29 0 354
+1875 */{ 0 , 2 , 6 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1876 */{ 5 , 1 , 26 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1877 */{ 0 , 2 , 13 , 51536 },/* 30 30 29 29 30 29 29 30 29 30 29 30 0 354
+1878 */{ 0 , 2 , 2 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1879 */{ 3 , 1 , 22 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1880 */{ 0 , 2 , 10 , 46736 },/* 30 29 30 30 29 30 30 29 30 29 29 30 0 355
+1881 */{ 7 , 1 , 30 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 29 384
+1882 */{ 0 , 2 , 18 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 0 355
+1883 */{ 0 , 2 , 8 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1884 */{ 5 , 1 , 28 , 37592 },/* 30 29 29 30 29 29 30 29 30 30 29 30 30 384
+1885 */{ 0 , 2 , 15 , 37552 },/* 30 29 29 30 29 29 30 29 30 29 30 30 0 354
+1886 */{ 0 , 2 , 4 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1887 */{ 4 , 1 , 24 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1888 */{ 0 , 2 , 12 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+1889 */{ 0 , 1 , 31 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1890 */{ 2 , 1 , 21 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1891 */{ 0 , 2 , 9 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 0 355
+1892 */{ 6 , 1 , 30 , 9656 },/* 29 29 30 29 29 30 29 30 30 29 30 30 30 384
+1893 */{ 0 , 2 , 17 , 9584 },/* 29 29 30 29 29 30 29 30 29 30 30 30 0 354
+1894 */{ 0 , 2 , 6 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1895 */{ 5 , 1 , 26 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1896 */{ 0 , 2 , 13 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 0 355
+1897 */{ 0 , 2 , 2 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1898 */{ 3 , 1 , 22 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1899 */{ 0 , 2 , 10 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1900 */{ 8 , 1 , 31 , 19304 },/* 29 30 29 29 30 29 30 30 29 30 30 29 30 384
+1901 */{ 0 , 2 , 19 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1902 */{ 0 , 2 , 8 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1903 */{ 5 , 1 , 29 , 21096 },/* 29 30 29 30 29 29 30 29 29 30 30 29 30 383
+1904 */{ 0 , 2 , 16 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+1905 */{ 0 , 2 , 4 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1906 */{ 4 , 1 , 25 , 27304 },/* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1907 */{ 0 , 2 , 13 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1908 */{ 0 , 2 , 2 , 39632 },/* 30 29 29 30 30 29 30 29 30 30 29 30 0 355
+1909 */{ 2 , 1 , 22 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1910 */{ 0 , 2 , 10 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1911 */{ 6 , 1 , 30 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1912 */{ 0 , 2 , 18 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1913 */{ 0 , 2 , 6 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1914 */{ 5 , 1 , 26 , 55624 },/* 30 30 29 30 30 29 29 30 29 30 29 29 30 384
+1915 */{ 0 , 2 , 14 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1916 */{ 0 , 2 , 4 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1917 */{ 2 , 1 , 23 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1918 */{ 0 , 2 , 11 , 38352 },/* 30 29 29 30 29 30 29 30 30 30 29 30 0 355
+1919 */{ 7 , 2 , 1 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+1920 */{ 0 , 2 , 20 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1921 */{ 0 , 2 , 8 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1922 */{ 5 , 1 , 28 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1923 */{ 0 , 2 , 16 , 27280 },/* 29 30 30 29 30 29 30 29 30 29 29 30 0 354
+1924 */{ 0 , 2 , 5 , 44352 },/* 30 29 30 29 30 30 29 30 29 30 29 29 0 354
+1925 */{ 4 , 1 , 24 , 46504 },/* 30 29 30 30 29 30 29 30 30 29 30 29 30 385
+1926 */{ 0 , 2 , 13 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1927 */{ 0 , 2 , 2 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1928 */{ 2 , 1 , 23 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1929 */{ 0 , 2 , 10 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1930 */{ 6 , 1 , 30 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1931 */{ 0 , 2 , 17 , 58528 },/* 30 30 30 29 29 30 29 29 30 29 30 29 0 354
+1932 */{ 0 , 2 , 6 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1933 */{ 5 , 1 , 26 , 27976 },/* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1934 */{ 0 , 2 , 14 , 23376 },/* 29 30 29 30 30 29 30 30 29 30 29 30 0 355
+1935 */{ 0 , 2 , 4 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1936 */{ 3 , 1 , 24 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 29 384
+1937 */{ 0 , 2 , 11 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1938 */{ 7 , 1 , 31 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1939 */{ 0 , 2 , 19 , 51536 },/* 30 30 29 29 30 29 29 30 29 30 29 30 0 354
+1940 */{ 0 , 2 , 8 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1941 */{ 6 , 1 , 27 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1942 */{ 0 , 2 , 15 , 46736 },/* 30 29 30 30 29 30 30 29 30 29 29 30 0 355
+1943 */{ 0 , 2 , 5 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+1944 */{ 4 , 1 , 26 , 10968 },/* 29 29 30 29 30 29 30 29 30 30 29 30 30 384
+1945 */{ 0 , 2 , 13 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1946 */{ 0 , 2 , 2 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+1947 */{ 2 , 1 , 22 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+1948 */{ 0 , 2 , 10 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1949 */{ 7 , 1 , 29 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+1950 */{ 0 , 2 , 17 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+1951 */{ 0 , 2 , 6 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1952 */{ 5 , 1 , 27 , 22184 },/* 29 30 29 30 29 30 30 29 30 29 30 29 30 384
+1953 */{ 0 , 2 , 14 , 19888 },/* 29 30 29 29 30 30 29 30 30 29 30 30 0 355
+1954 */{ 0 , 2 , 4 , 9648 },/* 29 29 30 29 29 30 29 30 30 29 30 30 0 354
+1955 */{ 3 , 1 , 24 , 37560 },/* 30 29 29 30 29 29 30 29 30 29 30 30 30 384
+1956 */{ 0 , 2 , 12 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1957 */{ 8 , 1 , 31 , 43352 },/* 30 29 30 29 30 29 29 30 29 30 29 30 30 384
+1958 */{ 0 , 2 , 19 , 26960 },/* 29 30 30 29 30 29 29 30 29 30 29 30 0 354
+1959 */{ 0 , 2 , 8 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1960 */{ 6 , 1 , 28 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1961 */{ 0 , 2 , 15 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1962 */{ 0 , 2 , 5 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1963 */{ 4 , 1 , 25 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1964 */{ 0 , 2 , 13 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1965 */{ 0 , 2 , 2 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 0 354
+1966 */{ 3 , 1 , 22 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+1967 */{ 0 , 2 , 9 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1968 */{ 7 , 1 , 30 , 27304 },/* 29 30 30 29 30 29 30 29 30 29 30 29 30 384
+1969 */{ 0 , 2 , 17 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1970 */{ 0 , 2 , 6 , 39632 },/* 30 29 29 30 30 29 30 29 30 30 29 30 0 355
+1971 */{ 5 , 1 , 27 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1972 */{ 0 , 2 , 15 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+1973 */{ 0 , 2 , 3 , 42208 },/* 30 29 30 29 29 30 29 29 30 30 30 29 0 354
+1974 */{ 4 , 1 , 23 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1975 */{ 0 , 2 , 11 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1976 */{ 8 , 1 , 31 , 54600 },/* 30 30 29 30 29 30 29 30 29 30 29 29 30 384
+1977 */{ 0 , 2 , 18 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1978 */{ 0 , 2 , 7 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 0 355
+1979 */{ 6 , 1 , 28 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1980 */{ 0 , 2 , 16 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1981 */{ 0 , 2 , 5 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1982 */{ 4 , 1 , 25 , 42200 },/* 30 29 30 29 29 30 29 29 30 30 29 30 30 384
+1983 */{ 0 , 2 , 13 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1984 */{ 10 , 2 , 2 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1985 */{ 0 , 2 , 20 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1986 */{ 0 , 2 , 9 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1987 */{ 6 , 1 , 29 , 46504 },/* 30 29 30 30 29 30 29 30 30 29 30 29 30 385
+1988 */{ 0 , 2 , 18 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1989 */{ 0 , 2 , 6 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1990 */{ 5 , 1 , 27 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1991 */{ 0 , 2 , 15 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1992 */{ 0 , 2 , 4 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1993 */{ 3 , 1 , 23 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1994 */{ 0 , 2 , 10 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1995 */{ 8 , 1 , 31 , 27976 },/* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1996 */{ 0 , 2 , 19 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1997 */{ 0 , 2 , 8 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1998 */{ 5 , 1 , 28 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1999 */{ 0 , 2 , 16 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+2000 */{ 0 , 2 , 5 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+2001 */{ 4 , 1 , 24 , 58536 },/* 30 30 30 29 29 30 29 29 30 29 30 29 30 384
+2002 */{ 0 , 2 , 12 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+2003 */{ 0 , 2 , 1 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+2004 */{ 2 , 1 , 22 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2005 */{ 0 , 2 , 9 , 22208 },/* 29 30 29 30 29 30 30 29 30 30 29 29 0 354
+2006 */{ 7 , 1 , 29 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+2007 */{ 0 , 2 , 18 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+2008 */{ 0 , 2 , 7 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+2009 */{ 5 , 1 , 26 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+2010 */{ 0 , 2 , 14 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2011 */{ 0 , 2 , 3 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+2012 */{ 3 , 1 , 23 , 47696 },/* 30 29 30 30 30 29 30 29 29 30 29 30 29 384
+2013 */{ 0 , 2 , 10 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+2014 */{ 9 , 1 , 31 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2015 */{ 0 , 2 , 19 , 19360 },/* 29 30 29 29 30 29 30 30 30 29 30 29 0 354
+2016 */{ 0 , 2 , 8 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+2017 */{ 5 , 1 , 28 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+2018 */{ 0 , 2 , 16 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+2019 */{ 0 , 2 , 5 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2020 */{ 4 , 1 , 25 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+2021 */{ 0 , 2 , 12 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+2022 */{ 0 , 2 , 1 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2023 */{ 2 , 1 , 22 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2024 */{ 0 , 2 , 10 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+2025 */{ 6 , 1 , 29 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+2026 */{ 0 , 2 , 17 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+2027 */{ 0 , 2 , 7 , 21104 },/* 29 30 29 30 29 29 30 29 29 30 30 30 0 354
+2028 */{ 5 , 1 , 27 , 26928 },/* 29 30 30 29 30 29 29 30 29 29 30 30 29 383
+2029 */{ 0 , 2 , 13 , 55600 },/* 30 30 29 30 30 29 29 30 29 29 30 30 0 355
+2030 */{ 0 , 2 , 3 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+2031 */{ 3 , 1 , 23 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 29 384
+2032 */{ 0 , 2 , 11 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2033 */{ 11 , 1 , 31 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+2034 */{ 0 , 2 , 19 , 19168 },/* 29 30 29 29 30 29 30 29 30 30 30 29 0 354
+2035 */{ 0 , 2 , 8 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+2036 */{ 6 , 1 , 28 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+2037 */{ 0 , 2 , 15 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+2038 */{ 0 , 2 , 4 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+2039 */{ 5 , 1 , 24 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 29 384
+2040 */{ 0 , 2 , 12 , 46752 },/* 30 29 30 30 29 30 30 29 30 29 30 29 0 355
+2041 */{ 0 , 2 , 1 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2042 */{ 2 , 1 , 22 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+2043 */{ 0 , 2 , 10 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+2044 */{ 7 , 1 , 30 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+2045 */{ 0 , 2 , 17 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+2046 */{ 0 , 2 , 6 , 45648 },/* 30 29 30 30 29 29 30 29 29 30 29 30 0 354
+2047 */{ 5 , 1 , 26 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+2048 */{ 0 , 2 , 14 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+2049 */{ 0 , 2 , 2 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+2050 */{ 3 , 1 , 23 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 29 384
+ */};
+
+
+ internal override int MinCalendarYear {
+ get
+ {
+ return (MIN_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override int MaxCalendarYear {
+ get
+ {
+ return (MAX_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override DateTime MinDate {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ internal override DateTime MaxDate {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ internal override EraInfo[] CalEraInfo {
+ get
+ {
+ return null;
+ }
+ }
+
+ internal override int GetYearInfo(int LunarYear, int Index) {
+ if ((LunarYear < MIN_LUNISOLAR_YEAR) || (LunarYear > MAX_LUNISOLAR_YEAR)) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ MIN_LUNISOLAR_YEAR,
+ MAX_LUNISOLAR_YEAR ));
+ }
+ Contract.EndContractBlock();
+ return yinfo[LunarYear - MIN_LUNISOLAR_YEAR, Index];
+ }
+
+ internal override int GetYear(int year, DateTime time)
+ {
+ return year;
+ }
+
+ internal override int GetGregorianYear(int year, int era)
+ {
+ if (era != CurrentEra && era != GregorianEra)
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+
+ if (year < MIN_LUNISOLAR_YEAR || year > MAX_LUNISOLAR_YEAR)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"), MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR));
+ }
+ Contract.EndContractBlock();
+
+ return year;
+ }
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of KoreanLunisolarCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+ /*
+ internal static Calendar GetDefaultInstance()
+ {
+ if (m_defaultInstance == null) {
+ m_defaultInstance = new KoreanLunisolarCalendar();
+ }
+ return (m_defaultInstance);
+ }
+ */
+
+ // Construct an instance of KoreanLunisolar calendar.
+
+ public KoreanLunisolarCalendar() {
+ }
+
+
+
+ public override int GetEra(DateTime time) {
+ CheckTicksRange(time.Ticks);
+ return (GregorianEra);
+ }
+
+ internal override int BaseCalendarID {
+ get {
+ return (CAL_KOREA);
+ }
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_KOREANLUNISOLAR);
+ }
+ }
+
+
+
+ public override int[] Eras {
+ get {
+ return (new int[] {GregorianEra});
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/NumberFormatInfo.cs b/src/mscorlib/src/System/Globalization/NumberFormatInfo.cs
new file mode 100644
index 0000000000..fae91c2a1d
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/NumberFormatInfo.cs
@@ -0,0 +1,842 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System.Security.Permissions;
+ using System.Runtime.Serialization;
+ using System.Text;
+ using System;
+ using System.Diagnostics.Contracts;
+ //
+ // Property Default Description
+ // PositiveSign '+' Character used to indicate positive values.
+ // NegativeSign '-' Character used to indicate negative values.
+ // NumberDecimalSeparator '.' The character used as the decimal separator.
+ // NumberGroupSeparator ',' The character used to separate groups of
+ // digits to the left of the decimal point.
+ // NumberDecimalDigits 2 The default number of decimal places.
+ // NumberGroupSizes 3 The number of digits in each group to the
+ // left of the decimal point.
+ // NaNSymbol "NaN" The string used to represent NaN values.
+ // PositiveInfinitySymbol"Infinity" The string used to represent positive
+ // infinities.
+ // NegativeInfinitySymbol"-Infinity" The string used to represent negative
+ // infinities.
+ //
+ //
+ //
+ // Property Default Description
+ // CurrencyDecimalSeparator '.' The character used as the decimal
+ // separator.
+ // CurrencyGroupSeparator ',' The character used to separate groups
+ // of digits to the left of the decimal
+ // point.
+ // CurrencyDecimalDigits 2 The default number of decimal places.
+ // CurrencyGroupSizes 3 The number of digits in each group to
+ // the left of the decimal point.
+ // CurrencyPositivePattern 0 The format of positive values.
+ // CurrencyNegativePattern 0 The format of negative values.
+ // CurrencySymbol "$" String used as local monetary symbol.
+ //
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ sealed public partial class NumberFormatInfo : ICloneable, IFormatProvider
+ {
+ // invariantInfo is constant irrespective of your current culture.
+ private static volatile NumberFormatInfo invariantInfo;
+
+ // READTHIS READTHIS READTHIS
+ // This class has an exact mapping onto a native structure defined in COMNumber.cpp
+ // DO NOT UPDATE THIS WITHOUT UPDATING THAT STRUCTURE. IF YOU ADD BOOL, ADD THEM AT THE END.
+ // ALSO MAKE SURE TO UPDATE mscorlib.h in the VM directory to check field offsets.
+ // READTHIS READTHIS READTHIS
+ internal int[] numberGroupSizes = new int[] {3};
+ internal int[] currencyGroupSizes = new int[] {3};
+ internal int[] percentGroupSizes = new int[] {3};
+ internal String positiveSign = "+";
+ internal String negativeSign = "-";
+ internal String numberDecimalSeparator = ".";
+ internal String numberGroupSeparator = ",";
+ internal String currencyGroupSeparator = ",";
+ internal String currencyDecimalSeparator = ".";
+ internal String currencySymbol = "\x00a4"; // U+00a4 is the symbol for International Monetary Fund.
+ // The alternative currency symbol used in Win9x ANSI codepage, that can not roundtrip between ANSI and Unicode.
+ // Currently, only ja-JP and ko-KR has non-null values (which is U+005c, backslash)
+ // NOTE: The only legal values for this string are null and "\x005c"
+ internal String ansiCurrencySymbol = null;
+ internal String nanSymbol = "NaN";
+ internal String positiveInfinitySymbol = "Infinity";
+ internal String negativeInfinitySymbol = "-Infinity";
+ internal String percentDecimalSeparator = ".";
+ internal String percentGroupSeparator = ",";
+ internal String percentSymbol = "%";
+ internal String perMilleSymbol = "\u2030";
+
+ [OptionalField(VersionAdded = 2)]
+ internal String[] nativeDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
+
+ // an index which points to a record in Culture Data Table.
+ // We shouldn't be persisting dataItem (since its useless & we weren't using it),
+ // but since COMNumber.cpp uses it and since serialization isn't implimented, its stuck for now.
+ [OptionalField(VersionAdded = 1)]
+ internal int m_dataItem = 0; // NEVER USED, DO NOT USE THIS! (Serialized in Everett)
+
+ internal int numberDecimalDigits = 2;
+ internal int currencyDecimalDigits = 2;
+ internal int currencyPositivePattern = 0;
+ internal int currencyNegativePattern = 0;
+ internal int numberNegativePattern = 1;
+ internal int percentPositivePattern = 0;
+ internal int percentNegativePattern = 0;
+ internal int percentDecimalDigits = 2;
+
+ [OptionalField(VersionAdded = 2)]
+ internal int digitSubstitution = 1; // DigitShapes.None
+
+ internal bool isReadOnly=false;
+ // We shouldn't be persisting m_useUserOverride (since its useless & we weren't using it),
+ // but since COMNumber.cpp uses it and since serialization isn't implimented, its stuck for now.
+ [OptionalField(VersionAdded = 1)]
+ internal bool m_useUserOverride=false; // NEVER USED, DO NOT USE THIS! (Serialized in Everett)
+
+ // Is this NumberFormatInfo for invariant culture?
+ [OptionalField(VersionAdded = 2)]
+ internal bool m_isInvariant=false;
+
+ public NumberFormatInfo() : this(null) {
+ }
+
+#region Serialization
+ // Check if NumberFormatInfo was not set up ambiguously for parsing as number and currency
+ // eg. if the NumberDecimalSeparator and the NumberGroupSeparator were the same. This check
+ // is solely for backwards compatibility / version tolerant serialization
+ [OptionalField(VersionAdded = 1)]
+ internal bool validForParseAsNumber = true; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey/Everett)
+ [OptionalField(VersionAdded = 1)]
+ internal bool validForParseAsCurrency = true; // NEVER USED, DO NOT USE THIS! (Serialized in Whidbey/Everett)
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+#if !FEATURE_CORECLR
+ // Update these legacy flags, so that 1.1/2.0 versions of the framework
+ // can still throw while parsing; even when using a de-serialized
+ // NumberFormatInfo from a 4.0+ version of the framework
+ if (numberDecimalSeparator != numberGroupSeparator) {
+ validForParseAsNumber = true;
+ } else {
+ validForParseAsNumber = false;
+ }
+
+ if ((numberDecimalSeparator != numberGroupSeparator) &&
+ (numberDecimalSeparator != currencyGroupSeparator) &&
+ (currencyDecimalSeparator != numberGroupSeparator) &&
+ (currencyDecimalSeparator != currencyGroupSeparator)) {
+ validForParseAsCurrency = true;
+ } else {
+ validForParseAsCurrency = false;
+ }
+#endif // !FEATURE_CORECLR
+ }
+
+
+ [OnDeserializing]
+ private void OnDeserializing(StreamingContext ctx)
+ {
+ }
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ }
+#endregion Serialization
+
+
+ static private void VerifyDecimalSeparator(String decSep, String propertyName) {
+ if (decSep==null) {
+ throw new ArgumentNullException(propertyName,
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+
+ if (decSep.Length==0) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_EmptyDecString"));
+ }
+ Contract.EndContractBlock();
+
+ }
+
+ static private void VerifyGroupSeparator(String groupSep, String propertyName) {
+ if (groupSep==null) {
+ throw new ArgumentNullException(propertyName,
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ }
+
+ static private void VerifyNativeDigits(String [] nativeDig, String propertyName) {
+ if (nativeDig==null) {
+ throw new ArgumentNullException(propertyName,
+ Environment.GetResourceString("ArgumentNull_Array"));
+ }
+
+ if (nativeDig.Length != 10)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidNativeDigitCount"), propertyName);
+ }
+ Contract.EndContractBlock();
+
+ for(int i = 0; i < nativeDig.Length; i++)
+ {
+ if (nativeDig[i] == null)
+ {
+ throw new ArgumentNullException(propertyName,
+ Environment.GetResourceString("ArgumentNull_ArrayValue"));
+ }
+
+
+ if (nativeDig[i].Length != 1) {
+ if(nativeDig[i].Length != 2) {
+ // Not 1 or 2 UTF-16 code points
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidNativeDigitValue"), propertyName);
+ } else if(!char.IsSurrogatePair(nativeDig[i][0], nativeDig[i][1])) {
+ // 2 UTF-6 code points, but not a surrogate pair
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidNativeDigitValue"), propertyName);
+ }
+ }
+
+ if (CharUnicodeInfo.GetDecimalDigitValue(nativeDig[i], 0) != i &&
+ CharUnicodeInfo.GetUnicodeCategory(nativeDig[i], 0) != UnicodeCategory.PrivateUse) {
+ // Not the appropriate digit according to the Unicode data properties
+ // (Digit 0 must be a 0, etc.).
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidNativeDigitValue"), propertyName);
+ }
+ }
+ }
+
+ static private void VerifyDigitSubstitution(DigitShapes digitSub, String propertyName) {
+ switch(digitSub)
+ {
+ case DigitShapes.Context:
+ case DigitShapes.None:
+ case DigitShapes.NativeNational:
+ // Success.
+ break;
+
+ default:
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidDigitSubstitution"), propertyName);
+ }
+ }
+
+ // We aren't persisting dataItem any more (since its useless & we weren't using it),
+ // Ditto with m_useUserOverride. Don't use them, we use a local copy of everything.
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal NumberFormatInfo(CultureData cultureData)
+ {
+ if (cultureData != null)
+ {
+ // We directly use fields here since these data is coming from data table or Win32, so we
+ // don't need to verify their values (except for invalid parsing situations).
+ cultureData.GetNFIValues(this);
+
+ if (cultureData.IsInvariantCulture)
+ {
+ // For invariant culture
+ this.m_isInvariant = true;
+ }
+ }
+ }
+
+ [Pure]
+ private void VerifyWritable() {
+ if (isReadOnly) {
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ }
+ Contract.EndContractBlock();
+ }
+
+ // Returns a default NumberFormatInfo that will be universally
+ // supported and constant irrespective of the current culture.
+ // Used by FromString methods.
+ //
+
+ public static NumberFormatInfo InvariantInfo {
+ get {
+ if (invariantInfo == null) {
+ // Lazy create the invariant info. This cannot be done in a .cctor because exceptions can
+ // be thrown out of a .cctor stack that will need this.
+ NumberFormatInfo nfi = new NumberFormatInfo();
+ nfi.m_isInvariant = true;
+ invariantInfo = ReadOnly(nfi);
+ }
+ return invariantInfo;
+ }
+ }
+
+
+ public static NumberFormatInfo GetInstance(IFormatProvider formatProvider) {
+ // Fast case for a regular CultureInfo
+ NumberFormatInfo info;
+ CultureInfo cultureProvider = formatProvider as CultureInfo;
+ if (cultureProvider != null && !cultureProvider.m_isInherited) {
+ info = cultureProvider.numInfo;
+ if (info != null) {
+ return info;
+ }
+ else {
+ return cultureProvider.NumberFormat;
+ }
+ }
+ // Fast case for an NFI;
+ info = formatProvider as NumberFormatInfo;
+ if (info != null) {
+ return info;
+ }
+ if (formatProvider != null) {
+ info = formatProvider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo;
+ if (info != null) {
+ return info;
+ }
+ }
+ return CurrentInfo;
+ }
+
+
+
+ public Object Clone() {
+ NumberFormatInfo n = (NumberFormatInfo)MemberwiseClone();
+ n.isReadOnly = false;
+ return n;
+ }
+
+
+ public int CurrencyDecimalDigits {
+ get { return currencyDecimalDigits; }
+ set {
+ if (value < 0 || value > 99) {
+ throw new ArgumentOutOfRangeException(
+ "CurrencyDecimalDigits",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ 99));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ currencyDecimalDigits = value;
+ }
+ }
+
+
+ public String CurrencyDecimalSeparator {
+ get { return currencyDecimalSeparator; }
+ set {
+ VerifyWritable();
+ VerifyDecimalSeparator(value, "CurrencyDecimalSeparator");
+ currencyDecimalSeparator = value;
+ }
+ }
+
+
+ public bool IsReadOnly {
+ get {
+ return isReadOnly;
+ }
+ }
+
+ //
+ // Check the values of the groupSize array.
+ //
+ // Every element in the groupSize array should be between 1 and 9
+ // excpet the last element could be zero.
+ //
+ static internal void CheckGroupSize(String propName, int[] groupSize)
+ {
+ for (int i = 0; i < groupSize.Length; i++)
+ {
+ if (groupSize[i] < 1)
+ {
+ if (i == groupSize.Length - 1 && groupSize[i] == 0)
+ return;
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidGroupSize"), propName);
+ }
+ else if (groupSize[i] > 9)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidGroupSize"), propName);
+ }
+ }
+ }
+
+
+ public int[] CurrencyGroupSizes {
+ get {
+ return ((int[])currencyGroupSizes.Clone());
+ }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("CurrencyGroupSizes",
+ Environment.GetResourceString("ArgumentNull_Obj"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+
+ Int32[] inputSizes = (Int32[])value.Clone();
+ CheckGroupSize("CurrencyGroupSizes", inputSizes);
+ currencyGroupSizes = inputSizes;
+ }
+
+ }
+
+
+
+ public int[] NumberGroupSizes {
+ get {
+ return ((int[])numberGroupSizes.Clone());
+ }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("NumberGroupSizes",
+ Environment.GetResourceString("ArgumentNull_Obj"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+
+ Int32[] inputSizes = (Int32[])value.Clone();
+ CheckGroupSize("NumberGroupSizes", inputSizes);
+ numberGroupSizes = inputSizes;
+ }
+ }
+
+
+ public int[] PercentGroupSizes {
+ get {
+ return ((int[])percentGroupSizes.Clone());
+ }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("PercentGroupSizes",
+ Environment.GetResourceString("ArgumentNull_Obj"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ Int32[] inputSizes = (Int32[])value.Clone();
+ CheckGroupSize("PercentGroupSizes", inputSizes);
+ percentGroupSizes = inputSizes;
+ }
+
+ }
+
+
+ public String CurrencyGroupSeparator {
+ get { return currencyGroupSeparator; }
+ set {
+ VerifyWritable();
+ VerifyGroupSeparator(value, "CurrencyGroupSeparator");
+ currencyGroupSeparator = value;
+ }
+ }
+
+
+ public String CurrencySymbol {
+ get { return currencySymbol; }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("CurrencySymbol",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ currencySymbol = value;
+ }
+ }
+
+ // Returns the current culture's NumberFormatInfo. Used by Parse methods.
+ //
+
+ public static NumberFormatInfo CurrentInfo {
+ get {
+ System.Globalization.CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture;
+ if (!culture.m_isInherited) {
+ NumberFormatInfo info = culture.numInfo;
+ if (info != null) {
+ return info;
+ }
+ }
+ return ((NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo)));
+ }
+ }
+
+
+ public String NaNSymbol {
+ get {
+ return nanSymbol;
+ }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("NaNSymbol",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ nanSymbol = value;
+ }
+ }
+
+
+
+ public int CurrencyNegativePattern {
+ get { return currencyNegativePattern; }
+ set {
+ if (value < 0 || value > 15) {
+ throw new ArgumentOutOfRangeException(
+ "CurrencyNegativePattern",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ 15));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ currencyNegativePattern = value;
+ }
+ }
+
+
+ public int NumberNegativePattern {
+ get { return numberNegativePattern; }
+ set {
+ //
+ // NOTENOTE: the range of value should correspond to negNumberFormats[] in vm\COMNumber.cpp.
+ //
+ if (value < 0 || value > 4) {
+ throw new ArgumentOutOfRangeException(
+ "NumberNegativePattern",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ 4));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ numberNegativePattern = value;
+ }
+ }
+
+
+ public int PercentPositivePattern {
+ get { return percentPositivePattern; }
+ set {
+ //
+ // NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp.
+ //
+ if (value < 0 || value > 3) {
+ throw new ArgumentOutOfRangeException(
+ "PercentPositivePattern",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ 3));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ percentPositivePattern = value;
+ }
+ }
+
+
+ public int PercentNegativePattern {
+ get { return percentNegativePattern; }
+ set {
+ //
+ // NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp.
+ //
+ if (value < 0 || value > 11) {
+ throw new ArgumentOutOfRangeException(
+ "PercentNegativePattern",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ 11));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ percentNegativePattern = value;
+ }
+ }
+
+
+ public String NegativeInfinitySymbol {
+ get {
+ return negativeInfinitySymbol;
+ }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("NegativeInfinitySymbol",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ negativeInfinitySymbol = value;
+ }
+ }
+
+
+ public String NegativeSign {
+ get { return negativeSign; }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("NegativeSign",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ negativeSign = value;
+ }
+ }
+
+
+ public int NumberDecimalDigits {
+ get { return numberDecimalDigits; }
+ set {
+ if (value < 0 || value > 99) {
+ throw new ArgumentOutOfRangeException(
+ "NumberDecimalDigits",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ 99));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ numberDecimalDigits = value;
+ }
+ }
+
+
+ public String NumberDecimalSeparator {
+ get { return numberDecimalSeparator; }
+ set {
+ VerifyWritable();
+ VerifyDecimalSeparator(value, "NumberDecimalSeparator");
+ numberDecimalSeparator = value;
+ }
+ }
+
+
+ public String NumberGroupSeparator {
+ get { return numberGroupSeparator; }
+ set {
+ VerifyWritable();
+ VerifyGroupSeparator(value, "NumberGroupSeparator");
+ numberGroupSeparator = value;
+ }
+ }
+
+
+ public int CurrencyPositivePattern {
+ get { return currencyPositivePattern; }
+ set {
+ if (value < 0 || value > 3) {
+ throw new ArgumentOutOfRangeException(
+ "CurrencyPositivePattern",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ 3));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ currencyPositivePattern = value;
+ }
+ }
+
+
+ public String PositiveInfinitySymbol {
+ get {
+ return positiveInfinitySymbol;
+ }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("PositiveInfinitySymbol",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ positiveInfinitySymbol = value;
+ }
+ }
+
+
+ public String PositiveSign {
+ get { return positiveSign; }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("PositiveSign",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ positiveSign = value;
+ }
+ }
+
+
+ public int PercentDecimalDigits {
+ get { return percentDecimalDigits; }
+ set {
+ if (value < 0 || value > 99) {
+ throw new ArgumentOutOfRangeException(
+ "PercentDecimalDigits",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 0,
+ 99));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ percentDecimalDigits = value;
+ }
+ }
+
+
+ public String PercentDecimalSeparator {
+ get { return percentDecimalSeparator; }
+ set {
+ VerifyWritable();
+ VerifyDecimalSeparator(value, "PercentDecimalSeparator");
+ percentDecimalSeparator = value;
+ }
+ }
+
+
+ public String PercentGroupSeparator {
+ get { return percentGroupSeparator; }
+ set {
+ VerifyWritable();
+ VerifyGroupSeparator(value, "PercentGroupSeparator");
+ percentGroupSeparator = value;
+ }
+ }
+
+
+ public String PercentSymbol {
+ get {
+ return percentSymbol;
+ }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("PercentSymbol",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ percentSymbol = value;
+ }
+ }
+
+
+ public String PerMilleSymbol {
+ get { return perMilleSymbol; }
+ set {
+ if (value == null) {
+ throw new ArgumentNullException("PerMilleSymbol",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ perMilleSymbol = value;
+ }
+ }
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public String[] NativeDigits
+ {
+ get { return (String[])nativeDigits.Clone(); }
+ set
+ {
+ VerifyWritable();
+ VerifyNativeDigits(value, "NativeDigits");
+ nativeDigits = value;
+ }
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public DigitShapes DigitSubstitution
+ {
+ get { return (DigitShapes)digitSubstitution; }
+ set
+ {
+ VerifyWritable();
+ VerifyDigitSubstitution(value, "DigitSubstitution");
+ digitSubstitution = (int)value;
+ }
+ }
+
+ public Object GetFormat(Type formatType) {
+ return formatType == typeof(NumberFormatInfo)? this: null;
+ }
+
+ public static NumberFormatInfo ReadOnly(NumberFormatInfo nfi) {
+ if (nfi == null) {
+ throw new ArgumentNullException("nfi");
+ }
+ Contract.EndContractBlock();
+ if (nfi.IsReadOnly) {
+ return (nfi);
+ }
+ NumberFormatInfo info = (NumberFormatInfo)(nfi.MemberwiseClone());
+ info.isReadOnly = true;
+ return info;
+ }
+
+ // private const NumberStyles InvalidNumberStyles = unchecked((NumberStyles) 0xFFFFFC00);
+ private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
+ | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign
+ | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint
+ | NumberStyles.AllowThousands | NumberStyles.AllowExponent
+ | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier);
+
+ internal static void ValidateParseStyleInteger(NumberStyles style) {
+ // Check for undefined flags
+ if ((style & InvalidNumberStyles) != 0) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidNumberStyles"), "style");
+ }
+ Contract.EndContractBlock();
+ if ((style & NumberStyles.AllowHexSpecifier) != 0) { // Check for hex number
+ if ((style & ~NumberStyles.HexNumber) != 0) {
+ throw new ArgumentException(Environment.GetResourceString("Arg_InvalidHexStyle"));
+ }
+ }
+ }
+
+ internal static void ValidateParseStyleFloatingPoint(NumberStyles style) {
+ // Check for undefined flags
+ if ((style & InvalidNumberStyles) != 0) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidNumberStyles"), "style");
+ }
+ Contract.EndContractBlock();
+ if ((style & NumberStyles.AllowHexSpecifier) != 0) { // Check for hex number
+ throw new ArgumentException(Environment.GetResourceString("Arg_HexStyleNotSupported"));
+ }
+ }
+ } // NumberFormatInfo
+}
+
+
+
+
+
+
+
+
+
diff --git a/src/mscorlib/src/System/Globalization/NumberStyles.cs b/src/mscorlib/src/System/Globalization/NumberStyles.cs
new file mode 100644
index 0000000000..42b9847ed3
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/NumberStyles.cs
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose: Contains valid formats for Numbers recognized by
+** the Number class' parsing code.
+**
+**
+===========================================================*/
+namespace System.Globalization {
+
+ using System;
+ [Serializable]
+ [Flags]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public enum NumberStyles {
+ // Bit flag indicating that leading whitespace is allowed. Character values
+ // 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, and 0x0020 are considered to be
+ // whitespace.
+
+ None = 0x00000000,
+
+ AllowLeadingWhite = 0x00000001,
+
+ AllowTrailingWhite = 0x00000002, //Bitflag indicating trailing whitespace is allowed.
+
+ AllowLeadingSign = 0x00000004, //Can the number start with a sign char.
+ //Specified by NumberFormatInfo.PositiveSign and NumberFormatInfo.NegativeSign
+
+ AllowTrailingSign = 0x00000008, //Allow the number to end with a sign char
+
+ AllowParentheses = 0x00000010, //Allow the number to be enclosed in parens
+
+ AllowDecimalPoint = 0x00000020, //Allow a decimal point
+
+ AllowThousands = 0x00000040, //Allow thousands separators (more properly, allow group separators)
+
+ AllowExponent = 0x00000080, //Allow an exponent
+
+ AllowCurrencySymbol = 0x00000100, //Allow a currency symbol.
+
+ AllowHexSpecifier = 0x00000200, //Allow specifiying hexadecimal.
+ //Common uses. These represent some of the most common combinations of these flags.
+
+
+ Integer = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign,
+
+ HexNumber = AllowLeadingWhite | AllowTrailingWhite | AllowHexSpecifier,
+
+ Number = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign | AllowTrailingSign |
+ AllowDecimalPoint | AllowThousands,
+
+ Float = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign |
+ AllowDecimalPoint | AllowExponent,
+
+ Currency = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign | AllowTrailingSign |
+ AllowParentheses | AllowDecimalPoint | AllowThousands | AllowCurrencySymbol,
+
+ Any = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign | AllowTrailingSign |
+ AllowParentheses | AllowDecimalPoint | AllowThousands | AllowCurrencySymbol | AllowExponent,
+
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/PersianCalendar.cs b/src/mscorlib/src/System/Globalization/PersianCalendar.cs
new file mode 100644
index 0000000000..2f1ffacee7
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/PersianCalendar.cs
@@ -0,0 +1,577 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+ using System.Diagnostics.Contracts;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Notes about PersianCalendar
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ // Modern Persian calendar is a solar observation based calendar. Each new year begins on the day when the vernal equinox occurs before noon.
+ // The epoch is the date of the vernal equinox prior to the epoch of the Islamic calendar (March 19, 622 Julian or March 22, 622 Gregorian)
+
+ // There is no Persian year 0. Ordinary years have 365 days. Leap years have 366 days with the last month (Esfand) gaining the extra day.
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 0622/03/22 9999/12/31
+ ** Persian 0001/01/01 9378/10/13
+ */
+
+ [Serializable]
+ public class PersianCalendar : Calendar {
+
+
+ public static readonly int PersianEra = 1;
+
+ internal static long PersianEpoch = new DateTime(622, 3, 22).Ticks / GregorianCalendar.TicksPerDay;
+ const int ApproximateHalfYear = 180;
+
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+ internal const int MonthsPerYear = 12;
+
+ internal static int[] DaysToMonth = { 0, 31, 62, 93, 124, 155, 186, 216, 246, 276, 306, 336, 366 };
+
+ internal const int MaxCalendarYear = 9378;
+ internal const int MaxCalendarMonth = 10;
+ internal const int MaxCalendarDay = 13;
+
+ // Persian calendar (year: 1, month: 1, day:1 ) = Gregorian (year: 622, month: 3, day: 22)
+ // This is the minimal Gregorian date that we support in the PersianCalendar.
+ internal static DateTime minDate = new DateTime(622, 3, 22);
+ internal static DateTime maxDate = DateTime.MaxValue;
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of PersianCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+ /*
+ internal static Calendar GetDefaultInstance() {
+ if (m_defaultInstance == null) {
+ m_defaultInstance = new PersianCalendar();
+ }
+ return (m_defaultInstance);
+ }
+ */
+
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ // Return the type of the Persian calendar.
+ //
+
+
+ public override CalendarAlgorithmType AlgorithmType {
+ get {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ // Construct an instance of Persian calendar.
+
+ public PersianCalendar() {
+ }
+
+
+ internal override int BaseCalendarID {
+ get {
+ return (CAL_GREGORIAN);
+ }
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_PERSIAN);
+ }
+ }
+
+
+ /*=================================GetAbsoluteDatePersian==========================
+ **Action: Gets the Absolute date for the given Persian date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ long GetAbsoluteDatePersian(int year, int month, int day) {
+ if (year >= 1 && year <= MaxCalendarYear && month >= 1 && month <= 12)
+ {
+ int ordinalDay = DaysInPreviousMonths(month) + day - 1; // day is one based, make 0 based since this will be the number of days we add to beginning of year below
+ int approximateDaysFromEpochForYearStart = (int)(CalendricalCalculationsHelper.MeanTropicalYearInDays * (year - 1));
+ long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(PersianEpoch + approximateDaysFromEpochForYearStart + ApproximateHalfYear);
+ yearStart += ordinalDay;
+ return yearStart;
+ }
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
+ }
+
+ static internal void CheckTicksRange(long ticks) {
+ if (ticks < minDate.Ticks || ticks > maxDate.Ticks) {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_CalendarRange"),
+ minDate,
+ maxDate));
+ }
+ }
+
+ static internal void CheckEraRange(int era) {
+ if (era != CurrentEra && era != PersianEra) {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+ }
+
+ static internal void CheckYearRange(int year, int era) {
+ CheckEraRange(era);
+ if (year < 1 || year > MaxCalendarYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ MaxCalendarYear));
+ }
+ }
+
+ static internal void CheckYearMonthRange(int year, int month, int era) {
+ CheckYearRange(year, era);
+ if (year == MaxCalendarYear) {
+ if (month > MaxCalendarMonth) {
+ throw new ArgumentOutOfRangeException(
+ "month",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ MaxCalendarMonth));
+ }
+ }
+
+ if (month < 1 || month > 12) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
+ }
+ }
+
+ static int MonthFromOrdinalDay(int ordinalDay)
+ {
+ Contract.Assert(ordinalDay <= 366);
+ int index = 0;
+ while (ordinalDay > DaysToMonth[index])
+ index++;
+
+ return index;
+ }
+
+ static int DaysInPreviousMonths(int month)
+ {
+ Contract.Assert(1 <= month && month <= 12);
+ --month; // months are one based but for calculations use 0 based
+ return DaysToMonth[month];
+ }
+
+ /*=================================GetDatePart==========================
+ **Action: Returns a given date part of this <i>DateTime</i>. This method is used
+ ** to compute the year, day-of-year, month, or day part.
+ **Returns:
+ **Arguments:
+ **Exceptions: ArgumentException if part is incorrect.
+ ============================================================================*/
+
+ internal int GetDatePart(long ticks, int part) {
+ long NumDays; // The calculation buffer in number of days.
+
+ CheckTicksRange(ticks);
+
+ //
+ // Get the absolute date. The absolute date is the number of days from January 1st, 1 A.D.
+ // 1/1/0001 is absolute date 1.
+ //
+ NumDays = ticks / GregorianCalendar.TicksPerDay + 1;
+
+ //
+ // Calculate the appromixate Persian Year.
+ //
+
+ long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(NumDays);
+ int y = (int)(Math.Floor(((yearStart - PersianEpoch) / CalendricalCalculationsHelper.MeanTropicalYearInDays) + 0.5)) + 1;
+ Contract.Assert(y >= 1);
+
+ if (part == DatePartYear)
+ {
+ return y;
+ }
+
+ //
+ // Calculate the Persian Month.
+ //
+
+ int ordinalDay = (int)(NumDays - CalendricalCalculationsHelper.GetNumberOfDays(this.ToDateTime(y, 1, 1, 0, 0, 0, 0, 1)));
+
+ if (part == DatePartDayOfYear)
+ {
+ return ordinalDay;
+ }
+
+ int m = MonthFromOrdinalDay(ordinalDay);
+ Contract.Assert(ordinalDay >= 1);
+ Contract.Assert(m >= 1 && m <= 12);
+ if (part == DatePartMonth)
+ {
+ return m;
+ }
+
+ int d = ordinalDay - DaysInPreviousMonths(m);
+ Contract.Assert(1 <= d);
+ Contract.Assert(d <= 31);
+
+ //
+ // Calculate the Persian Day.
+ //
+
+ if (part == DatePartDay)
+ {
+ return (d);
+ }
+
+ // Incorrect part value.
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_DateTimeParsing"));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+
+ public override DateTime AddMonths(DateTime time, int months) {
+ if (months < -120000 || months > 120000) {
+ throw new ArgumentOutOfRangeException(
+ "months",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ -120000,
+ 120000));
+ }
+ Contract.EndContractBlock();
+ // Get the date in Persian calendar.
+ int y = GetDatePart(time.Ticks, DatePartYear);
+ int m = GetDatePart(time.Ticks, DatePartMonth);
+ int d = GetDatePart(time.Ticks, DatePartDay);
+ int i = m - 1 + months;
+ if (i >= 0) {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ } else {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+ int days = GetDaysInMonth(y, m);
+ if (d > days) {
+ d = days;
+ }
+ long ticks = GetAbsoluteDatePersian(y, m, d) * TicksPerDay + time.Ticks % TicksPerDay;
+ Calendar.CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (new DateTime(ticks));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+
+ public override DateTime AddYears(DateTime time, int years) {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+
+ public override int GetDayOfMonth(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 366.
+ //
+
+
+ public override int GetDayOfYear(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartDayOfYear));
+ }
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ CheckYearMonthRange(year, month, era);
+
+ if ((month==MaxCalendarMonth) && (year==MaxCalendarYear)) {
+ return MaxCalendarDay;
+ }
+
+ int daysInMonth = DaysToMonth[month] - DaysToMonth[month - 1];
+ if ((month == MonthsPerYear) && !IsLeapYear(year))
+ {
+ Contract.Assert(daysInMonth == 30);
+ --daysInMonth;
+ }
+ return daysInMonth;
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+ public override int GetDaysInYear(int year, int era) {
+ CheckYearRange(year, era);
+ if (year==MaxCalendarYear) {
+ return DaysToMonth[MaxCalendarMonth-1] + MaxCalendarDay;
+ }
+ // Common years have 365 days. Leap years have 366 days.
+ return (IsLeapYear(year, CurrentEra) ? 366: 365);
+ }
+
+ // Returns the era for the specified DateTime value.
+
+
+ public override int GetEra(DateTime time) {
+ CheckTicksRange(time.Ticks);
+ return (PersianEra);
+ }
+
+
+
+ public override int[] Eras {
+ get {
+ return (new int[] {PersianEra});
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+
+ public override int GetMonth(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+
+ public override int GetMonthsInYear(int year, int era) {
+ CheckYearRange(year, era);
+ if (year==MaxCalendarYear) {
+ return MaxCalendarMonth;
+ }
+ return (12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between 1 and MaxCalendarYear.
+ //
+
+
+ public override int GetYear(DateTime time) {
+ return (GetDatePart(time.Ticks, DatePartYear));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era) {
+ // The year/month/era value checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth) {
+ throw new ArgumentOutOfRangeException(
+ "day",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Day"),
+ daysInMonth,
+ month));
+ }
+ return (IsLeapYear(year, era) && month == 12 && day == 30);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+
+ public override bool IsLeapMonth(int year, int month, int era) {
+ CheckYearMonthRange(year, month, era);
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+ public override bool IsLeapYear(int year, int era) {
+ CheckYearRange(year, era);
+
+ if (year == MaxCalendarYear)
+ {
+ return false;
+ }
+
+ return (GetAbsoluteDatePersian(year + 1, 1, 1) - GetAbsoluteDatePersian(year, 1, 1)) == 366;
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ // The year/month/era checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth) {
+ BCLDebug.Log("year = " + year + ", month = " + month + ", day = " + day);
+ throw new ArgumentOutOfRangeException(
+ "day",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Day"),
+ daysInMonth,
+ month));
+ }
+
+ long lDate = GetAbsoluteDatePersian(year, month, day);
+
+ if (lDate >= 0) {
+ return (new DateTime(lDate * GregorianCalendar.TicksPerDay + TimeToTicks(hour, minute, second, millisecond)));
+ } else {
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 1410;
+
+ public override int TwoDigitYearMax {
+ get {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value < 99 || value > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "value",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 99,
+ MaxCalendarYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+
+ public override int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (year < 100) {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if (year > MaxCalendarYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ MaxCalendarYear));
+ }
+ return (year);
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/RegionInfo.cs b/src/mscorlib/src/System/Globalization/RegionInfo.cs
new file mode 100644
index 0000000000..f06d63f1d2
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/RegionInfo.cs
@@ -0,0 +1,629 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class represents settings specified by de jure or
+// de facto standards for a particular country/region. In
+// contrast to CultureInfo, the RegionInfo does not represent
+// preferences of the user and does not depend on the user's
+// language or culture.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+namespace System.Globalization {
+
+ using System;
+ using System.Runtime.Serialization;
+ using System.Diagnostics.Contracts;
+
+ [Serializable]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public partial class RegionInfo
+ {
+ //--------------------------------------------------------------------//
+ // Internal Information //
+ //--------------------------------------------------------------------//
+
+ //
+ // Variables.
+ //
+
+ //
+ // Name of this region (ie: es-US): serialized, the field used for deserialization
+ //
+ internal String m_name;
+
+ //
+ // The CultureData instance that we are going to read data from.
+ //
+ [NonSerialized]internal CultureData m_cultureData;
+
+ //
+ // The RegionInfo for our current region
+ //
+ internal static volatile RegionInfo s_currentRegionInfo;
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // RegionInfo Constructors
+ //
+ // Note: We prefer that a region be created with a full culture name (ie: en-US)
+ // because otherwise the native strings won't be right.
+ //
+ // In Silverlight we enforce that RegionInfos must be created with a full culture name
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public RegionInfo(String name) {
+ if (name==null)
+ throw new ArgumentNullException("name");
+
+ if (name.Length == 0) //The InvariantCulture has no matching region
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_NoRegionInvariantCulture"), "name");
+ }
+
+ Contract.EndContractBlock();
+
+ //
+ // First try it as an entire culture. We must have user override as true here so
+ // that we can pick up custom cultures *before* built-in ones (if they want to
+ // prefer built-in cultures they will pass "us" instead of "en-US").
+ //
+ this.m_cultureData = CultureData.GetCultureDataForRegion(name,true);
+ // this.m_name = name.ToUpper(CultureInfo.InvariantCulture);
+
+ if (this.m_cultureData == null)
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("Argument_InvalidCultureName"), name), "name");
+
+
+ // Not supposed to be neutral
+ if (this.m_cultureData.IsNeutralCulture)
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidNeutralRegionName", name), "name");
+
+ SetName(name);
+ }
+
+#if FEATURE_USE_LCID
+ // We'd rather people use the named version since this doesn't allow custom locales
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public RegionInfo(int culture)
+ {
+ if (culture == CultureInfo.LOCALE_INVARIANT) //The InvariantCulture has no matching region
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_NoRegionInvariantCulture"));
+ }
+
+ if (culture == CultureInfo.LOCALE_NEUTRAL)
+ {
+ // Not supposed to be neutral
+ throw new ArgumentException(Environment.GetResourceString("Argument_CultureIsNeutral", culture), "culture");
+ }
+
+ if (culture == CultureInfo.LOCALE_CUSTOM_DEFAULT)
+ {
+ // Not supposed to be neutral
+ throw new ArgumentException(Environment.GetResourceString("Argument_CustomCultureCannotBePassedByNumber", culture), "culture");
+ }
+
+ this.m_cultureData = CultureData.GetCultureData(culture,true);
+ this.m_name = this.m_cultureData.SREGIONNAME;
+
+ if (this.m_cultureData.IsNeutralCulture)
+ {
+ // Not supposed to be neutral
+ throw new ArgumentException(Environment.GetResourceString("Argument_CultureIsNeutral", culture), "culture");
+ }
+ m_cultureId = culture;
+ }
+#endif
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal RegionInfo(CultureData cultureData)
+ {
+ this.m_cultureData = cultureData;
+ this.m_name = this.m_cultureData.SREGIONNAME;
+ }
+
+ [System.Security.SecurityCritical] // auto-generated
+ private void SetName(string name)
+ {
+#if FEATURE_CORECLR
+ // Use the name of the region we found
+ this.m_name = this.m_cultureData.SREGIONNAME;
+#else
+ // when creating region by culture name, we keep the region name as the culture name so regions
+ // created by custom culture names can be differentiated from built in regions.
+ this.m_name = name.Equals(this.m_cultureData.SREGIONNAME, StringComparison.OrdinalIgnoreCase) ?
+ this.m_cultureData.SREGIONNAME :
+ this.m_cultureData.CultureName;
+#endif // FEATURE_CORECLR
+ }
+
+
+#region Serialization
+ //
+ // m_cultureId is needed for serialization only to detect the case if the region info is created using the name or using the LCID.
+ // in case m_cultureId is zero means that the RigionInfo is created using name. otherwise it is created using LCID.
+ //
+
+ [OptionalField(VersionAdded = 2)]
+ int m_cultureId;
+ // the following field is defined to keep the compatibility with Everett.
+ // don't change/remove the names/types of these field.
+ [OptionalField(VersionAdded = 2)]
+ internal int m_dataItem = 0;
+
+#if !FEATURE_CORECLR
+ static private readonly int[] IdFromEverettRegionInfoDataItem =
+ {
+ 0x3801, /* 0 */ // AE ar-AE Arabic (U.A.E.)
+ 0x041C, /* 1 */ // AL sq-AL Albanian (Albania)
+ 0x042B, /* 2 */ // AM hy-AM Armenian (Armenia)
+ 0x2C0A, /* 3 */ // AR es-AR Spanish (Argentina)
+ 0x0C07, /* 4 */ // AT de-AT German (Austria)
+ 0x0C09, /* 5 */ // AU en-AU English (Australia)
+ 0x042C, /* 6 */ // AZ az-AZ-Latn Azeri (Latin) (Azerbaijan)
+ // 0x082C, 6, // AZ az-AZ-Cyrl Azeri (Cyrillic) (Azerbaijan)
+ 0x080C, /* 7 */ // BE fr-BE French (Belgium)
+ // 0x0813, 7, // BE nl-BE Dutch (Belgium)
+ 0x0402, /* 8 */ // BG bg-BG Bulgarian (Bulgaria)
+ 0x3C01, /* 9 */ // BH ar-BH Arabic (Bahrain)
+ 0x083E, /* 10 */ // BN ms-BN Malay (Brunei Darussalam)
+ 0x400A, /* 11 */ // BO es-BO Spanish (Bolivia)
+ 0x0416, /* 12 */ // BR pt-BR Portuguese (Brazil)
+ 0x0423, /* 13 */ // BY be-BY Belarusian (Belarus)
+ 0x2809, /* 14 */ // BZ en-BZ English (Belize)
+ 0x0C0C, /* 15 */ // CA fr-CA French (Canada)
+ // 0x1009, 15, // CA en-CA English (Canada)
+ 0x2409, /* 16 */ // CB en-CB English (Caribbean)
+ 0x0807, /* 17 */ // CH de-CH German (Switzerland)
+ // 0x0810, 17, // CH it-CH Italian (Switzerland)
+ // 0x100C, 17, // CH fr-CH French (Switzerland)
+ 0x340A, /* 18 */ // CL es-CL Spanish (Chile)
+ 0x0804, /* 19 */ // CN zh-CN Chinese (People's Republic of China)
+ 0x240A, /* 20 */ // CO es-CO Spanish (Colombia)
+ 0x140A, /* 21 */ // CR es-CR Spanish (Costa Rica)
+ 0x0405, /* 22 */ // CZ cs-CZ Czech (Czech Republic)
+ 0x0407, /* 23 */ // DE de-DE German (Germany)
+ 0x0406, /* 24 */ // DK da-DK Danish (Denmark)
+ 0x1C0A, /* 25 */ // DO es-DO Spanish (Dominican Republic)
+ 0x1401, /* 26 */ // DZ ar-DZ Arabic (Algeria)
+ 0x300A, /* 27 */ // EC es-EC Spanish (Ecuador)
+ 0x0425, /* 28 */ // EE et-EE Estonian (Estonia)
+ 0x0C01, /* 29 */ // EG ar-EG Arabic (Egypt)
+ 0x0403, /* 30 */ // ES ca-ES Catalan (Catalan)
+ // 0x042D, 30, // ES eu-ES Basque (Basque)
+ // 0x0456, 30, // ES gl-ES Galician (Galician)
+ // 0x0C0A, 30, // ES es-ES Spanish (Spain)
+ 0x040B, /* 31 */ // FI fi-FI Finnish (Finland)
+ // 0x081D, 31, // FI sv-FI Swedish (Finland)
+ 0x0438, /* 32 */ // FO fo-FO Faroese (Faroe Islands)
+ 0x040C, /* 33 */ // FR fr-FR French (France)
+ 0x0809, /* 34 */ // GB en-GB English (United Kingdom)
+ 0x0437, /* 35 */ // GE ka-GE Georgian (Georgia)
+ 0x0408, /* 36 */ // GR el-GR Greek (Greece)
+ 0x100A, /* 37 */ // GT es-GT Spanish (Guatemala)
+ 0x0C04, /* 38 */ // HK zh-HK Chinese (Hong Kong S.A.R.)
+ 0x480A, /* 39 */ // HN es-HN Spanish (Honduras)
+ 0x041A, /* 40 */ // HR hr-HR Croatian (Croatia)
+ 0x040E, /* 41 */ // HU hu-HU Hungarian (Hungary)
+ 0x0421, /* 42 */ // ID id-ID Indonesian (Indonesia)
+ 0x1809, /* 43 */ // IE en-IE English (Ireland)
+ 0x040D, /* 44 */ // IL he-IL Hebrew (Israel)
+ 0x0439, /* 45 */ // IN hi-IN Hindi (India)
+ // 0x0446, 45, // IN pa-IN Punjabi (India)
+ // 0x0447, 45, // IN gu-IN Gujarati (India)
+ // 0x0449, 45, // IN ta-IN Tamil (India)
+ // 0x044A, 45, // IN te-IN Telugu (India)
+ // 0x044B, 45, // IN kn-IN Kannada (India)
+ // 0x044E, 45, // IN mr-IN Marathi (India)
+ // 0x044F, 45, // IN sa-IN Sanskrit (India)
+ // 0x0457, 45, // IN kok-IN Konkani (India)
+ 0x0801, /* 46 */ // IQ ar-IQ Arabic (Iraq)
+ 0x0429, /* 47 */ // IR fa-IR (Iran)
+ 0x040F, /* 48 */ // IS is-IS Icelandic (Iceland)
+ 0x0410, /* 49 */ // IT it-IT Italian (Italy)
+ 0x2009, /* 50 */ // JM en-JM English (Jamaica)
+ 0x2C01, /* 51 */ // JO ar-JO Arabic (Jordan)
+ 0x0411, /* 52 */ // JP ja-JP Japanese (Japan)
+ 0x0441, /* 53 */ // KE sw-KE Swahili (Kenya)
+ 0x0440, /* 54 */ // KG ky-KG Kyrgyz (Kyrgyzstan)
+ 0x0412, /* 55 */ // KR ko-KR Korean (Korea)
+ 0x3401, /* 56 */ // KW ar-KW Arabic (Kuwait)
+ 0x043F, /* 57 */ // KZ kk-KZ Kazakh (Kazakhstan)
+ 0x3001, /* 58 */ // LB ar-LB Arabic (Lebanon)
+ 0x1407, /* 59 */ // LI de-LI German (Liechtenstein)
+ 0x0427, /* 60 */ // LT lt-LT Lithuanian (Lithuania)
+ 0x1007, /* 61 */ // LU de-LU German (Luxembourg)
+ // 0x140C, 61, // LU fr-LU French (Luxembourg)
+ 0x0426, /* 62 */ // LV lv-LV Latvian (Latvia)
+ 0x1001, /* 63 */ // LY ar-LY Arabic (Libya)
+ 0x1801, /* 64 */ // MA ar-MA Arabic (Morocco)
+ 0x180C, /* 65 */ // MC fr-MC French (Principality of Monaco)
+ 0x042F, /* 66 */ // MK mk-MK Macedonian (Macedonia, FYRO)
+ 0x0450, /* 67 */ // MN mn-MN Mongolian (Mongolia)
+ 0x1404, /* 68 */ // MO zh-MO Chinese (Macau S.A.R.)
+ 0x0465, /* 69 */ // MV div-MV Divehi (Maldives)
+ 0x080A, /* 70 */ // MX es-MX Spanish (Mexico)
+ 0x043E, /* 71 */ // MY ms-MY Malay (Malaysia)
+ 0x4C0A, /* 72 */ // NI es-NI Spanish (Nicaragua)
+ 0x0413, /* 73 */ // NL nl-NL Dutch (Netherlands)
+ 0x0414, /* 74 */ // NO nb-NO Norwegian (Bokm?) (Norway)
+ // 0x0814, 74, // NO nn-NO Norwegian (Nynorsk) (Norway)
+ 0x1409, /* 75 */ // NZ en-NZ English (New Zealand)
+ 0x2001, /* 76 */ // OM ar-OM Arabic (Oman)
+ 0x180A, /* 77 */ // PA es-PA Spanish (Panama)
+ 0x280A, /* 78 */ // PE es-PE Spanish (Peru)
+ 0x3409, /* 79 */ // PH en-PH English (Republic of the Philippines)
+ 0x0420, /* 80 */ // PK ur-PK Urdu (Islamic Republic of Pakistan)
+ 0x0415, /* 81 */ // PL pl-PL Polish (Poland)
+ 0x500A, /* 82 */ // PR es-PR Spanish (Puerto Rico)
+ 0x0816, /* 83 */ // PT pt-PT Portuguese (Portugal)
+ 0x3C0A, /* 84 */ // PY es-PY Spanish (Paraguay)
+ 0x4001, /* 85 */ // QA ar-QA Arabic (Qatar)
+ 0x0418, /* 86 */ // RO ro-RO Romanian (Romania)
+ 0x0419, /* 87 */ // RU ru-RU Russian (Russia)
+ // 0x0444, 87, // RU tt-RU Tatar (Russia)
+ 0x0401, /* 88 */ // SA ar-SA Arabic (Saudi Arabia)
+ 0x041D, /* 89 */ // SE sv-SE Swedish (Sweden)
+ 0x1004, /* 90 */ // SG zh-SG Chinese (Singapore)
+ 0x0424, /* 91 */ // SI sl-SI Slovenian (Slovenia)
+ 0x041B, /* 92 */ // SK sk-SK Slovak (Slovakia)
+ 0x081A, /* 93 */ // SP sr-SP-Latn Serbian (Latin) (Serbia)
+ // 0x0C1A, 93, // SP sr-SP-Cyrl Serbian (Cyrillic) (Serbia)
+ 0x440A, /* 94 */ // SV es-SV Spanish (El Salvador)
+ 0x045A, /* 95 */ // SY syr-SY Syriac (Syria)
+ // 0x2801, 95, // SY ar-SY Arabic (Syria)
+ 0x041E, /* 96 */ // TH th-TH Thai (Thailand)
+ 0x1C01, /* 97 */ // TN ar-TN Arabic (Tunisia)
+ 0x041F, /* 98 */ // TR tr-TR Turkish (Turkey)
+ 0x2C09, /* 99 */ // TT en-TT English (Trinidad and Tobago)
+ 0x0404, /*100 */ // TW zh-TW Chinese (Taiwan)
+ 0x0422, /*101 */ // UA uk-UA Ukrainian (Ukraine)
+ 0x0409, /*102 */ // US en-US English (United States)
+ 0x380A, /*103 */ // UY es-UY Spanish (Uruguay)
+ 0x0443, /*104 */ // UZ uz-UZ-Latn Uzbek (Latin) (Uzbekistan)
+ // 0x0843, 104 // UZ uz-UZ-Cyrl Uzbek (Cyrillic) (Uzbekistan)
+ 0x200A, /*105*/ // VE es-VE Spanish (Venezuela)
+ 0x042A, /*106*/ // VN vi-VN Vietnamese (Viet Nam)
+ 0x2401, /*107*/ // YE ar-YE Arabic (Yemen)
+ 0x0436, /*108*/ // ZA af-ZA Afrikaans (South Africa)
+ // 0x1C09, 108, // ZA en-ZA English (South Africa)
+ 0x3009, /*109*/ // ZW en-ZW English (Zimbabwe)
+ };
+#endif
+ [System.Security.SecurityCritical] // auto-generated
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+#if FEATURE_CORECLR
+ // This won't happen anyway since CoreCLR doesn't support serialization
+ this.m_cultureData = CultureData.GetCultureData(m_name, true);
+#else
+ if (m_name == null)
+ {
+ Contract.Assert(m_dataItem >= 0, "[RegionInfo.OnDeserialized] null name and invalid dataItem");
+ m_cultureId = IdFromEverettRegionInfoDataItem[m_dataItem];
+ }
+
+ if (m_cultureId == 0)
+ {
+ this.m_cultureData = CultureData.GetCultureDataForRegion(this.m_name, true);
+ }
+ else
+ {
+ this.m_cultureData = CultureData.GetCultureData(m_cultureId, true);
+ }
+
+#endif
+ if (this.m_cultureData == null)
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("Argument_InvalidCultureName"), m_name), "m_name");
+
+ if (m_cultureId == 0)
+ {
+ SetName(this.m_name);
+ }
+ else
+ {
+ this.m_name = this.m_cultureData.SREGIONNAME;
+ }
+ }
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+ // Used to fill in everett data item, unnecessary now
+ }
+#endregion Serialization
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetCurrentRegion
+ //
+ // This instance provides methods based on the current user settings.
+ // These settings are volatile and may change over the lifetime of the
+ // thread.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public static RegionInfo CurrentRegion {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ RegionInfo temp = s_currentRegionInfo;
+ if (temp == null)
+ {
+ temp = new RegionInfo(CultureInfo.CurrentCulture.m_cultureData);
+
+ // Need full name for custom cultures
+ temp.m_name=temp.m_cultureData.SREGIONNAME;
+ s_currentRegionInfo = temp;
+ }
+ return temp;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetName
+ //
+ // Returns the name of the region (ie: en-US)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String Name {
+ get {
+ Contract.Assert(m_name != null, "Expected RegionInfo.m_name to be populated already");
+ return (m_name);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetEnglishName
+ //
+ // Returns the name of the region in English. (ie: United States)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String EnglishName
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return (this.m_cultureData.SENGCOUNTRY);
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetDisplayName
+ //
+ // Returns the display name (localized) of the region. (ie: United States
+ // if the current UI language is en-US)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String DisplayName
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return (this.m_cultureData.SLOCALIZEDCOUNTRY);
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetNativeName
+ //
+ // Returns the native name of the region. (ie: Deutschland)
+ // WARNING: You need a full locale name for this to make sense.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual String NativeName
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return (this.m_cultureData.SNATIVECOUNTRY);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // TwoLetterISORegionName
+ //
+ // Returns the two letter ISO region name (ie: US)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String TwoLetterISORegionName
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return (this.m_cultureData.SISO3166CTRYNAME);
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ThreeLetterISORegionName
+ //
+ // Returns the three letter ISO region name (ie: USA)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String ThreeLetterISORegionName
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return (this.m_cultureData.SISO3166CTRYNAME2);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ThreeLetterWindowsRegionName
+ //
+ // Returns the three letter windows region name (ie: USA)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String ThreeLetterWindowsRegionName
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return (this.m_cultureData.SABBREVCTRYNAME);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsMetric
+ //
+ // Returns true if this region uses the metric measurement system
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual bool IsMetric {
+ get {
+ int value = this.m_cultureData.IMEASURE;
+ return (value==0);
+ }
+ }
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual int GeoId
+ {
+ get
+ {
+ return (this.m_cultureData.IGEOID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CurrencyEnglishName
+ //
+ // English name for this region's currency, ie: Swiss Franc
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual String CurrencyEnglishName
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return (this.m_cultureData.SENGLISHCURRENCY);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CurrencyEnglishName
+ //
+ // English name for this region's currency, ie: Schweizer Franken
+ // WARNING: You need a full locale name for this to make sense.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual String CurrencyNativeName
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ return (this.m_cultureData.SNATIVECURRENCY);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CurrencySymbol
+ //
+ // Currency Symbol for this locale, ie: Fr. or $
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String CurrencySymbol {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ return (this.m_cultureData.SCURRENCY);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ISOCurrencySymbol
+ //
+ // ISO Currency Symbol for this locale, ie: CHF
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String ISOCurrencySymbol {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get {
+ return (this.m_cultureData.SINTLSYMBOL);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Equals
+ //
+ // Implements Object.Equals(). Returns a boolean indicating whether
+ // or not object refers to the same RegionInfo as the current instance.
+ //
+ // RegionInfos are considered equal if and only if they have the same name
+ // (ie: en-US)
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override bool Equals(Object value)
+ {
+ RegionInfo that = value as RegionInfo;
+ if (that != null)
+ {
+ return this.Name.Equals(that.Name);
+ }
+
+ return (false);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCode
+ //
+ // Implements Object.GetHashCode(). Returns the hash code for the
+ // CultureInfo. The hash code is guaranteed to be the same for RegionInfo
+ // A and B where A.Equals(B) is true.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override int GetHashCode()
+ {
+ return (this.Name.GetHashCode());
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Implements Object.ToString(). Returns the name of the Region, ie: es-US
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override String ToString()
+ {
+ return (Name);
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/SortKey.cs b/src/mscorlib/src/System/Globalization/SortKey.cs
new file mode 100644
index 0000000000..e3308dc4f8
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/SortKey.cs
@@ -0,0 +1,208 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class implements a set of methods for retrieving
+// sort key information.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+namespace System.Globalization {
+
+ using System;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.Serialization;
+ using System.Diagnostics.Contracts;
+
+ [System.Runtime.InteropServices.ComVisible(true)]
+ [Serializable]
+ public partial class SortKey
+ {
+ //--------------------------------------------------------------------//
+ // Internal Information //
+ //--------------------------------------------------------------------//
+
+ //
+ // Variables.
+ //
+
+ [OptionalField(VersionAdded = 3)]
+ internal String localeName; // locale identifier
+
+ [OptionalField(VersionAdded = 1)] // LCID field so serialization is Whidbey compatible though we don't officially support it
+ internal int win32LCID;
+ // Whidbey serialization
+
+ internal CompareOptions options; // options
+ internal String m_String; // original string
+ internal byte[] m_KeyData; // sortkey data
+
+ //
+ // The following constructor is designed to be called from CompareInfo to get the
+ // the sort key of specific string for synthetic culture
+ //
+ internal SortKey(String localeName, String str, CompareOptions options, byte[] keyData)
+ {
+ this.m_KeyData = keyData;
+ this.localeName = localeName;
+ this.options = options;
+ this.m_String = str;
+ }
+
+#if FEATURE_USE_LCID
+ [OnSerializing]
+ private void OnSerializing(StreamingContext context)
+ {
+ //set LCID to proper value for Whidbey serialization (no other use)
+ if (win32LCID == 0)
+ {
+ win32LCID = CultureInfo.GetCultureInfo(localeName).LCID;
+ }
+ }
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext context)
+ {
+ //set locale name to proper value after Whidbey deserialization
+ if (String.IsNullOrEmpty(localeName) && win32LCID != 0)
+ {
+ localeName = CultureInfo.GetCultureInfo(win32LCID).Name;
+ }
+ }
+#endif //FEATURE_USE_LCID
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetOriginalString
+ //
+ // Returns the original string used to create the current instance
+ // of SortKey.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual String OriginalString
+ {
+ get {
+ return (m_String);
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetKeyData
+ //
+ // Returns a byte array representing the current instance of the
+ // sort key.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public virtual byte[] KeyData
+ {
+ get {
+ return (byte[])(m_KeyData.Clone());
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Compare
+ //
+ // Compares the two sort keys. Returns 0 if the two sort keys are
+ // equal, a number less than 0 if sortkey1 is less than sortkey2,
+ // and a number greater than 0 if sortkey1 is greater than sortkey2.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public static int Compare(SortKey sortkey1, SortKey sortkey2) {
+
+ if (sortkey1==null || sortkey2==null) {
+ throw new ArgumentNullException((sortkey1==null ? "sortkey1": "sortkey2"));
+ }
+ Contract.EndContractBlock();
+
+ byte[] key1Data = sortkey1.m_KeyData;
+ byte[] key2Data = sortkey2.m_KeyData;
+
+ Contract.Assert(key1Data!=null, "key1Data!=null");
+ Contract.Assert(key2Data!=null, "key2Data!=null");
+
+ if (key1Data.Length == 0) {
+ if (key2Data.Length == 0) {
+ return (0);
+ }
+ return (-1);
+ }
+ if (key2Data.Length == 0) {
+ return (1);
+ }
+
+ int compLen = (key1Data.Length<key2Data.Length)?key1Data.Length:key2Data.Length;
+
+ for (int i=0; i<compLen; i++) {
+ if (key1Data[i]>key2Data[i]) {
+ return (1);
+ }
+ if (key1Data[i]<key2Data[i]) {
+ return (-1);
+ }
+ }
+
+ return 0;
+
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Equals
+ //
+ // Implements Object.Equals(). Returns a boolean indicating whether
+ // or not object refers to the same SortKey as the current instance.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override bool Equals(Object value)
+ {
+ SortKey that = value as SortKey;
+
+ if (that != null)
+ {
+ return Compare(this, that) == 0;
+ }
+
+ return (false);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCode
+ //
+ // Implements Object.GetHashCode(). Returns the hash code for the
+ // SortKey. The hash code is guaranteed to be the same for
+ // SortKey A and B where A.Equals(B) is true.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override int GetHashCode()
+ {
+ return (CompareInfo.GetCompareInfo(
+ this.localeName).GetHashCodeOfString(this.m_String, this.options));
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Implements Object.ToString(). Returns a string describing the
+ // SortKey.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ public override String ToString()
+ {
+ return ("SortKey - " + localeName + ", " + options + ", " + m_String);
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/SortVersion.cs b/src/mscorlib/src/System/Globalization/SortVersion.cs
new file mode 100644
index 0000000000..24fe8546fc
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/SortVersion.cs
@@ -0,0 +1,102 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization
+{
+ using System;
+ using System.Diagnostics.Contracts;
+
+ [Serializable]
+ public sealed class SortVersion : IEquatable<SortVersion>
+ {
+ private int m_NlsVersion;
+ private Guid m_SortId;
+
+ public int FullVersion
+ {
+ get
+ {
+ return m_NlsVersion;
+ }
+ }
+
+ public Guid SortId
+ {
+ get
+ {
+ return m_SortId;
+ }
+ }
+
+ public SortVersion(int fullVersion, Guid sortId)
+ {
+ m_SortId = sortId;
+ m_NlsVersion = fullVersion;
+ }
+
+ internal SortVersion(int nlsVersion, int effectiveId, Guid customVersion)
+ {
+ m_NlsVersion = nlsVersion;
+
+ if (customVersion == Guid.Empty)
+ {
+ byte[] b = BitConverter.GetBytes(effectiveId);
+ byte b1 = (byte) ((uint) effectiveId >> 24);
+ byte b2 = (byte) ((effectiveId & 0x00FF0000) >> 16);
+ byte b3 = (byte) ((effectiveId & 0x0000FF00) >> 8);
+ byte b4 = (byte) (effectiveId & 0xFF);
+ customVersion = new Guid(0,0,0,0,0,0,0,b1,b2,b3,b4);
+ }
+
+ m_SortId = customVersion;
+ }
+
+ public override bool Equals(object obj)
+ {
+ SortVersion n = obj as SortVersion;
+ if(n != null)
+ {
+ return this.Equals(n);
+ }
+
+ return false;
+ }
+
+ public bool Equals(SortVersion other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ return m_NlsVersion == other.m_NlsVersion && m_SortId == other.m_SortId;
+ }
+
+ public override int GetHashCode()
+ {
+ return m_NlsVersion * 7 | m_SortId.GetHashCode();
+ }
+
+ public static bool operator ==(SortVersion left, SortVersion right)
+ {
+ if (((object) left) != null)
+ {
+ return left.Equals(right);
+ }
+
+ if (((object) right) != null)
+ {
+ return right.Equals(left);
+ }
+
+ // Both null.
+ return true;
+ }
+
+ public static bool operator !=(SortVersion left, SortVersion right)
+ {
+ return !(left == right);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/mscorlib/src/System/Globalization/StringInfo.cs b/src/mscorlib/src/System/Globalization/StringInfo.cs
new file mode 100644
index 0000000000..b1151bde4f
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/StringInfo.cs
@@ -0,0 +1,361 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class defines behaviors specific to a writing system.
+// A writing system is the collection of scripts and
+// orthographic rules required to represent a language as text.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+namespace System.Globalization {
+
+ using System;
+ using System.Runtime.Serialization;
+ using System.Security.Permissions;
+ using System.Diagnostics.Contracts;
+
+ [Serializable]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public class StringInfo
+ {
+
+ [OptionalField(VersionAdded = 2)]
+ private String m_str;
+
+ // We allow this class to be serialized but there is no conceivable reason
+ // for them to do so. Thus, we do not serialize the instance variables.
+ [NonSerialized] private int[] m_indexes;
+
+ // Legacy constructor
+ public StringInfo() : this(""){}
+
+ // Primary, useful constructor
+ public StringInfo(String value) {
+ this.String = value;
+ }
+
+#region Serialization
+ [OnDeserializing]
+ private void OnDeserializing(StreamingContext ctx)
+ {
+ m_str = String.Empty;
+ }
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ if (m_str.Length == 0)
+ {
+ m_indexes = null;
+ }
+ }
+#endregion Serialization
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override bool Equals(Object value)
+ {
+ StringInfo that = value as StringInfo;
+ if (that != null)
+ {
+ return (this.m_str.Equals(that.m_str));
+ }
+ return (false);
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetHashCode()
+ {
+ return this.m_str.GetHashCode();
+ }
+
+
+ // Our zero-based array of index values into the string. Initialize if
+ // our private array is not yet, in fact, initialized.
+ private int[] Indexes {
+ get {
+ if((null == this.m_indexes) && (0 < this.String.Length)) {
+ this.m_indexes = StringInfo.ParseCombiningCharacters(this.String);
+ }
+
+ return(this.m_indexes);
+ }
+ }
+
+ public String String {
+ get {
+ return(this.m_str);
+ }
+ set {
+ if (null == value) {
+ throw new ArgumentNullException("String",
+ Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+
+ this.m_str = value;
+ this.m_indexes = null;
+ }
+ }
+
+ public int LengthInTextElements {
+ get {
+ if(null == this.Indexes) {
+ // Indexes not initialized, so assume length zero
+ return(0);
+ }
+
+ return(this.Indexes.Length);
+ }
+ }
+
+ public String SubstringByTextElements(int startingTextElement) {
+ // If the string is empty, no sense going further.
+ if(null == this.Indexes) {
+ // Just decide which error to give depending on the param they gave us....
+ if(startingTextElement < 0) {
+ throw new ArgumentOutOfRangeException("startingTextElement",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+ else {
+ throw new ArgumentOutOfRangeException("startingTextElement",
+ Environment.GetResourceString("Arg_ArgumentOutOfRangeException"));
+ }
+ }
+ return (this.SubstringByTextElements(startingTextElement, this.Indexes.Length - startingTextElement));
+ }
+
+ public String SubstringByTextElements(int startingTextElement, int lengthInTextElements) {
+
+ //
+ // Parameter checking
+ //
+ if(startingTextElement < 0) {
+ throw new ArgumentOutOfRangeException("startingTextElement",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+
+ if(this.String.Length == 0 || startingTextElement >= this.Indexes.Length) {
+ throw new ArgumentOutOfRangeException("startingTextElement",
+ Environment.GetResourceString("Arg_ArgumentOutOfRangeException"));
+ }
+
+ if(lengthInTextElements < 0) {
+ throw new ArgumentOutOfRangeException("lengthInTextElements",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+
+ if(startingTextElement > this.Indexes.Length - lengthInTextElements) {
+ throw new ArgumentOutOfRangeException("lengthInTextElements",
+ Environment.GetResourceString("Arg_ArgumentOutOfRangeException"));
+ }
+
+ int start = this.Indexes[startingTextElement];
+
+ if(startingTextElement + lengthInTextElements == this.Indexes.Length) {
+ // We are at the last text element in the string and because of that
+ // must handle the call differently.
+ return(this.String.Substring(start));
+ }
+ else {
+ return(this.String.Substring(start, (this.Indexes[lengthInTextElements + startingTextElement] - start)));
+ }
+ }
+
+ public static String GetNextTextElement(String str)
+ {
+ return (GetNextTextElement(str, 0));
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Get the code point count of the current text element.
+ //
+ // A combining class is defined as:
+ // A character/surrogate that has the following Unicode category:
+ // * NonSpacingMark (e.g. U+0300 COMBINING GRAVE ACCENT)
+ // * SpacingCombiningMark (e.g. U+ 0903 DEVANGARI SIGN VISARGA)
+ // * EnclosingMark (e.g. U+20DD COMBINING ENCLOSING CIRCLE)
+ //
+ // In the context of GetNextTextElement() and ParseCombiningCharacters(), a text element is defined as:
+ //
+ // 1. If a character/surrogate is in the following category, it is a text element.
+ // It can NOT further combine with characters in the combinging class to form a text element.
+ // * one of the Unicode category in the combinging class
+ // * UnicodeCategory.Format
+ // * UnicodeCateogry.Control
+ // * UnicodeCategory.OtherNotAssigned
+ // 2. Otherwise, the character/surrogate can be combined with characters in the combinging class to form a text element.
+ //
+ // Return:
+ // The length of the current text element
+ //
+ // Parameters:
+ // String str
+ // index The starting index
+ // len The total length of str (to define the upper boundary)
+ // ucCurrent The Unicode category pointed by Index. It will be updated to the uc of next character if this is not the last text element.
+ // currentCharCount The char count of an abstract char pointed by Index. It will be updated to the char count of next abstract character if this is not the last text element.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ internal static int GetCurrentTextElementLen(String str, int index, int len, ref UnicodeCategory ucCurrent, ref int currentCharCount)
+ {
+ Contract.Assert(index >= 0 && len >= 0, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len);
+ Contract.Assert(index < len, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len);
+ if (index + currentCharCount == len)
+ {
+ // This is the last character/surrogate in the string.
+ return (currentCharCount);
+ }
+
+ // Call an internal GetUnicodeCategory, which will tell us both the unicode category, and also tell us if it is a surrogate pair or not.
+ int nextCharCount;
+ UnicodeCategory ucNext = CharUnicodeInfo.InternalGetUnicodeCategory(str, index + currentCharCount, out nextCharCount);
+ if (CharUnicodeInfo.IsCombiningCategory(ucNext)) {
+ // The next element is a combining class.
+ // Check if the current text element to see if it is a valid base category (i.e. it should not be a combining category,
+ // not a format character, and not a control character).
+
+ if (CharUnicodeInfo.IsCombiningCategory(ucCurrent)
+ || (ucCurrent == UnicodeCategory.Format)
+ || (ucCurrent == UnicodeCategory.Control)
+ || (ucCurrent == UnicodeCategory.OtherNotAssigned)
+ || (ucCurrent == UnicodeCategory.Surrogate)) // An unpair high surrogate or low surrogate
+ {
+ // Will fall thru and return the currentCharCount
+ } else {
+ int startIndex = index; // Remember the current index.
+
+ // We have a valid base characters, and we have a character (or surrogate) that is combining.
+ // Check if there are more combining characters to follow.
+ // Check if the next character is a nonspacing character.
+ index += currentCharCount + nextCharCount;
+
+ while (index < len)
+ {
+ ucNext = CharUnicodeInfo.InternalGetUnicodeCategory(str, index, out nextCharCount);
+ if (!CharUnicodeInfo.IsCombiningCategory(ucNext)) {
+ ucCurrent = ucNext;
+ currentCharCount = nextCharCount;
+ break;
+ }
+ index += nextCharCount;
+ }
+ return (index - startIndex);
+ }
+ }
+ // The return value will be the currentCharCount.
+ int ret = currentCharCount;
+ ucCurrent = ucNext;
+ // Update currentCharCount.
+ currentCharCount = nextCharCount;
+ return (ret);
+ }
+
+ // Returns the str containing the next text element in str starting at
+ // index index. If index is not supplied, then it will start at the beginning
+ // of str. It recognizes a base character plus one or more combining
+ // characters or a properly formed surrogate pair as a text element. See also
+ // the ParseCombiningCharacters() and the ParseSurrogates() methods.
+ public static String GetNextTextElement(String str, int index) {
+ //
+ // Validate parameters.
+ //
+ if (str==null) {
+ throw new ArgumentNullException("str");
+ }
+ Contract.EndContractBlock();
+
+ int len = str.Length;
+ if (index < 0 || index >= len) {
+ if (index == len) {
+ return (String.Empty);
+ }
+ throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+
+ int charLen;
+ UnicodeCategory uc = CharUnicodeInfo.InternalGetUnicodeCategory(str, index, out charLen);
+ return (str.Substring(index, GetCurrentTextElementLen(str, index, len, ref uc, ref charLen)));
+ }
+
+ public static TextElementEnumerator GetTextElementEnumerator(String str)
+ {
+ return (GetTextElementEnumerator(str, 0));
+ }
+
+ public static TextElementEnumerator GetTextElementEnumerator(String str, int index)
+ {
+ //
+ // Validate parameters.
+ //
+ if (str==null)
+ {
+ throw new ArgumentNullException("str");
+ }
+ Contract.EndContractBlock();
+
+ int len = str.Length;
+ if (index < 0 || (index > len))
+ {
+ throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+
+ return (new TextElementEnumerator(str, index, len));
+ }
+
+ /*
+ * Returns the indices of each base character or properly formed surrogate pair
+ * within the str. It recognizes a base character plus one or more combining
+ * characters or a properly formed surrogate pair as a text element and returns
+ * the index of the base character or high surrogate. Each index is the
+ * beginning of a text element within a str. The length of each element is
+ * easily computed as the difference between successive indices. The length of
+ * the array will always be less than or equal to the length of the str. For
+ * example, given the str \u4f00\u302a\ud800\udc00\u4f01, this method would
+ * return the indices: 0, 2, 4.
+ */
+
+ public static int[] ParseCombiningCharacters(String str)
+ {
+ if (str == null)
+ {
+ throw new ArgumentNullException("str");
+ }
+ Contract.EndContractBlock();
+
+ int len = str.Length;
+ int[] result = new int[len];
+ if (len == 0)
+ {
+ return (result);
+ }
+
+ int resultCount = 0;
+
+ int i = 0;
+ int currentCharLen;
+ UnicodeCategory currentCategory = CharUnicodeInfo.InternalGetUnicodeCategory(str, 0, out currentCharLen);
+
+ while (i < len) {
+ result[resultCount++] = i;
+ i += GetCurrentTextElementLen(str, i, len, ref currentCategory, ref currentCharLen);
+ }
+
+ if (resultCount < len)
+ {
+ int[] returnArray = new int[resultCount];
+ Array.Copy(result, returnArray, resultCount);
+ return (returnArray);
+ }
+ return (result);
+
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/Tables/charinfo.nlp b/src/mscorlib/src/System/Globalization/Tables/charinfo.nlp
new file mode 100644
index 0000000000..2d5444f524
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/Tables/charinfo.nlp
Binary files differ
diff --git a/src/mscorlib/src/System/Globalization/TaiwanCalendar.cs b/src/mscorlib/src/System/Globalization/TaiwanCalendar.cs
new file mode 100644
index 0000000000..013e2cd50e
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/TaiwanCalendar.cs
@@ -0,0 +1,262 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ /* SSS_DROP_BEGIN */ /* SSS_WARNINGS_OFF */
+ /*=================================TaiwanCalendar==========================
+ **
+ ** Taiwan calendar is based on the Gregorian calendar. And the year is an offset to Gregorian calendar.
+ ** That is,
+ ** Taiwan year = Gregorian year - 1911. So 1912/01/01 A.D. is Taiwan 1/01/01
+ **
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1912/01/01 9999/12/31
+ ** Taiwan 01/01/01 8088/12/31
+ ============================================================================*/
+ /* SSS_WARNINGS_ON */ /* SSS_DROP_END */
+
+ [System.Runtime.InteropServices.ComVisible(true)]
+ [Serializable] public class TaiwanCalendar: Calendar {
+ //
+ // The era value for the current era.
+ //
+
+ // Since
+ // Gregorian Year = Era Year + yearOffset
+ // When Gregorian Year 1912 is year 1, so that
+ // 1912 = 1 + yearOffset
+ // So yearOffset = 1911
+ //m_EraInfo[0] = new EraInfo(1, new DateTime(1912, 1, 1).Ticks, 1911, 1, GregorianCalendar.MaxYear - 1911);
+
+ // Initialize our era info.
+ static internal EraInfo[] taiwanEraInfo = new EraInfo[] {
+ new EraInfo( 1, 1912, 1, 1, 1911, 1, GregorianCalendar.MaxYear - 1911) // era #, start year/month/day, yearOffset, minEraYear
+ };
+
+ internal static volatile Calendar s_defaultInstance;
+
+ internal GregorianCalendarHelper helper;
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of TaiwanCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+
+ internal static Calendar GetDefaultInstance() {
+ if (s_defaultInstance == null) {
+ s_defaultInstance = new TaiwanCalendar();
+ }
+ return (s_defaultInstance);
+ }
+
+ internal static readonly DateTime calendarMinValue = new DateTime(1912, 1, 1);
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ // Return the type of the Taiwan calendar.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+
+ public TaiwanCalendar() {
+ try {
+ new CultureInfo("zh-TW");
+ } catch (ArgumentException e) {
+ throw new TypeInitializationException(this.GetType().FullName, e);
+ }
+ helper = new GregorianCalendarHelper(this, taiwanEraInfo);
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_TAIWAN);
+ }
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months) {
+ return (helper.AddMonths(time, months));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years) {
+ return (helper.AddYears(time, years));
+ }
+
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ return (helper.GetDaysInMonth(year, month, era));
+ }
+
+
+ public override int GetDaysInYear(int year, int era) {
+ return (helper.GetDaysInYear(year, era));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time) {
+ return (helper.GetDayOfMonth(time));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ return (helper.GetDayOfWeek(time));
+ }
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (helper.GetDayOfYear(time));
+ }
+
+
+ public override int GetMonthsInYear(int year, int era) {
+ return (helper.GetMonthsInYear(year, era));
+ }
+
+
+ [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ return (helper.GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+
+ public override int GetEra(DateTime time) {
+ return (helper.GetEra(time));
+ }
+
+ public override int GetMonth(DateTime time) {
+ return (helper.GetMonth(time));
+ }
+
+
+ public override int GetYear(DateTime time) {
+ return (helper.GetYear(time));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ return (helper.IsLeapDay(year, month, day, era));
+ }
+
+
+ public override bool IsLeapYear(int year, int era) {
+ return (helper.IsLeapYear(year, era));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetLeapMonth(int year, int era)
+ {
+ return (helper.GetLeapMonth(year, era));
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era) {
+ return (helper.IsLeapMonth(year, month, era));
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ return (helper.ToDateTime(year, month, day, hour, minute, second, millisecond, era));
+ }
+
+
+ public override int[] Eras {
+ get {
+ return (helper.Eras);
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 99;
+
+ public override int TwoDigitYearMax {
+ get {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value < 99 || value > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 99,
+ helper.MaxYear));
+
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+ // For Taiwan calendar, four digit year is not used.
+ // Therefore, for any two digit number, we just return the original number.
+
+ public override int ToFourDigitYear(int year) {
+ if (year <= 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (year > helper.MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 1,
+ helper.MaxYear));
+ }
+ return (year);
+ }
+ }
+}
+
diff --git a/src/mscorlib/src/System/Globalization/TaiwanLunisolarCalendar.cs b/src/mscorlib/src/System/Globalization/TaiwanLunisolarCalendar.cs
new file mode 100644
index 0000000000..d96c450f49
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/TaiwanLunisolarCalendar.cs
@@ -0,0 +1,330 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+ using System.Diagnostics.Contracts;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Notes about TaiwanLunisolarCalendar
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1912/02/18 2051/02/10
+ ** TaiwanLunisolar 1912/01/01 2050/13/29
+ */
+
+ [Serializable]
+ public class TaiwanLunisolarCalendar : EastAsianLunisolarCalendar {
+
+ // Since
+ // Gregorian Year = Era Year + yearOffset
+ // When Gregorian Year 1912 is year 1, so that
+ // 1912 = 1 + yearOffset
+ // So yearOffset = 1911
+ //m_EraInfo[0] = new EraInfo(1, new DateTime(1912, 1, 1).Ticks, 1911, 1, GregorianCalendar.MaxYear - 1911);
+
+ // Initialize our era info.
+ static internal EraInfo[] taiwanLunisolarEraInfo = new EraInfo[] {
+ new EraInfo( 1, 1912, 1, 1, 1911, 1, GregorianCalendar.MaxYear - 1911) // era #, start year/month/day, yearOffset, minEraYear
+ };
+
+ internal GregorianCalendarHelper helper;
+
+ internal const int MIN_LUNISOLAR_YEAR = 1912;
+ internal const int MAX_LUNISOLAR_YEAR = 2050;
+
+ internal const int MIN_GREGORIAN_YEAR = 1912;
+ internal const int MIN_GREGORIAN_MONTH = 2;
+ internal const int MIN_GREGORIAN_DAY = 18;
+
+ internal const int MAX_GREGORIAN_YEAR = 2051;
+ internal const int MAX_GREGORIAN_MONTH = 2;
+ internal const int MAX_GREGORIAN_DAY = 10;
+
+ internal static DateTime minDate = new DateTime(MIN_GREGORIAN_YEAR, MIN_GREGORIAN_MONTH, MIN_GREGORIAN_DAY);
+ internal static DateTime maxDate = new DateTime((new DateTime(MAX_GREGORIAN_YEAR, MAX_GREGORIAN_MONTH, MAX_GREGORIAN_DAY, 23, 59, 59, 999)).Ticks + 9999);
+
+ public override DateTime MinSupportedDateTime {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+
+ public override DateTime MaxSupportedDateTime {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // 1911 from ChineseLunisolarCalendar
+ return 384;
+ }
+ }
+
+ static readonly int [,] yinfo =
+ {
+/*Y LM Lmon Lday DaysPerMonth D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 #Days
+1912 */{ 0 , 2 , 18 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1913 */{ 0 , 2 , 6 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1914 */{ 5 , 1 , 26 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1915 */{ 0 , 2 , 14 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1916 */{ 0 , 2 , 3 , 54944 },/* 30 30 29 30 29 30 30 29 30 29 30 29 0 355
+1917 */{ 2 , 1 , 23 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1918 */{ 0 , 2 , 11 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1919 */{ 7 , 2 , 1 , 18872 },/* 29 30 29 29 30 29 29 30 30 29 30 30 30 384
+1920 */{ 0 , 2 , 20 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1921 */{ 0 , 2 , 8 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1922 */{ 5 , 1 , 28 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1923 */{ 0 , 2 , 16 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1924 */{ 0 , 2 , 5 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1925 */{ 4 , 1 , 24 , 44456 },/* 30 29 30 29 30 30 29 30 30 29 30 29 30 385
+1926 */{ 0 , 2 , 13 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1927 */{ 0 , 2 , 2 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 0 355
+1928 */{ 2 , 1 , 23 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1929 */{ 0 , 2 , 10 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1930 */{ 6 , 1 , 30 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 29 383
+1931 */{ 0 , 2 , 17 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1932 */{ 0 , 2 , 6 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1933 */{ 5 , 1 , 26 , 27976 },/* 29 30 30 29 30 30 29 30 29 30 29 29 30 384
+1934 */{ 0 , 2 , 14 , 23248 },/* 29 30 29 30 30 29 30 29 30 30 29 30 0 355
+1935 */{ 0 , 2 , 4 , 11104 },/* 29 29 30 29 30 29 30 30 29 30 30 29 0 354
+1936 */{ 3 , 1 , 24 , 37744 },/* 30 29 29 30 29 29 30 30 29 30 30 30 29 384
+1937 */{ 0 , 2 , 11 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+1938 */{ 7 , 1 , 31 , 51560 },/* 30 30 29 29 30 29 29 30 29 30 30 29 30 384
+1939 */{ 0 , 2 , 19 , 51536 },/* 30 30 29 29 30 29 29 30 29 30 29 30 0 354
+1940 */{ 0 , 2 , 8 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+1941 */{ 6 , 1 , 27 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 29 384
+1942 */{ 0 , 2 , 15 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1943 */{ 0 , 2 , 5 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1944 */{ 4 , 1 , 25 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+1945 */{ 0 , 2 , 13 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+1946 */{ 0 , 2 , 2 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+1947 */{ 2 , 1 , 22 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+1948 */{ 0 , 2 , 10 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+1949 */{ 7 , 1 , 29 , 46248 },/* 30 29 30 30 29 30 29 29 30 29 30 29 30 384
+1950 */{ 0 , 2 , 17 , 27808 },/* 29 30 30 29 30 30 29 29 30 29 30 29 0 354
+1951 */{ 0 , 2 , 6 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 0 355
+1952 */{ 5 , 1 , 27 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+1953 */{ 0 , 2 , 14 , 19872 },/* 29 30 29 29 30 30 29 30 30 29 30 29 0 354
+1954 */{ 0 , 2 , 3 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+1955 */{ 3 , 1 , 24 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+1956 */{ 0 , 2 , 12 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+1957 */{ 8 , 1 , 31 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 29 383
+1958 */{ 0 , 2 , 18 , 59728 },/* 30 30 30 29 30 29 29 30 29 30 29 30 0 355
+1959 */{ 0 , 2 , 8 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+1960 */{ 6 , 1 , 28 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 29 384
+1961 */{ 0 , 2 , 15 , 43856 },/* 30 29 30 29 30 29 30 30 29 30 29 30 0 355
+1962 */{ 0 , 2 , 5 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+1963 */{ 4 , 1 , 25 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+1964 */{ 0 , 2 , 13 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 0 355
+1965 */{ 0 , 2 , 2 , 21088 },/* 29 30 29 30 29 29 30 29 29 30 30 29 0 353
+1966 */{ 3 , 1 , 21 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+1967 */{ 0 , 2 , 9 , 55632 },/* 30 30 29 30 30 29 29 30 29 30 29 30 0 355
+1968 */{ 7 , 1 , 30 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+1969 */{ 0 , 2 , 17 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+1970 */{ 0 , 2 , 6 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+1971 */{ 5 , 1 , 27 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+1972 */{ 0 , 2 , 15 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+1973 */{ 0 , 2 , 3 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+1974 */{ 4 , 1 , 23 , 53864 },/* 30 30 29 30 29 29 30 29 29 30 30 29 30 384
+1975 */{ 0 , 2 , 11 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+1976 */{ 8 , 1 , 31 , 54568 },/* 30 30 29 30 29 30 29 30 29 29 30 29 30 384
+1977 */{ 0 , 2 , 18 , 46400 },/* 30 29 30 30 29 30 29 30 29 30 29 29 0 354
+1978 */{ 0 , 2 , 7 , 46752 },/* 30 29 30 30 29 30 30 29 30 29 30 29 0 355
+1979 */{ 6 , 1 , 28 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 29 384
+1980 */{ 0 , 2 , 16 , 38320 },/* 30 29 29 30 29 30 29 30 30 29 30 30 0 355
+1981 */{ 0 , 2 , 5 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+1982 */{ 4 , 1 , 25 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+1983 */{ 0 , 2 , 13 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+1984 */{ 10 , 2 , 2 , 45656 },/* 30 29 30 30 29 29 30 29 29 30 29 30 30 384
+1985 */{ 0 , 2 , 20 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 0 354
+1986 */{ 0 , 2 , 9 , 27968 },/* 29 30 30 29 30 30 29 30 29 30 29 29 0 354
+1987 */{ 6 , 1 , 29 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 29 384
+1988 */{ 0 , 2 , 17 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1989 */{ 0 , 2 , 6 , 38256 },/* 30 29 29 30 29 30 29 30 29 30 30 30 0 355
+1990 */{ 5 , 1 , 27 , 18808 },/* 29 30 29 29 30 29 29 30 29 30 30 30 30 384
+1991 */{ 0 , 2 , 15 , 18800 },/* 29 30 29 29 30 29 29 30 29 30 30 30 0 354
+1992 */{ 0 , 2 , 4 , 25776 },/* 29 30 30 29 29 30 29 29 30 29 30 30 0 354
+1993 */{ 3 , 1 , 23 , 27216 },/* 29 30 30 29 30 29 30 29 29 30 29 30 29 383
+1994 */{ 0 , 2 , 10 , 59984 },/* 30 30 30 29 30 29 30 29 29 30 29 30 0 355
+1995 */{ 8 , 1 , 31 , 27432 },/* 29 30 30 29 30 29 30 30 29 29 30 29 30 384
+1996 */{ 0 , 2 , 19 , 23232 },/* 29 30 29 30 30 29 30 29 30 30 29 29 0 354
+1997 */{ 0 , 2 , 7 , 43872 },/* 30 29 30 29 30 29 30 30 29 30 30 29 0 355
+1998 */{ 5 , 1 , 28 , 37736 },/* 30 29 29 30 29 29 30 30 29 30 30 29 30 384
+1999 */{ 0 , 2 , 16 , 37600 },/* 30 29 29 30 29 29 30 29 30 30 30 29 0 354
+2000 */{ 0 , 2 , 5 , 51552 },/* 30 30 29 29 30 29 29 30 29 30 30 29 0 354
+2001 */{ 4 , 1 , 24 , 54440 },/* 30 30 29 30 29 30 29 29 30 29 30 29 30 384
+2002 */{ 0 , 2 , 12 , 54432 },/* 30 30 29 30 29 30 29 29 30 29 30 29 0 354
+2003 */{ 0 , 2 , 1 , 55888 },/* 30 30 29 30 30 29 30 29 29 30 29 30 0 355
+2004 */{ 2 , 1 , 22 , 23208 },/* 29 30 29 30 30 29 30 29 30 29 30 29 30 384
+2005 */{ 0 , 2 , 9 , 22176 },/* 29 30 29 30 29 30 30 29 30 29 30 29 0 354
+2006 */{ 7 , 1 , 29 , 43736 },/* 30 29 30 29 30 29 30 29 30 30 29 30 30 385
+2007 */{ 0 , 2 , 18 , 9680 },/* 29 29 30 29 29 30 29 30 30 30 29 30 0 354
+2008 */{ 0 , 2 , 7 , 37584 },/* 30 29 29 30 29 29 30 29 30 30 29 30 0 354
+2009 */{ 5 , 1 , 26 , 51544 },/* 30 30 29 29 30 29 29 30 29 30 29 30 30 384
+2010 */{ 0 , 2 , 14 , 43344 },/* 30 29 30 29 30 29 29 30 29 30 29 30 0 354
+2011 */{ 0 , 2 , 3 , 46240 },/* 30 29 30 30 29 30 29 29 30 29 30 29 0 354
+2012 */{ 4 , 1 , 23 , 46416 },/* 30 29 30 30 29 30 29 30 29 30 29 30 29 384
+2013 */{ 0 , 2 , 10 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2014 */{ 9 , 1 , 31 , 21928 },/* 29 30 29 30 29 30 29 30 30 29 30 29 30 384
+2015 */{ 0 , 2 , 19 , 19360 },/* 29 30 29 29 30 29 30 30 30 29 30 29 0 354
+2016 */{ 0 , 2 , 8 , 42416 },/* 30 29 30 29 29 30 29 30 30 29 30 30 0 355
+2017 */{ 6 , 1 , 28 , 21176 },/* 29 30 29 30 29 29 30 29 30 29 30 30 30 384
+2018 */{ 0 , 2 , 16 , 21168 },/* 29 30 29 30 29 29 30 29 30 29 30 30 0 354
+2019 */{ 0 , 2 , 5 , 43312 },/* 30 29 30 29 30 29 29 30 29 29 30 30 0 354
+2020 */{ 4 , 1 , 25 , 29864 },/* 29 30 30 30 29 30 29 29 30 29 30 29 30 384
+2021 */{ 0 , 2 , 12 , 27296 },/* 29 30 30 29 30 29 30 29 30 29 30 29 0 354
+2022 */{ 0 , 2 , 1 , 44368 },/* 30 29 30 29 30 30 29 30 29 30 29 30 0 355
+2023 */{ 2 , 1 , 22 , 19880 },/* 29 30 29 29 30 30 29 30 30 29 30 29 30 384
+2024 */{ 0 , 2 , 10 , 19296 },/* 29 30 29 29 30 29 30 30 29 30 30 29 0 354
+2025 */{ 6 , 1 , 29 , 42352 },/* 30 29 30 29 29 30 29 30 29 30 30 30 29 384
+2026 */{ 0 , 2 , 17 , 42208 },/* 30 29 30 29 29 30 29 29 30 30 30 29 0 354
+2027 */{ 0 , 2 , 6 , 53856 },/* 30 30 29 30 29 29 30 29 29 30 30 29 0 354
+2028 */{ 5 , 1 , 26 , 59696 },/* 30 30 30 29 30 29 29 30 29 29 30 30 29 384
+2029 */{ 0 , 2 , 13 , 54576 },/* 30 30 29 30 29 30 29 30 29 29 30 30 0 355
+2030 */{ 0 , 2 , 3 , 23200 },/* 29 30 29 30 30 29 30 29 30 29 30 29 0 354
+2031 */{ 3 , 1 , 23 , 27472 },/* 29 30 30 29 30 29 30 30 29 30 29 30 29 384
+2032 */{ 0 , 2 , 11 , 38608 },/* 30 29 29 30 29 30 30 29 30 30 29 30 0 355
+2033 */{ 11 , 1 , 31 , 19176 },/* 29 30 29 29 30 29 30 29 30 30 30 29 30 384
+2034 */{ 0 , 2 , 19 , 19152 },/* 29 30 29 29 30 29 30 29 30 30 29 30 0 354
+2035 */{ 0 , 2 , 8 , 42192 },/* 30 29 30 29 29 30 29 29 30 30 29 30 0 354
+2036 */{ 6 , 1 , 28 , 53848 },/* 30 30 29 30 29 29 30 29 29 30 29 30 30 384
+2037 */{ 0 , 2 , 15 , 53840 },/* 30 30 29 30 29 29 30 29 29 30 29 30 0 354
+2038 */{ 0 , 2 , 4 , 54560 },/* 30 30 29 30 29 30 29 30 29 29 30 29 0 354
+2039 */{ 5 , 1 , 24 , 55968 },/* 30 30 29 30 30 29 30 29 30 29 30 29 29 384
+2040 */{ 0 , 2 , 12 , 46496 },/* 30 29 30 30 29 30 29 30 30 29 30 29 0 355
+2041 */{ 0 , 2 , 1 , 22224 },/* 29 30 29 30 29 30 30 29 30 30 29 30 0 355
+2042 */{ 2 , 1 , 22 , 19160 },/* 29 30 29 29 30 29 30 29 30 30 29 30 30 384
+2043 */{ 0 , 2 , 10 , 18864 },/* 29 30 29 29 30 29 29 30 30 29 30 30 0 354
+2044 */{ 7 , 1 , 30 , 42168 },/* 30 29 30 29 29 30 29 29 30 29 30 30 30 384
+2045 */{ 0 , 2 , 17 , 42160 },/* 30 29 30 29 29 30 29 29 30 29 30 30 0 354
+2046 */{ 0 , 2 , 6 , 43600 },/* 30 29 30 29 30 29 30 29 29 30 29 30 0 354
+2047 */{ 5 , 1 , 26 , 46376 },/* 30 29 30 30 29 30 29 30 29 29 30 29 30 384
+2048 */{ 0 , 2 , 14 , 27936 },/* 29 30 30 29 30 30 29 30 29 29 30 29 0 354
+2049 */{ 0 , 2 , 2 , 44448 },/* 30 29 30 29 30 30 29 30 30 29 30 29 0 355
+2050 */{ 3 , 1 , 23 , 21936 },/* 29 30 29 30 29 30 29 30 30 29 30 30 29 384
+ */};
+
+
+ internal override int MinCalendarYear {
+ get
+ {
+ return (MIN_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override int MaxCalendarYear {
+ get
+ {
+ return (MAX_LUNISOLAR_YEAR);
+ }
+ }
+
+ internal override DateTime MinDate {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+ internal override DateTime MaxDate {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+ internal override EraInfo[] CalEraInfo {
+ get
+ {
+ return (taiwanLunisolarEraInfo);
+ }
+ }
+
+ internal override int GetYearInfo(int LunarYear, int Index) {
+ if ((LunarYear < MIN_LUNISOLAR_YEAR) || (LunarYear > MAX_LUNISOLAR_YEAR)) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ MIN_LUNISOLAR_YEAR,
+ MAX_LUNISOLAR_YEAR ));
+ }
+ Contract.EndContractBlock();
+
+ return yinfo[LunarYear - MIN_LUNISOLAR_YEAR, Index];
+ }
+
+ internal override int GetYear(int year, DateTime time) {
+ return helper.GetYear(year, time);
+ }
+
+ internal override int GetGregorianYear(int year, int era) {
+ return helper.GetGregorianYear(year, era);
+ }
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of TaiwanLunisolarCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+ /*
+ internal static Calendar GetDefaultInstance()
+ {
+ if (m_defaultInstance == null) {
+ m_defaultInstance = new TaiwanLunisolarCalendar();
+ }
+ return (m_defaultInstance);
+ }
+ */
+
+ // Construct an instance of TaiwanLunisolar calendar.
+
+ public TaiwanLunisolarCalendar() {
+ helper = new GregorianCalendarHelper(this, taiwanLunisolarEraInfo);
+ }
+
+
+
+ public override int GetEra(DateTime time) {
+ return (helper.GetEra(time));
+ }
+
+ internal override int BaseCalendarID {
+ get {
+ return (CAL_TAIWAN);
+ }
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_TAIWANLUNISOLAR);
+ }
+ }
+
+
+
+ public override int[] Eras {
+ get {
+ return (helper.Eras);
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/TextElementEnumerator.cs b/src/mscorlib/src/System/Globalization/TextElementEnumerator.cs
new file mode 100644
index 0000000000..5f47f5fbd4
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/TextElementEnumerator.cs
@@ -0,0 +1,155 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose:
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Runtime.Serialization;
+
+
+namespace System.Globalization {
+ using System.Collections;
+ using System.Diagnostics.Contracts;
+
+ //
+ // This is public because GetTextElement() is public.
+ //
+
+ [Serializable]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public class TextElementEnumerator: IEnumerator
+ {
+ private String str;
+ private int index;
+ private int startIndex;
+
+ [NonSerialized]
+ private int strLen; // This is the length of the total string, counting from the beginning of string.
+
+ [NonSerialized]
+ private int currTextElementLen; // The current text element lenght after MoveNext() is called.
+
+ [OptionalField(VersionAdded = 2)]
+ private UnicodeCategory uc;
+
+ [OptionalField(VersionAdded = 2)]
+ private int charLen; // The next abstract char to look at after MoveNext() is called. It could be 1 or 2, depending on if it is a surrogate or not.
+
+ internal TextElementEnumerator(String str, int startIndex, int strLen)
+ {
+ Contract.Assert(str != null, "TextElementEnumerator(): str != null");
+ Contract.Assert(startIndex >= 0 && strLen >= 0, "TextElementEnumerator(): startIndex >= 0 && strLen >= 0");
+ Contract.Assert(strLen >= startIndex, "TextElementEnumerator(): strLen >= startIndex");
+ this.str = str;
+ this.startIndex = startIndex;
+ this.strLen = strLen;
+ Reset();
+ }
+
+#region Serialization
+ // the following fields is defined to keep the compatibility with Everett.
+ // don't change/remove the names/types of these fields.
+ private int endIndex;
+ private int nextTextElementLen;
+
+ [OnDeserializing]
+ private void OnDeserializing(StreamingContext ctx)
+ {
+ charLen = -1;
+ }
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ strLen = endIndex + 1;
+ currTextElementLen = nextTextElementLen;
+
+ if (charLen == -1)
+ {
+ uc = CharUnicodeInfo.InternalGetUnicodeCategory(str, index, out charLen);
+ }
+ }
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+ endIndex = strLen - 1;
+ nextTextElementLen = currTextElementLen;
+ }
+
+#endregion Serialization
+
+
+
+ public bool MoveNext()
+ {
+ if (index >= strLen)
+ {
+ // Make the index to be greater than strLen so that we can throw exception if GetTextElement() is called.
+ index = strLen + 1;
+ return (false);
+ }
+ currTextElementLen = StringInfo.GetCurrentTextElementLen(str, index, strLen, ref uc, ref charLen);
+ index += currTextElementLen;
+ return (true);
+ }
+
+ //
+ // Get the current text element.
+ //
+
+ public Object Current {
+ get {
+ return (GetTextElement());
+ }
+ }
+
+ //
+ // Get the current text element.
+ //
+
+ public String GetTextElement()
+ {
+ if (index == startIndex) {
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumNotStarted"));
+ }
+ if (index > strLen) {
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumEnded"));
+ }
+
+ return (str.Substring(index - currTextElementLen, currTextElementLen));
+ }
+
+ //
+ // Get the starting index of the current text element.
+ //
+
+ public int ElementIndex
+ {
+ get
+ {
+ if (index == startIndex)
+ {
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumNotStarted"));
+ }
+ return (index - currTextElementLen);
+ }
+ }
+
+
+ public void Reset()
+ {
+ index = startIndex;
+ if (index < strLen) {
+ // If we have more than 1 character, get the category of the current char.
+ uc = CharUnicodeInfo.InternalGetUnicodeCategory(str, index, out charLen);
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/TextInfo.cs b/src/mscorlib/src/System/Globalization/TextInfo.cs
new file mode 100644
index 0000000000..9b84486f4e
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/TextInfo.cs
@@ -0,0 +1,1004 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This Class defines behaviors specific to a writing system.
+// A writing system is the collection of scripts and
+// orthographic rules required to represent a language as text.
+//
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Security;
+
+namespace System.Globalization {
+ using System;
+ using System.Text;
+ using System.Threading;
+ using System.Runtime;
+ using System.Runtime.InteropServices;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.Serialization;
+ using System.Runtime.Versioning;
+ using System.Security.Permissions;
+ using System.Diagnostics.Contracts;
+
+
+ [Serializable]
+ [System.Runtime.InteropServices.ComVisible(true)]
+ public partial class TextInfo : ICloneable, IDeserializationCallback
+ {
+ //--------------------------------------------------------------------//
+ // Internal Information //
+ //--------------------------------------------------------------------//
+
+ private enum Tristate : byte
+ {
+ NotInitialized,
+ True,
+ False,
+ }
+
+ //
+ // Variables.
+ //
+
+ [OptionalField(VersionAdded = 2)]
+ private String m_listSeparator;
+
+ [OptionalField(VersionAdded = 2)]
+ private bool m_isReadOnly = false;
+
+ //
+ // In Whidbey we had several names:
+ // m_win32LangID is the name of the culture, but only used for (de)serialization.
+ // customCultureName is the name of the creating custom culture (if custom) In combination with m_win32LangID
+ // this is authoratative, ie when deserializing.
+ // m_cultureTableRecord was the data record of the creating culture. (could have different name if custom)
+ // m_textInfoID is the LCID of the textinfo itself (no longer used)
+ // m_name is the culture name (from cultureinfo.name)
+ //
+ // In Silverlight/Arrowhead this is slightly different:
+ // m_cultureName is the name of the creating culture. Note that we consider this authoratative,
+ // if the culture's textinfo changes when deserializing, then behavior may change.
+ // (ala Whidbey behavior). This is the only string Arrowhead needs to serialize.
+ // m_cultureData is the data that backs this class.
+ // m_textInfoName is the actual name of the textInfo (from cultureData.STEXTINFO)
+ // m_textInfoName can be the same as m_cultureName on Silverlight since the OS knows
+ // how to do the sorting. However in the desktop, when we call the sorting dll, it doesn't
+ // know how to resolve custom locle names to sort ids so we have to have alredy resolved this.
+ //
+
+ [OptionalField(VersionAdded = 3)]
+ private String m_cultureName; // Name of the culture that created this text info
+ [NonSerialized]private CultureData m_cultureData; // Data record for the culture that made us, not for this textinfo
+ [NonSerialized]private String m_textInfoName; // Name of the text info we're using (ie: m_cultureData.STEXTINFO)
+ [NonSerialized]private IntPtr m_dataHandle; // Sort handle
+ [NonSerialized]private IntPtr m_handleOrigin;
+ [NonSerialized]private Tristate m_IsAsciiCasingSameAsInvariant = Tristate.NotInitialized;
+
+
+ // Invariant text info
+ internal static TextInfo Invariant
+ {
+ get
+ {
+ if (s_Invariant == null)
+ s_Invariant = new TextInfo(CultureData.Invariant);
+ return s_Invariant;
+ }
+ }
+ internal volatile static TextInfo s_Invariant;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // TextInfo Constructors
+ //
+ // Implements CultureInfo.TextInfo.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ internal TextInfo(CultureData cultureData)
+ {
+ // This is our primary data source, we don't need most of the rest of this
+ this.m_cultureData = cultureData;
+ this.m_cultureName = this.m_cultureData.CultureName;
+ this.m_textInfoName = this.m_cultureData.STEXTINFO;
+#if !FEATURE_CORECLR
+ IntPtr handleOrigin;
+ this.m_dataHandle = CompareInfo.InternalInitSortHandle(m_textInfoName, out handleOrigin);
+ this.m_handleOrigin = handleOrigin;
+#endif
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Serialization / Deserialization
+ //
+ // Note that we have to respect the Whidbey behavior for serialization compatibility
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+#region Serialization
+ // the following fields are defined to keep the compatibility with Whidbey.
+ // don't change/remove the names/types of these fields.
+ [OptionalField(VersionAdded = 2)]
+ private string customCultureName;
+
+ // the following fields are defined to keep compatibility with Everett.
+ // don't change/remove the names/types of these fields.
+ [OptionalField(VersionAdded = 1)]
+ internal int m_nDataItem;
+ [OptionalField(VersionAdded = 1)]
+ internal bool m_useUserOverride;
+ [OptionalField(VersionAdded = 1)]
+ internal int m_win32LangID;
+
+
+ [OnDeserializing]
+ private void OnDeserializing(StreamingContext ctx)
+ {
+ // Clear these so we can check if we've fixed them yet
+ this.m_cultureData = null;
+ this.m_cultureName = null;
+ }
+
+ private void OnDeserialized()
+ {
+ // this method will be called twice because of the support of IDeserializationCallback
+ if (this.m_cultureData == null)
+ {
+ if (this.m_cultureName == null)
+ {
+ // This is whidbey data, get it from customCultureName/win32langid
+ if (this.customCultureName != null)
+ {
+ // They gave a custom cultuer name, so use that
+ this.m_cultureName = this.customCultureName;
+ }
+#if FEATURE_USE_LCID
+ else
+ {
+ if (m_win32LangID == 0)
+ {
+ // m_cultureName and m_win32LangID are nulls which means we got uninitialized textinfo serialization stream.
+ // To be compatible with v2/3/3.5 we need to return ar-SA TextInfo in this case.
+ m_cultureName = "ar-SA";
+ }
+ else
+ {
+ // No custom culture, use the name from the LCID
+ m_cultureName = CultureInfo.GetCultureInfo(m_win32LangID).m_cultureData.CultureName;
+ }
+ }
+#endif
+ }
+
+ // Get the text info name belonging to that culture
+ this.m_cultureData = CultureInfo.GetCultureInfo(m_cultureName).m_cultureData;
+ this.m_textInfoName = this.m_cultureData.STEXTINFO;
+#if !FEATURE_CORECLR
+ IntPtr handleOrigin;
+ this.m_dataHandle = CompareInfo.InternalInitSortHandle(m_textInfoName, out handleOrigin);
+ this.m_handleOrigin = handleOrigin;
+#endif
+ }
+ }
+
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ OnDeserialized();
+ }
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+#if !FEATURE_CORECLR
+ // Initialize the fields Whidbey expects:
+ // Whidbey expected this, so set it, but the value doesn't matter much
+ this.m_useUserOverride = false;
+#endif // FEATURE_CORECLR
+
+ // Relabel our name since Whidbey expects it to be called customCultureName
+ this.customCultureName = this.m_cultureName;
+
+#if FEATURE_USE_LCID
+ // Ignore the m_win32LangId because whidbey'll just get it by name if we make it the LOCALE_CUSTOM_UNSPECIFIED.
+ this.m_win32LangID = (CultureInfo.GetCultureInfo(m_cultureName)).LCID;
+#endif
+ }
+
+#endregion Serialization
+
+ //
+ // Internal ordinal comparison functions
+ //
+ internal static int GetHashCodeOrdinalIgnoreCase(String s)
+ {
+ return GetHashCodeOrdinalIgnoreCase(s, false, 0);
+ }
+
+ internal static int GetHashCodeOrdinalIgnoreCase(String s, bool forceRandomizedHashing, long additionalEntropy)
+ {
+ // This is the same as an case insensitive hash for Invariant
+ // (not necessarily true for sorting, but OK for casing & then we apply normal hash code rules)
+ return (Invariant.GetCaseInsensitiveHashCode(s, forceRandomizedHashing, additionalEntropy));
+ }
+
+ [System.Security.SecuritySafeCritical]
+ internal static unsafe bool TryFastFindStringOrdinalIgnoreCase(int searchFlags, String source, int startIndex, String value, int count, ref int foundIndex)
+ {
+ return InternalTryFindStringOrdinalIgnoreCase(searchFlags, source, count, startIndex, value, value.Length, ref foundIndex);
+ }
+
+ // This function doesn't check arguments. Please do check in the caller.
+ // The underlying unmanaged code will assert the sanity of arguments.
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal static unsafe int CompareOrdinalIgnoreCase(String str1, String str2)
+ {
+ // Compare the whole string and ignore case.
+ return InternalCompareStringOrdinalIgnoreCase(str1, 0, str2, 0, str1.Length, str2.Length);
+ }
+
+ // This function doesn't check arguments. Please do check in the caller.
+ // The underlying unmanaged code will assert the sanity of arguments.
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal static unsafe int CompareOrdinalIgnoreCaseEx(String strA, int indexA, String strB, int indexB, int lengthA, int lengthB )
+ {
+ Contract.Assert(strA.Length >= indexA + lengthA, "[TextInfo.CompareOrdinalIgnoreCaseEx] Caller should've validated strA.Length >= indexA + lengthA");
+ Contract.Assert(strB.Length >= indexB + lengthB, "[TextInfo.CompareOrdinalIgnoreCaseEx] Caller should've validated strB.Length >= indexB + lengthB");
+ return InternalCompareStringOrdinalIgnoreCase(strA, indexA, strB, indexB, lengthA, lengthB);
+ }
+
+ internal static int IndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count)
+ {
+ Contract.Assert(source != null, "[TextInfo.IndexOfStringOrdinalIgnoreCase] Caller should've validated source != null");
+ Contract.Assert(value != null, "[TextInfo.IndexOfStringOrdinalIgnoreCase] Caller should've validated value != null");
+ Contract.Assert(startIndex + count <= source.Length, "[TextInfo.IndexOfStringOrdinalIgnoreCase] Caller should've validated startIndex + count <= source.Length");
+
+ // We return 0 if both inputs are empty strings
+ if (source.Length == 0 && value.Length == 0)
+ {
+ return 0;
+ }
+
+ // fast path
+ int ret = -1;
+ if (TryFastFindStringOrdinalIgnoreCase(Microsoft.Win32.Win32Native.FIND_FROMSTART, source, startIndex, value, count, ref ret))
+ return ret;
+
+ // the search space within [source] starts at offset [startIndex] inclusive and includes
+ // [count] characters (thus the last included character is at index [startIndex + count -1]
+ // [end] is the index of the next character after the search space
+ // (it points past the end of the search space)
+ int end = startIndex + count;
+
+ // maxStartIndex is the index beyond which we never *start* searching, inclusive; in other words;
+ // a search could include characters beyond maxStartIndex, but we'd never begin a search at an
+ // index strictly greater than maxStartIndex.
+ int maxStartIndex = end - value.Length;
+
+ for (; startIndex <= maxStartIndex; startIndex++)
+ {
+ // We should always have the same or more characters left to search than our actual pattern
+ Contract.Assert(end - startIndex >= value.Length);
+ // since this is an ordinal comparison, we can assume that the lengths must match
+ if (CompareOrdinalIgnoreCaseEx(source, startIndex, value, 0, value.Length, value.Length) == 0)
+ {
+ return startIndex;
+ }
+ }
+
+ // Not found
+ return -1;
+ }
+
+ internal static int LastIndexOfStringOrdinalIgnoreCase(String source, String value, int startIndex, int count)
+ {
+ Contract.Assert(source != null, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated source != null");
+ Contract.Assert(value != null, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated value != null");
+ Contract.Assert(startIndex - count+1 >= 0, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated startIndex - count+1 >= 0");
+ Contract.Assert(startIndex <= source.Length, "[TextInfo.LastIndexOfStringOrdinalIgnoreCase] Caller should've validated startIndex <= source.Length");
+
+ // If value is Empty, the return value is startIndex
+ if (value.Length == 0)
+ {
+ return startIndex;
+ }
+
+ // fast path
+ int ret = -1;
+ if (TryFastFindStringOrdinalIgnoreCase(Microsoft.Win32.Win32Native.FIND_FROMEND, source, startIndex, value, count, ref ret))
+ return ret;
+
+ // the search space within [source] ends at offset [startIndex] inclusive
+ // and includes [count] characters
+ // minIndex is the first included character and is at index [startIndex - count + 1]
+ int minIndex = startIndex - count + 1;
+
+ // First place we can find it is start index - (value.length -1)
+ if (value.Length > 0)
+ {
+ startIndex -= (value.Length - 1);
+ }
+
+ for (; startIndex >= minIndex; startIndex--)
+ {
+ if (CompareOrdinalIgnoreCaseEx(source, startIndex, value, 0, value.Length, value.Length) == 0)
+ {
+ return startIndex;
+ }
+ }
+
+ // Not found
+ return -1;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CodePage
+ //
+ // Returns the number of the code page used by this writing system.
+ // The type parameter can be any of the following values:
+ // ANSICodePage
+ // OEMCodePage
+ // MACCodePage
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public virtual int ANSICodePage
+ {
+ get
+ {
+ return (this.m_cultureData.IDEFAULTANSICODEPAGE);
+ }
+ }
+
+
+ public virtual int OEMCodePage
+ {
+ get
+ {
+ return (this.m_cultureData.IDEFAULTOEMCODEPAGE);
+ }
+ }
+
+
+ public virtual int MacCodePage
+ {
+ get
+ {
+ return (this.m_cultureData.IDEFAULTMACCODEPAGE);
+ }
+ }
+
+
+ public virtual int EBCDICCodePage
+ {
+ get
+ {
+ return (this.m_cultureData.IDEFAULTEBCDICCODEPAGE);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // LCID
+ //
+ // We need a way to get an LCID from outside of the BCL. This prop is the way.
+ // NOTE: neutral cultures will cause GPS incorrect LCIDS from this
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+#if FEATURE_USE_LCID
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public int LCID
+ {
+ get
+ {
+ // Just use the LCID from our text info name
+ return CultureInfo.GetCultureInfo(this.m_textInfoName).LCID;
+ }
+ }
+#endif
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // CultureName
+ //
+ // The name of the culture associated with the current TextInfo.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public string CultureName
+ {
+ get
+ {
+ return(this.m_textInfoName);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // IsReadOnly
+ //
+ // Detect if the object is readonly.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public bool IsReadOnly
+ {
+ get { return (m_isReadOnly); }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Clone
+ //
+ // Is the implementation of ICloneable.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public virtual Object Clone()
+ {
+ object o = MemberwiseClone();
+ ((TextInfo) o).SetReadOnlyState(false);
+ return (o);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ReadOnly
+ //
+ // Create a cloned readonly instance or return the input one if it is
+ // readonly.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public static TextInfo ReadOnly(TextInfo textInfo)
+ {
+ if (textInfo == null) { throw new ArgumentNullException("textInfo"); }
+ Contract.EndContractBlock();
+ if (textInfo.IsReadOnly) { return (textInfo); }
+
+ TextInfo clonedTextInfo = (TextInfo)(textInfo.MemberwiseClone());
+ clonedTextInfo.SetReadOnlyState(true);
+
+ return (clonedTextInfo);
+ }
+
+ private void VerifyWritable()
+ {
+ if (m_isReadOnly)
+ {
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
+ }
+ Contract.EndContractBlock();
+ }
+
+ internal void SetReadOnlyState(bool readOnly)
+ {
+ m_isReadOnly = readOnly;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ListSeparator
+ //
+ // Returns the string used to separate items in a list.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public virtual String ListSeparator
+ {
+ [System.Security.SecuritySafeCritical] // auto-generated
+ get
+ {
+ if (m_listSeparator == null) {
+ m_listSeparator = this.m_cultureData.SLIST;
+ }
+ return (m_listSeparator);
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value", Environment.GetResourceString("ArgumentNull_String"));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ m_listSeparator = value;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToLower
+ //
+ // Converts the character or string to lower case. Certain locales
+ // have different casing semantics from the file systems in Win32.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual char ToLower(char c)
+ {
+ if(IsAscii(c) && IsAsciiCasingSameAsInvariant)
+ {
+ return ToLowerAsciiInvariant(c);
+ }
+ return (InternalChangeCaseChar(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, c, false));
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual String ToLower(String str)
+ {
+ if (str == null) { throw new ArgumentNullException("str"); }
+ Contract.EndContractBlock();
+
+ return InternalChangeCaseString(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, str, false);
+
+ }
+
+ static private Char ToLowerAsciiInvariant(Char c)
+ {
+ if ('A' <= c && c <= 'Z')
+ {
+ c = (Char)(c | 0x20);
+ }
+ return c;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToUpper
+ //
+ // Converts the character or string to upper case. Certain locales
+ // have different casing semantics from the file systems in Win32.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual char ToUpper(char c)
+ {
+ if (IsAscii(c) && IsAsciiCasingSameAsInvariant)
+ {
+ return ToUpperAsciiInvariant(c);
+ }
+ return (InternalChangeCaseChar(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, c, true));
+ }
+
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public unsafe virtual String ToUpper(String str)
+ {
+ if (str == null) { throw new ArgumentNullException("str"); }
+ Contract.EndContractBlock();
+ return InternalChangeCaseString(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, str, true);
+ }
+
+ static private Char ToUpperAsciiInvariant(Char c)
+ {
+ if ('a' <= c && c <= 'z')
+ {
+ c = (Char)(c & ~0x20);
+ }
+ return c;
+ }
+
+ static private bool IsAscii(Char c)
+ {
+ return c < 0x80;
+ }
+
+ private bool IsAsciiCasingSameAsInvariant
+ {
+ get
+ {
+ if (m_IsAsciiCasingSameAsInvariant == Tristate.NotInitialized)
+ {
+ m_IsAsciiCasingSameAsInvariant =
+ CultureInfo.GetCultureInfo(m_textInfoName).CompareInfo.Compare("abcdefghijklmnopqrstuvwxyz",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ CompareOptions.IgnoreCase) == 0 ? Tristate.True : Tristate.False;
+ }
+ return m_IsAsciiCasingSameAsInvariant == Tristate.True;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Equals
+ //
+ // Implements Object.Equals(). Returns a boolean indicating whether
+ // or not object refers to the same CultureInfo as the current instance.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override bool Equals(Object obj)
+ {
+ TextInfo that = obj as TextInfo;
+
+ if (that != null)
+ {
+ return this.CultureName.Equals(that.CultureName);
+ }
+
+ return (false);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // GetHashCode
+ //
+ // Implements Object.GetHashCode(). Returns the hash code for the
+ // CultureInfo. The hash code is guaranteed to be the same for CultureInfo A
+ // and B where A.Equals(B) is true.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override int GetHashCode()
+ {
+ return (this.CultureName.GetHashCode());
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // ToString
+ //
+ // Implements Object.ToString(). Returns a string describing the
+ // TextInfo.
+ //
+ ////////////////////////////////////////////////////////////////////////
+
+
+ public override String ToString()
+ {
+ return ("TextInfo - " + this.m_cultureData.CultureName);
+ }
+
+
+ //
+ // Titlecasing:
+ // -----------
+ // Titlecasing refers to a casing practice wherein the first letter of a word is an uppercase letter
+ // and the rest of the letters are lowercase. The choice of which words to titlecase in headings
+ // and titles is dependent on language and local conventions. For example, "The Merry Wives of Windor"
+ // is the appropriate titlecasing of that play's name in English, with the word "of" not titlecased.
+ // In German, however, the title is "Die lustigen Weiber von Windsor," and both "lustigen" and "von"
+ // are not titlecased. In French even fewer words are titlecased: "Les joyeuses commeres de Windsor."
+ //
+ // Moreover, the determination of what actually constitutes a word is language dependent, and this can
+ // influence which letter or letters of a "word" are uppercased when titlecasing strings. For example
+ // "l'arbre" is considered two words in French, whereas "can't" is considered one word in English.
+ //
+ //
+ // Differences between UNICODE 5.0 and the .NET Framework:
+ // -------------------------------------------------------------------------------------
+ // The .NET Framework previously shipped a naive titlecasing implementation. Every word is titlecased
+ // regardless of language or orthographic practice. Furthermore, apostrophe is always considered to be
+ // a word joiner as used in English. The longterm vision is to depend on the operating system for
+ // titlecasing. Windows 7 is expected to be the first release with this feature. On the Macintosh side,
+ // titlecasing is not available as of version 10.5 of the operating system.
+ //
+ public unsafe String ToTitleCase(String str)
+ {
+ if (str == null)
+ {
+ throw new ArgumentNullException("str");
+ }
+ Contract.EndContractBlock();
+ if (str.Length == 0)
+ {
+ return (str);
+ }
+
+ StringBuilder result = new StringBuilder();
+ String lowercaseData = null;
+
+ for (int i = 0; i < str.Length; i++)
+ {
+ UnicodeCategory charType;
+ int charLen;
+
+ charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen);
+ if (Char.CheckLetter(charType))
+ {
+ // Do the titlecasing for the first character of the word.
+ i = AddTitlecaseLetter(ref result, ref str, i, charLen) + 1;
+
+ //
+ // Convert the characters until the end of the this word
+ // to lowercase.
+ //
+ int lowercaseStart = i;
+
+ //
+ // Use hasLowerCase flag to prevent from lowercasing acronyms (like "URT", "USA", etc)
+ // This is in line with Word 2000 behavior of titlecasing.
+ //
+ bool hasLowerCase = (charType == UnicodeCategory.LowercaseLetter);
+ // Use a loop to find all of the other letters following this letter.
+ while (i < str.Length)
+ {
+ charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen);
+ if (IsLetterCategory(charType))
+ {
+ if (charType == UnicodeCategory.LowercaseLetter)
+ {
+ hasLowerCase = true;
+ }
+ i += charLen;
+ }
+ else if (str[i] == '\'')
+ {
+ i++;
+ if (hasLowerCase)
+ {
+ if (lowercaseData == null)
+ {
+ lowercaseData = this.ToLower(str);
+ }
+ result.Append(lowercaseData, lowercaseStart, i - lowercaseStart);
+ }
+ else
+ {
+ result.Append(str, lowercaseStart, i - lowercaseStart);
+ }
+ lowercaseStart = i;
+ hasLowerCase = true;
+ }
+ else if (!IsWordSeparator(charType))
+ {
+ // This category is considered to be part of the word.
+ // This is any category that is marked as false in wordSeprator array.
+ i+= charLen;
+ }
+ else
+ {
+ // A word separator. Break out of the loop.
+ break;
+ }
+ }
+
+ int count = i - lowercaseStart;
+
+ if (count>0)
+ {
+ if (hasLowerCase)
+ {
+ if (lowercaseData == null)
+ {
+ lowercaseData = this.ToLower(str);
+ }
+ result.Append(lowercaseData, lowercaseStart, count);
+ }
+ else
+ {
+ result.Append(str, lowercaseStart, count);
+ }
+ }
+
+ if (i < str.Length)
+ {
+ // not a letter, just append it
+ i = AddNonLetter(ref result, ref str, i, charLen);
+ }
+ }
+ else
+ {
+ // not a letter, just append it
+ i = AddNonLetter(ref result, ref str, i, charLen);
+ }
+ }
+ return (result.ToString());
+ }
+
+ private static int AddNonLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen)
+ {
+ Contract.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddNonLetter] CharUnicodeInfo.InternalGetUnicodeCategory returned an unexpected charLen!");
+ if (charLen == 2)
+ {
+ // Surrogate pair
+ result.Append(input[inputIndex++]);
+ result.Append(input[inputIndex]);
+ }
+ else
+ {
+ result.Append(input[inputIndex]);
+ }
+ return inputIndex;
+ }
+
+
+ private int AddTitlecaseLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen)
+ {
+ Contract.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddTitlecaseLetter] CharUnicodeInfo.InternalGetUnicodeCategory returned an unexpected charLen!");
+
+ // for surrogate pairs do a simple ToUpper operation on the substring
+ if (charLen == 2)
+ {
+ // Surrogate pair
+ result.Append( this.ToUpper(input.Substring(inputIndex, charLen)) );
+ inputIndex++;
+ }
+ else
+ {
+ switch (input[inputIndex])
+ {
+ //
+ // For AppCompat, the Titlecase Case Mapping data from NDP 2.0 is used below.
+ case (char)0x01C4: // DZ with Caron -> Dz with Caron
+ case (char)0x01C5: // Dz with Caron -> Dz with Caron
+ case (char)0x01C6: // dz with Caron -> Dz with Caron
+ result.Append( (char)0x01C5 );
+ break;
+ case (char)0x01C7: // LJ -> Lj
+ case (char)0x01C8: // Lj -> Lj
+ case (char)0x01C9: // lj -> Lj
+ result.Append( (char)0x01C8 );
+ break;
+ case (char)0x01CA: // NJ -> Nj
+ case (char)0x01CB: // Nj -> Nj
+ case (char)0x01CC: // nj -> Nj
+ result.Append( (char)0x01CB );
+ break;
+ case (char)0x01F1: // DZ -> Dz
+ case (char)0x01F2: // Dz -> Dz
+ case (char)0x01F3: // dz -> Dz
+ result.Append( (char)0x01F2 );
+ break;
+ default:
+ result.Append( this.ToUpper(input[inputIndex]) );
+ break;
+ }
+ }
+ return inputIndex;
+ }
+
+
+ //
+ // Used in ToTitleCase():
+ // When we find a starting letter, the following array decides if a category should be
+ // considered as word seprator or not.
+ //
+ private const int wordSeparatorMask =
+ /* false */ (0 << 0) | // UppercaseLetter = 0,
+ /* false */ (0 << 1) | // LowercaseLetter = 1,
+ /* false */ (0 << 2) | // TitlecaseLetter = 2,
+ /* false */ (0 << 3) | // ModifierLetter = 3,
+ /* false */ (0 << 4) | // OtherLetter = 4,
+ /* false */ (0 << 5) | // NonSpacingMark = 5,
+ /* false */ (0 << 6) | // SpacingCombiningMark = 6,
+ /* false */ (0 << 7) | // EnclosingMark = 7,
+ /* false */ (0 << 8) | // DecimalDigitNumber = 8,
+ /* false */ (0 << 9) | // LetterNumber = 9,
+ /* false */ (0 << 10) | // OtherNumber = 10,
+ /* true */ (1 << 11) | // SpaceSeparator = 11,
+ /* true */ (1 << 12) | // LineSeparator = 12,
+ /* true */ (1 << 13) | // ParagraphSeparator = 13,
+ /* true */ (1 << 14) | // Control = 14,
+ /* true */ (1 << 15) | // Format = 15,
+ /* false */ (0 << 16) | // Surrogate = 16,
+ /* false */ (0 << 17) | // PrivateUse = 17,
+ /* true */ (1 << 18) | // ConnectorPunctuation = 18,
+ /* true */ (1 << 19) | // DashPunctuation = 19,
+ /* true */ (1 << 20) | // OpenPunctuation = 20,
+ /* true */ (1 << 21) | // ClosePunctuation = 21,
+ /* true */ (1 << 22) | // InitialQuotePunctuation = 22,
+ /* true */ (1 << 23) | // FinalQuotePunctuation = 23,
+ /* true */ (1 << 24) | // OtherPunctuation = 24,
+ /* true */ (1 << 25) | // MathSymbol = 25,
+ /* true */ (1 << 26) | // CurrencySymbol = 26,
+ /* true */ (1 << 27) | // ModifierSymbol = 27,
+ /* true */ (1 << 28) | // OtherSymbol = 28,
+ /* false */ (0 << 29); // OtherNotAssigned = 29;
+
+ private static bool IsWordSeparator(UnicodeCategory category)
+ {
+ return (wordSeparatorMask & (1 << (int)category)) != 0;
+ }
+
+ private static bool IsLetterCategory(UnicodeCategory uc)
+ {
+ return (uc == UnicodeCategory.UppercaseLetter
+ || uc == UnicodeCategory.LowercaseLetter
+ || uc == UnicodeCategory.TitlecaseLetter
+ || uc == UnicodeCategory.ModifierLetter
+ || uc == UnicodeCategory.OtherLetter);
+ }
+
+ // IsRightToLeft
+ //
+ // Returns true if the dominant direction of text and UI such as the relative position of buttons and scroll bars
+ //
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public bool IsRightToLeft
+ {
+ get
+ {
+ return this.m_cultureData.IsRightToLeft;
+ }
+ }
+
+ /// <internalonly/>
+ void IDeserializationCallback.OnDeserialization(Object sender)
+ {
+ OnDeserialized();
+ }
+
+ //
+ // Get case-insensitive hash code for the specified string.
+ //
+ // NOTENOTE: this is an internal function. The caller should verify the string
+ // is not null before calling this. Currenlty, CaseInsensitiveHashCodeProvider
+ // does that.
+ //
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe int GetCaseInsensitiveHashCode(String str)
+ {
+ return GetCaseInsensitiveHashCode(str, false, 0);
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe int GetCaseInsensitiveHashCode(String str, bool forceRandomizedHashing, long additionalEntropy)
+ {
+ // Validate inputs
+ if (str==null)
+ {
+ throw new ArgumentNullException("str");
+ }
+ Contract.EndContractBlock();
+
+ // Return our result
+ return (InternalGetCaseInsHash(this.m_dataHandle, this.m_handleOrigin, this.m_textInfoName, str, forceRandomizedHashing, additionalEntropy));
+ }
+
+ // Change case (ToUpper/ToLower) -- COMNlsInfo::InternalChangeCaseChar
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private static unsafe extern char InternalChangeCaseChar(IntPtr handle, IntPtr handleOrigin, String localeName, char ch, bool isToUpper);
+
+ // Change case (ToUpper/ToLower) -- COMNlsInfo::InternalChangeCaseString
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private static unsafe extern String InternalChangeCaseString(IntPtr handle, IntPtr handleOrigin, String localeName, String str, bool isToUpper);
+
+ // Get case insensitive hash -- ComNlsInfo::InternalGetCaseInsHash
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private static unsafe extern int InternalGetCaseInsHash(IntPtr handle, IntPtr handleOrigin, String localeName, String str, bool forceRandomizedHashing, long additionalEntropy);
+
+ // Call ::CompareStringOrdinal -- ComNlsInfo::InternalCompareStringOrdinalIgnoreCase
+ // Start at indexes and compare for length characters (or remainder of string if length == -1)
+ [System.Security.SecurityCritical] // auto-generated
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ private static unsafe extern int InternalCompareStringOrdinalIgnoreCase(String string1, int index1, String string2, int index2, int length1, int length2);
+
+ // ComNlsInfo::InternalTryFindStringOrdinalIgnoreCase attempts a faster IndexOf/LastIndexOf OrdinalIgnoreCase using a kernel function.
+ // Returns true if FindStringOrdinal was handled, with foundIndex set to the target's index into the source
+ // Returns false when FindStringOrdinal wasn't handled
+ [System.Security.SecurityCritical] // auto-generated
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ [SuppressUnmanagedCodeSecurity]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static unsafe extern bool InternalTryFindStringOrdinalIgnoreCase(int searchFlags, String source, int sourceCount, int startIndex, String target, int targetCount, ref int foundIndex);
+ }
+
+}
+
+
diff --git a/src/mscorlib/src/System/Globalization/ThaiBuddhistCalendar.cs b/src/mscorlib/src/System/Globalization/ThaiBuddhistCalendar.cs
new file mode 100644
index 0000000000..f26c68adce
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/ThaiBuddhistCalendar.cs
@@ -0,0 +1,225 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+
+ /*=================================ThaiBuddhistCalendar==========================
+ **
+ ** ThaiBuddhistCalendar is based on Gregorian calendar. Its year value has
+ ** an offset to the Gregorain calendar.
+ **
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 0001/01/01 9999/12/31
+ ** Thai 0544/01/01 10542/12/31
+ ============================================================================*/
+
+
+[System.Runtime.InteropServices.ComVisible(true)]
+ [Serializable] public class ThaiBuddhistCalendar: Calendar {
+
+ // Initialize our era info.
+ static internal EraInfo[] thaiBuddhistEraInfo = new EraInfo[] {
+ new EraInfo( 1, 1, 1, 1, -543, 544, GregorianCalendar.MaxYear + 543) // era #, start year/month/day, yearOffset, minEraYear
+ };
+
+ //
+ // The era value for the current era.
+ //
+
+ public const int ThaiBuddhistEra = 1;
+
+ //internal static Calendar m_defaultInstance;
+
+ internal GregorianCalendarHelper helper;
+
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MinValue);
+ }
+ }
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ // Return the type of the Thai Buddhist calendar.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.SolarCalendar;
+ }
+ }
+
+ public ThaiBuddhistCalendar() {
+ helper = new GregorianCalendarHelper(this, thaiBuddhistEraInfo);
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_THAI);
+ }
+ }
+
+
+ public override DateTime AddMonths(DateTime time, int months) {
+ return (helper.AddMonths(time, months));
+ }
+
+
+ public override DateTime AddYears(DateTime time, int years) {
+ return (helper.AddYears(time, years));
+ }
+
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ return (helper.GetDaysInMonth(year, month, era));
+ }
+
+
+ public override int GetDaysInYear(int year, int era) {
+ return (helper.GetDaysInYear(year, era));
+ }
+
+
+ public override int GetDayOfMonth(DateTime time) {
+ return (helper.GetDayOfMonth(time));
+ }
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ return (helper.GetDayOfWeek(time));
+ }
+
+
+ public override int GetDayOfYear(DateTime time)
+ {
+ return (helper.GetDayOfYear(time));
+ }
+
+
+ public override int GetMonthsInYear(int year, int era) {
+ return (helper.GetMonthsInYear(year, era));
+ }
+
+
+ [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems.
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
+ {
+ return (helper.GetWeekOfYear(time, rule, firstDayOfWeek));
+ }
+
+
+ public override int GetEra(DateTime time) {
+ return (helper.GetEra(time));
+ }
+
+ public override int GetMonth(DateTime time) {
+ return (helper.GetMonth(time));
+ }
+
+
+ public override int GetYear(DateTime time) {
+ return (helper.GetYear(time));
+ }
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era)
+ {
+ return (helper.IsLeapDay(year, month, day, era));
+ }
+
+
+ public override bool IsLeapYear(int year, int era) {
+ return (helper.IsLeapYear(year, era));
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+ [System.Runtime.InteropServices.ComVisible(false)]
+ public override int GetLeapMonth(int year, int era)
+ {
+ return (helper.GetLeapMonth(year, era));
+ }
+
+
+ public override bool IsLeapMonth(int year, int month, int era) {
+ return (helper.IsLeapMonth(year, month, era));
+ }
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ return (helper.ToDateTime(year, month, day, hour, minute, second, millisecond, era));
+ }
+
+
+ public override int[] Eras {
+ get {
+ return (helper.Eras);
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 2572;
+
+
+ public override int TwoDigitYearMax
+ {
+ get
+ {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ VerifyWritable();
+ if (value < 99 || value > helper.MaxYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ 99,
+ helper.MaxYear));
+
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ return (helper.ToFourDigitYear(year, this.TwoDigitYearMax));
+ }
+ }
+}
+
diff --git a/src/mscorlib/src/System/Globalization/TimeSpanFormat.cs b/src/mscorlib/src/System/Globalization/TimeSpanFormat.cs
new file mode 100644
index 0000000000..8f58623868
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/TimeSpanFormat.cs
@@ -0,0 +1,474 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+namespace System.Globalization {
+ using System.Text;
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+
+ internal static class TimeSpanFormat {
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ private static String IntToString(int n, int digits) {
+ return ParseNumbers.IntToString(n, 10, digits, '0', 0);
+ }
+
+ internal static readonly FormatLiterals PositiveInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.InitInvariant(false /*isNegative*/);
+ internal static readonly FormatLiterals NegativeInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.InitInvariant(true /*isNegative*/);
+
+ internal enum Pattern {
+ None = 0,
+ Minimum = 1,
+ Full = 2,
+ }
+
+ //
+ // Format
+ //
+ // Actions: Main method called from TimeSpan.ToString
+ //
+ internal static String Format(TimeSpan value, String format, IFormatProvider formatProvider) {
+ if (format == null || format.Length == 0)
+ format = "c";
+
+ // standard formats
+ if (format.Length == 1) {
+ char f = format[0];
+
+ if (f == 'c' || f == 't' || f == 'T')
+ return FormatStandard(value, true, format, Pattern.Minimum);
+ if (f == 'g' || f == 'G') {
+ Pattern pattern;
+ DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(formatProvider);
+
+ if (value._ticks < 0)
+ format = dtfi.FullTimeSpanNegativePattern;
+ else
+ format = dtfi.FullTimeSpanPositivePattern;
+ if (f == 'g')
+ pattern = Pattern.Minimum;
+ else
+ pattern = Pattern.Full;
+
+ return FormatStandard(value, false, format, pattern);
+ }
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+
+ return FormatCustomized(value, format, DateTimeFormatInfo.GetInstance(formatProvider));
+ }
+
+ //
+ // FormatStandard
+ //
+ // Actions: Format the TimeSpan instance using the specified format.
+ //
+ private static String FormatStandard(TimeSpan value, bool isInvariant, String format, Pattern pattern) {
+ StringBuilder sb = StringBuilderCache.Acquire();
+ int day = (int)(value._ticks / TimeSpan.TicksPerDay);
+ long time = value._ticks % TimeSpan.TicksPerDay;
+
+ if (value._ticks < 0) {
+ day = -day;
+ time = -time;
+ }
+ int hours = (int)(time / TimeSpan.TicksPerHour % 24);
+ int minutes = (int)(time / TimeSpan.TicksPerMinute % 60);
+ int seconds = (int)(time / TimeSpan.TicksPerSecond % 60);
+ int fraction = (int)(time % TimeSpan.TicksPerSecond);
+
+ FormatLiterals literal;
+ if (isInvariant) {
+ if (value._ticks < 0)
+ literal = NegativeInvariantFormatLiterals;
+ else
+ literal = PositiveInvariantFormatLiterals;
+ }
+ else {
+ literal = new FormatLiterals();
+ literal.Init(format, pattern == Pattern.Full);
+ }
+ if (fraction != 0) { // truncate the partial second to the specified length
+ fraction = (int)((long)fraction / (long)Math.Pow(10, DateTimeFormat.MaxSecondsFractionDigits - literal.ff));
+ }
+
+ // Pattern.Full: [-]dd.hh:mm:ss.fffffff
+ // Pattern.Minimum: [-][d.]hh:mm:ss[.fffffff]
+
+ sb.Append(literal.Start); // [-]
+ if (pattern == Pattern.Full || day != 0) { //
+ sb.Append(day); // [dd]
+ sb.Append(literal.DayHourSep); // [.]
+ } //
+ sb.Append(IntToString(hours, literal.hh)); // hh
+ sb.Append(literal.HourMinuteSep); // :
+ sb.Append(IntToString(minutes, literal.mm)); // mm
+ sb.Append(literal.MinuteSecondSep); // :
+ sb.Append(IntToString(seconds, literal.ss)); // ss
+ if (!isInvariant && pattern == Pattern.Minimum) {
+ int effectiveDigits = literal.ff;
+ while (effectiveDigits > 0) {
+ if (fraction % 10 == 0) {
+ fraction = fraction / 10;
+ effectiveDigits--;
+ }
+ else {
+ break;
+ }
+ }
+ if (effectiveDigits > 0) {
+ sb.Append(literal.SecondFractionSep); // [.FFFFFFF]
+ sb.Append((fraction).ToString(DateTimeFormat.fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
+ }
+ }
+ else if (pattern == Pattern.Full || fraction != 0) {
+ sb.Append(literal.SecondFractionSep); // [.]
+ sb.Append(IntToString(fraction, literal.ff)); // [fffffff]
+ } //
+ sb.Append(literal.End); //
+
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+
+
+
+
+ //
+ // FormatCustomized
+ //
+ // Actions: Format the TimeSpan instance using the specified format.
+ //
+ internal static String FormatCustomized(TimeSpan value, String format, DateTimeFormatInfo dtfi) {
+
+ Contract.Assert(dtfi != null, "dtfi == null");
+
+ int day = (int)(value._ticks / TimeSpan.TicksPerDay);
+ long time = value._ticks % TimeSpan.TicksPerDay;
+
+ if (value._ticks < 0) {
+ day = -day;
+ time = -time;
+ }
+ int hours = (int)(time / TimeSpan.TicksPerHour % 24);
+ int minutes = (int)(time / TimeSpan.TicksPerMinute % 60);
+ int seconds = (int)(time / TimeSpan.TicksPerSecond % 60);
+ int fraction = (int)(time % TimeSpan.TicksPerSecond);
+
+ long tmp = 0;
+ int i = 0;
+ int tokenLen;
+ StringBuilder result = StringBuilderCache.Acquire();
+
+ while (i < format.Length) {
+ char ch = format[i];
+ int nextChar;
+ switch (ch) {
+ case 'h':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2)
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ DateTimeFormat.FormatDigits(result, hours, tokenLen);
+ break;
+ case 'm':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2)
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ DateTimeFormat.FormatDigits(result, minutes, tokenLen);
+ break;
+ case 's':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2)
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ DateTimeFormat.FormatDigits(result, seconds, tokenLen);
+ break;
+ case 'f':
+ //
+ // The fraction of a second in single-digit precision. The remaining digits are truncated.
+ //
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits)
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+
+ tmp = (long)fraction;
+ tmp /= (long)Math.Pow(10, DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
+ result.Append((tmp).ToString(DateTimeFormat.fixedNumberFormats[tokenLen - 1], CultureInfo.InvariantCulture));
+ break;
+ case 'F':
+ //
+ // Displays the most significant digit of the seconds fraction. Nothing is displayed if the digit is zero.
+ //
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits)
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+
+ tmp = (long)fraction;
+ tmp /= (long)Math.Pow(10, DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
+ int effectiveDigits = tokenLen;
+ while (effectiveDigits > 0) {
+ if (tmp % 10 == 0) {
+ tmp = tmp / 10;
+ effectiveDigits--;
+ }
+ else {
+ break;
+ }
+ }
+ if (effectiveDigits > 0) {
+ result.Append((tmp).ToString(DateTimeFormat.fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
+ }
+ break;
+ case 'd':
+ //
+ // tokenLen == 1 : Day as digits with no leading zero.
+ // tokenLen == 2+: Day as digits with leading zero for single-digit days.
+ //
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 8)
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ DateTimeFormat.FormatDigits(result, day, tokenLen, true);
+ break;
+ case '\'':
+ case '\"':
+ tokenLen = DateTimeFormat.ParseQuoteString(format, i, result);
+ break;
+ case '%':
+ // Optional format character.
+ // For example, format string "%d" will print day
+ // Most of the cases, "%" can be ignored.
+ nextChar = DateTimeFormat.ParseNextChar(format, i);
+ // nextChar will be -1 if we already reach the end of the format string.
+ // Besides, we will not allow "%%" appear in the pattern.
+ if (nextChar >= 0 && nextChar != (int)'%') {
+ result.Append(TimeSpanFormat.FormatCustomized(value, ((char)nextChar).ToString(), dtfi));
+ tokenLen = 2;
+ }
+ else
+ {
+ //
+ // This means that '%' is at the end of the format string or
+ // "%%" appears in the format string.
+ //
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ break;
+ case '\\':
+ // Escaped character. Can be used to insert character into the format string.
+ // For example, "\d" will insert the character 'd' into the string.
+ //
+ nextChar = DateTimeFormat.ParseNextChar(format, i);
+ if (nextChar >= 0)
+ {
+ result.Append(((char)nextChar));
+ tokenLen = 2;
+ }
+ else
+ {
+ //
+ // This means that '\' is at the end of the formatting string.
+ //
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ break;
+ default:
+ throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ i += tokenLen;
+ }
+ return StringBuilderCache.GetStringAndRelease(result);
+
+ }
+
+
+
+
+ internal struct FormatLiterals {
+ internal String Start {
+ get {
+ return literals[0];
+ }
+ }
+ internal String DayHourSep {
+ get {
+ return literals[1];
+ }
+ }
+ internal String HourMinuteSep {
+ get {
+ return literals[2];
+ }
+ }
+ internal String MinuteSecondSep {
+ get {
+ return literals[3];
+ }
+ }
+ internal String SecondFractionSep {
+ get {
+ return literals[4];
+ }
+ }
+ internal String End {
+ get {
+ return literals[5];
+ }
+ }
+ internal String AppCompatLiteral;
+ internal int dd;
+ internal int hh;
+ internal int mm;
+ internal int ss;
+ internal int ff;
+
+ private String[] literals;
+
+
+ /* factory method for static invariant FormatLiterals */
+ internal static FormatLiterals InitInvariant(bool isNegative) {
+ FormatLiterals x = new FormatLiterals();
+ x.literals = new String[6];
+ x.literals[0] = isNegative ? "-" : String.Empty;
+ x.literals[1] = ".";
+ x.literals[2] = ":";
+ x.literals[3] = ":";
+ x.literals[4] = ".";
+ x.literals[5] = String.Empty;
+ x.AppCompatLiteral = ":."; // MinuteSecondSep+SecondFractionSep;
+ x.dd = 2;
+ x.hh = 2;
+ x.mm = 2;
+ x.ss = 2;
+ x.ff = DateTimeFormat.MaxSecondsFractionDigits;
+ return x;
+ }
+
+ // For the "v1" TimeSpan localized patterns, the data is simply literal field separators with
+ // the constants guaranteed to include DHMSF ordered greatest to least significant.
+ // Once the data becomes more complex than this we will need to write a proper tokenizer for
+ // parsing and formatting
+ internal void Init(String format, bool useInvariantFieldLengths) {
+ literals = new String[6];
+ for (int i = 0; i < literals.Length; i++)
+ literals[i] = String.Empty;
+ dd = 0;
+ hh = 0;
+ mm = 0;
+ ss = 0;
+ ff = 0;
+
+ StringBuilder sb = StringBuilderCache.Acquire();
+ bool inQuote = false;
+ char quote = '\'';
+ int field = 0;
+
+ for (int i = 0; i < format.Length; i++) {
+ switch (format[i]) {
+ case '\'':
+ case '\"':
+ if (inQuote && (quote == format[i])) {
+ /* we were in a quote and found a matching exit quote, so we are outside a quote now */
+ Contract.Assert(field >= 0 && field <= 5, "field >= 0 && field <= 5");
+ if (field >= 0 && field <= 5) {
+ literals[field] = sb.ToString();
+ sb.Length = 0;
+ inQuote = false;
+ }
+ else {
+ return; // how did we get here?
+ }
+ }
+ else if (!inQuote) {
+ /* we are at the start of a new quote block */
+ quote = format[i];
+ inQuote = true;
+ }
+ else {
+ /* we were in a quote and saw the other type of quote character, so we are still in a quote */
+ }
+ break;
+ case '%':
+ Contract.Assert(false, "Unexpected special token '%', Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ goto default;
+ case '\\':
+ if (!inQuote) {
+ i++; /* skip next character that is escaped by this backslash or percent sign */
+ break;
+ }
+ goto default;
+ case 'd':
+ if (!inQuote) {
+ Contract.Assert((field == 0 && sb.Length == 0) || field == 1,
+ "field == 0 || field == 1, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 1; // DayHourSep
+ dd++;
+ }
+ break;
+ case 'h':
+ if (!inQuote) {
+ Contract.Assert((field == 1 && sb.Length == 0) || field == 2,
+ "field == 1 || field == 2, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 2; // HourMinuteSep
+ hh++;
+ }
+ break;
+ case 'm':
+ if (!inQuote) {
+ Contract.Assert((field == 2 && sb.Length == 0) || field == 3,
+ "field == 2 || field == 3, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 3; // MinuteSecondSep
+ mm++;
+ }
+ break;
+ case 's':
+ if (!inQuote) {
+ Contract.Assert((field == 3 && sb.Length == 0) || field == 4,
+ "field == 3 || field == 4, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 4; // SecondFractionSep
+ ss++;
+ }
+ break;
+ case 'f':
+ case 'F':
+ if (!inQuote) {
+ Contract.Assert((field == 4 && sb.Length == 0) || field == 5,
+ "field == 4 || field == 5, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ field = 5; // End
+ ff++;
+ }
+ break;
+ default:
+ sb.Append(format[i]);
+ break;
+ }
+ }
+
+ Contract.Assert(field == 5);
+ AppCompatLiteral = MinuteSecondSep + SecondFractionSep;
+
+ Contract.Assert(0 < dd && dd < 3, "0 < dd && dd < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ Contract.Assert(0 < hh && hh < 3, "0 < hh && hh < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ Contract.Assert(0 < mm && mm < 3, "0 < mm && mm < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ Contract.Assert(0 < ss && ss < 3, "0 < ss && ss < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+ Contract.Assert(0 < ff && ff < 8, "0 < ff && ff < 8, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
+
+ if (useInvariantFieldLengths) {
+ dd = 2;
+ hh = 2;
+ mm = 2;
+ ss = 2;
+ ff = DateTimeFormat.MaxSecondsFractionDigits;
+ }
+ else {
+ if (dd < 1 || dd > 2) dd = 2; // The DTFI property has a problem. let's try to make the best of the situation.
+ if (hh < 1 || hh > 2) hh = 2;
+ if (mm < 1 || mm > 2) mm = 2;
+ if (ss < 1 || ss > 2) ss = 2;
+ if (ff < 1 || ff > 7) ff = 7;
+ }
+ StringBuilderCache.Release(sb);
+ }
+ } //end of struct FormatLiterals
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/TimeSpanParse.cs b/src/mscorlib/src/System/Globalization/TimeSpanParse.cs
new file mode 100644
index 0000000000..e72e582a97
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/TimeSpanParse.cs
@@ -0,0 +1,1557 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+////////////////////////////////////////////////////////////////////////////
+//
+//
+// Purpose: This class is called by TimeSpan to parse a time interval string.
+//
+// Standard Format:
+// -=-=-=-=-=-=-=-
+// "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
+// Not culture sensitive. Default format (and null/empty format string) map to this format.
+//
+// "g": General format, short: [-][d':']h':'mm':'ss'.'FFFFFFF
+// Only print what's needed. Localized (if you want Invariant, pass in Invariant).
+// The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
+//
+// "G": General format, long: [-]d':'hh':'mm':'ss'.'fffffff
+// Always print days and 7 fractional digits. Localized (if you want Invariant, pass in Invariant).
+// The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
+//
+//
+// * "TryParseTimeSpan" is the main method for Parse/TryParse
+//
+// - TimeSpanTokenizer.GetNextToken() is used to split the input string into number and literal tokens.
+// - TimeSpanRawInfo.ProcessToken() adds the next token into the parsing intermediary state structure
+// - ProcessTerminalState() uses the fully initialized TimeSpanRawInfo to find a legal parse match.
+// The terminal states are attempted as follows:
+// foreach (+InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern) try
+// 1 number => d
+// 2 numbers => h:m
+// 3 numbers => h:m:s | d.h:m | h:m:.f
+// 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
+// 5 numbers => d.h:m:s.f
+//
+// Custom Format:
+// -=-=-=-=-=-=-=
+//
+// * "TryParseExactTimeSpan" is the main method for ParseExact/TryParseExact methods
+// * "TryParseExactMultipleTimeSpan" is the main method for ParseExact/TryparseExact
+// methods that take a String[] of formats
+//
+// - For single-letter formats "TryParseTimeSpan" is called (see above)
+// - For multi-letter formats "TryParseByFormat" is called
+// - TryParseByFormat uses helper methods (ParseExactLiteral, ParseExactDigits, etc)
+// which drive the underlying TimeSpanTokenizer. However, unlike standard formatting which
+// operates on whole-tokens, ParseExact operates at the character-level. As such,
+// TimeSpanTokenizer.NextChar and TimeSpanTokenizer.BackOne() are called directly.
+//
+////////////////////////////////////////////////////////////////////////////
+namespace System.Globalization {
+ using System.Text;
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+
+ internal static class TimeSpanParse {
+ // ---- SECTION: members for internal support ---------*
+ internal static void ValidateStyles(TimeSpanStyles style, String parameterName) {
+ if (style != TimeSpanStyles.None && style != TimeSpanStyles.AssumeNegative)
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidTimeSpanStyles"), parameterName);
+ }
+
+ internal const int unlimitedDigits = -1;
+ internal const int maxFractionDigits = 7;
+
+ internal const int maxDays = 10675199;
+ internal const int maxHours = 23;
+ internal const int maxMinutes = 59;
+ internal const int maxSeconds = 59;
+ internal const int maxFraction = 9999999;
+
+ #region InternalSupport
+ enum TimeSpanThrowStyle {
+ None = 0,
+ All = 1,
+ }
+
+ private enum ParseFailureKind {
+ None = 0,
+ ArgumentNull = 1,
+ Format = 2,
+ FormatWithParameter = 3,
+ Overflow = 4,
+ }
+
+ [Flags]
+ enum TimeSpanStandardStyles { // Standard Format Styles
+ None = 0x00000000,
+ Invariant = 0x00000001, //Allow Invariant Culture
+ Localized = 0x00000002, //Allow Localized Culture
+ RequireFull = 0x00000004, //Require the input to be in DHMSF format
+ Any = Invariant | Localized,
+ }
+
+ // TimeSpan Token Types
+ private enum TTT {
+ None = 0, // None of the TimeSpanToken fields are set
+ End = 1, // '\0'
+ Num = 2, // Number
+ Sep = 3, // literal
+ NumOverflow = 4, // Number that overflowed
+ }
+
+ private static readonly TimeSpanToken zero = new TimeSpanToken(0);
+ struct TimeSpanToken {
+ internal TTT ttt;
+ internal int num; // Store the number that we are parsing (if any)
+ internal int zeroes; // Store the number of leading zeroes (if any)
+ internal String sep; // Store the literal that we are parsing (if any)
+
+ public TimeSpanToken(int number) {
+ ttt = TTT.Num;
+ num = number;
+ zeroes = 0;
+ sep = null;
+ }
+
+ public TimeSpanToken(int leadingZeroes, int number) {
+ ttt = TTT.Num;
+ num = number;
+ zeroes = leadingZeroes;
+ sep = null;
+ }
+
+ public bool IsInvalidNumber(int maxValue, int maxPrecision) {
+ Contract.Assert(ttt == TTT.Num);
+ Contract.Assert(num > -1);
+ Contract.Assert(maxValue > 0);
+ Contract.Assert(maxPrecision == maxFractionDigits || maxPrecision == unlimitedDigits);
+
+ if (num > maxValue)
+ return true;
+ if (maxPrecision == unlimitedDigits)
+ return false; // all validation past this point applies only to fields with precision limits
+ if (zeroes > maxPrecision)
+ return true;
+ if (num == 0 || zeroes == 0)
+ return false;
+
+ // num > 0 && zeroes > 0 && num <= maxValue && zeroes <= maxPrecision
+ return (num >= (maxValue/(long)Math.Pow(10, zeroes-1)));
+ }
+ }
+
+ //
+ // TimeSpanTokenizer
+ //
+ // Actions: TimeSpanTokenizer.GetNextToken() returns the next token in the input string.
+ //
+ struct TimeSpanTokenizer {
+ private int m_pos;
+ private String m_value;
+
+ internal void Init(String input) {
+ Init(input, 0);
+ }
+ internal void Init(String input, int startPosition) {
+ m_pos = startPosition;
+ m_value = input;
+ }
+ // used by the parsing routines that operate on standard-formats
+ internal TimeSpanToken GetNextToken() {
+ Contract.Assert(m_pos > -1);
+
+ TimeSpanToken tok = new TimeSpanToken();
+ char ch = CurrentChar;
+
+ if (ch == (char)0) {
+ tok.ttt = TTT.End;
+ return tok;
+ }
+
+ if (ch >= '0' && ch <= '9') {
+ tok.ttt = TTT.Num;
+ tok.num = 0;
+ tok.zeroes = 0;
+ do {
+ if ((tok.num & 0xF0000000) != 0) {
+ tok.ttt = TTT.NumOverflow;
+ return tok;
+ }
+ tok.num = tok.num * 10 + ch - '0';
+ if (tok.num == 0) tok.zeroes++;
+ if (tok.num < 0) {
+ tok.ttt = TTT.NumOverflow;
+ return tok;
+ }
+ ch = NextChar;
+ } while (ch >= '0' && ch <= '9');
+ return tok;
+ }
+ else {
+ tok.ttt = TTT.Sep;
+ int startIndex = m_pos;
+ int length = 0;
+
+ while (ch != (char)0 && (ch < '0' || '9' < ch)) {
+ ch = NextChar;
+ length++;
+ }
+ tok.sep = m_value.Substring(startIndex, length);
+ return tok;
+ }
+ }
+
+ internal Boolean EOL {
+ get {
+ return m_pos >= (m_value.Length-1);
+ }
+ }
+ // BackOne, NextChar, CurrentChar - used by ParseExact (ParseByFormat) to operate
+ // on custom-formats where exact character-by-character control is allowed
+ internal void BackOne() {
+ if (m_pos > 0) --m_pos;
+ }
+
+ internal char NextChar {
+ get {
+ m_pos++;
+ return CurrentChar;
+ }
+ }
+ internal char CurrentChar {
+ get {
+ if (m_pos > -1 && m_pos < m_value.Length) {
+ return m_value[m_pos];
+ }
+ else {
+ return (char) 0;
+ }
+ }
+ }
+ }
+
+
+
+ // This stores intermediary parsing state for the standard formats
+ struct TimeSpanRawInfo {
+ internal TimeSpanFormat.FormatLiterals PositiveInvariant {
+ get {
+ return TimeSpanFormat.PositiveInvariantFormatLiterals;
+ }
+ }
+ internal TimeSpanFormat.FormatLiterals NegativeInvariant {
+ get {
+ return TimeSpanFormat.NegativeInvariantFormatLiterals;
+ }
+ }
+
+ internal TimeSpanFormat.FormatLiterals PositiveLocalized {
+ get {
+ if (!m_posLocInit) {
+ m_posLoc = new TimeSpanFormat.FormatLiterals();
+ m_posLoc.Init(m_fullPosPattern, false);
+ m_posLocInit = true;
+ }
+ return m_posLoc;
+ }
+ }
+ internal TimeSpanFormat.FormatLiterals NegativeLocalized {
+ get {
+ if (!m_negLocInit) {
+ m_negLoc = new TimeSpanFormat.FormatLiterals();
+ m_negLoc.Init(m_fullNegPattern, false);
+ m_negLocInit = true;
+ }
+ return m_negLoc;
+ }
+ }
+
+ internal Boolean FullAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) {
+ return SepCount == 5
+ && NumCount == 4
+ && pattern.Start == literals[0]
+ && pattern.DayHourSep == literals[1]
+ && pattern.HourMinuteSep == literals[2]
+ && pattern.AppCompatLiteral == literals[3]
+ && pattern.End == literals[4];
+ }
+
+ internal Boolean PartialAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) {
+ return SepCount == 4
+ && NumCount == 3
+ && pattern.Start == literals[0]
+ && pattern.HourMinuteSep == literals[1]
+ && pattern.AppCompatLiteral == literals[2]
+ && pattern.End == literals[3];
+ }
+ // DHMSF (all values matched)
+ internal Boolean FullMatch(TimeSpanFormat.FormatLiterals pattern) {
+ return SepCount == MaxLiteralTokens
+ && NumCount == MaxNumericTokens
+ && pattern.Start == literals[0]
+ && pattern.DayHourSep == literals[1]
+ && pattern.HourMinuteSep == literals[2]
+ && pattern.MinuteSecondSep == literals[3]
+ && pattern.SecondFractionSep == literals[4]
+ && pattern.End == literals[5];
+ }
+ // D (no hours, minutes, seconds, or fractions)
+ internal Boolean FullDMatch(TimeSpanFormat.FormatLiterals pattern) {
+ return SepCount == 2
+ && NumCount == 1
+ && pattern.Start == literals[0]
+ && pattern.End == literals[1];
+ }
+ // HM (no days, seconds, or fractions)
+ internal Boolean FullHMMatch(TimeSpanFormat.FormatLiterals pattern) {
+ return SepCount == 3
+ && NumCount == 2
+ && pattern.Start == literals[0]
+ && pattern.HourMinuteSep == literals[1]
+ && pattern.End == literals[2];
+ }
+ // DHM (no seconds or fraction)
+ internal Boolean FullDHMMatch(TimeSpanFormat.FormatLiterals pattern) {
+ return SepCount == 4
+ && NumCount == 3
+ && pattern.Start == literals[0]
+ && pattern.DayHourSep == literals[1]
+ && pattern.HourMinuteSep == literals[2]
+ && pattern.End == literals[3];
+
+ }
+ // HMS (no days or fraction)
+ internal Boolean FullHMSMatch(TimeSpanFormat.FormatLiterals pattern) {
+ return SepCount == 4
+ && NumCount == 3
+ && pattern.Start == literals[0]
+ && pattern.HourMinuteSep == literals[1]
+ && pattern.MinuteSecondSep == literals[2]
+ && pattern.End == literals[3];
+ }
+ // DHMS (no fraction)
+ internal Boolean FullDHMSMatch(TimeSpanFormat.FormatLiterals pattern) {
+ return SepCount == 5
+ && NumCount == 4
+ && pattern.Start == literals[0]
+ && pattern.DayHourSep == literals[1]
+ && pattern.HourMinuteSep == literals[2]
+ && pattern.MinuteSecondSep == literals[3]
+ && pattern.End == literals[4];
+ }
+ // HMSF (no days)
+ internal Boolean FullHMSFMatch(TimeSpanFormat.FormatLiterals pattern) {
+ return SepCount == 5
+ && NumCount == 4
+ && pattern.Start == literals[0]
+ && pattern.HourMinuteSep == literals[1]
+ && pattern.MinuteSecondSep == literals[2]
+ && pattern.SecondFractionSep == literals[3]
+ && pattern.End == literals[4];
+ }
+
+ internal TTT lastSeenTTT;
+ internal int tokenCount;
+ internal int SepCount;
+ internal int NumCount;
+ internal String[] literals;
+ internal TimeSpanToken[] numbers; // raw numbers
+
+ private TimeSpanFormat.FormatLiterals m_posLoc;
+ private TimeSpanFormat.FormatLiterals m_negLoc;
+ private Boolean m_posLocInit;
+ private Boolean m_negLocInit;
+ private String m_fullPosPattern;
+ private String m_fullNegPattern;
+
+ private const int MaxTokens = 11;
+ private const int MaxLiteralTokens = 6;
+ private const int MaxNumericTokens = 5;
+
+ internal void Init(DateTimeFormatInfo dtfi) {
+ Contract.Assert(dtfi != null);
+
+ lastSeenTTT = TTT.None;
+ tokenCount = 0;
+ SepCount = 0;
+ NumCount = 0;
+
+ literals = new String[MaxLiteralTokens];
+ numbers = new TimeSpanToken[MaxNumericTokens];
+
+ m_fullPosPattern = dtfi.FullTimeSpanPositivePattern;
+ m_fullNegPattern = dtfi.FullTimeSpanNegativePattern;
+ m_posLocInit = false;
+ m_negLocInit = false;
+ }
+
+ internal Boolean ProcessToken(ref TimeSpanToken tok, ref TimeSpanResult result) {
+ if (tok.ttt == TTT.NumOverflow) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge", null);
+ return false;
+ }
+ if (tok.ttt != TTT.Sep && tok.ttt != TTT.Num) {
+ // Some unknown token or a repeat token type in the input
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null);
+ return false;
+ }
+
+ switch (tok.ttt) {
+ case TTT.Sep:
+ if (!AddSep(tok.sep, ref result)) return false;
+ break;
+ case TTT.Num:
+ if (tokenCount == 0) {
+ if (!AddSep(String.Empty, ref result)) return false;
+ }
+ if (!AddNum(tok, ref result)) return false;
+ break;
+ default:
+ break;
+ }
+
+ lastSeenTTT = tok.ttt;
+ Contract.Assert(tokenCount == (SepCount + NumCount), "tokenCount == (SepCount + NumCount)");
+ return true;
+ }
+
+ private bool AddSep(String sep, ref TimeSpanResult result) {
+ if (SepCount >= MaxLiteralTokens || tokenCount >= MaxTokens) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null);
+ return false;
+ }
+ literals[SepCount++] = sep;
+ tokenCount++;
+ return true;
+ }
+ private bool AddNum(TimeSpanToken num, ref TimeSpanResult result) {
+ if (NumCount >= MaxNumericTokens || tokenCount >= MaxTokens) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan", null);
+ return false;
+ }
+ numbers[NumCount++] = num;
+ tokenCount++;
+ return true;
+ }
+ }
+
+ // This will store the result of the parsing. And it will eventually be used to construct a TimeSpan instance.
+ struct TimeSpanResult {
+ internal TimeSpan parsedTimeSpan;
+ internal TimeSpanThrowStyle throwStyle;
+
+ internal ParseFailureKind m_failure;
+ internal string m_failureMessageID;
+ internal object m_failureMessageFormatArgument;
+ internal string m_failureArgumentName;
+
+ internal void Init(TimeSpanThrowStyle canThrow) {
+ parsedTimeSpan = default(TimeSpan);
+ throwStyle = canThrow;
+ }
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID) {
+ SetFailure(failure, failureMessageID, null, null);
+ }
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument) {
+ SetFailure(failure, failureMessageID, failureMessageFormatArgument, null);
+ }
+ internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument,
+ string failureArgumentName) {
+ m_failure = failure;
+ m_failureMessageID = failureMessageID;
+ m_failureMessageFormatArgument = failureMessageFormatArgument;
+ m_failureArgumentName = failureArgumentName;
+ if (throwStyle != TimeSpanThrowStyle.None) {
+ throw GetTimeSpanParseException();
+ }
+ }
+
+ internal Exception GetTimeSpanParseException() {
+ switch (m_failure) {
+ case ParseFailureKind.ArgumentNull:
+ return new ArgumentNullException(m_failureArgumentName, Environment.GetResourceString(m_failureMessageID));
+
+ case ParseFailureKind.FormatWithParameter:
+ return new FormatException(Environment.GetResourceString(m_failureMessageID, m_failureMessageFormatArgument));
+
+ case ParseFailureKind.Format:
+ return new FormatException(Environment.GetResourceString(m_failureMessageID));
+
+ case ParseFailureKind.Overflow:
+ return new OverflowException(Environment.GetResourceString(m_failureMessageID));
+
+ default:
+ Contract.Assert(false, "Unknown TimeSpanParseFailure: " + m_failure);
+ return new FormatException(Environment.GetResourceString("Format_InvalidString"));
+ }
+ }
+ }
+
+ static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanToken hours, TimeSpanToken minutes, TimeSpanToken seconds, TimeSpanToken fraction, out long result) {
+ if (days.IsInvalidNumber(maxDays, unlimitedDigits)
+ || hours.IsInvalidNumber(maxHours, unlimitedDigits)
+ || minutes.IsInvalidNumber(maxMinutes, unlimitedDigits)
+ || seconds.IsInvalidNumber(maxSeconds, unlimitedDigits)
+ || fraction.IsInvalidNumber(maxFraction, maxFractionDigits)) {
+ result = 0;
+ return false;
+ }
+
+ Int64 ticks = ((Int64)days.num * 3600 * 24 + (Int64)hours.num * 3600 + (Int64)minutes.num * 60 + seconds.num) * 1000;
+ if (ticks > TimeSpan.MaxMilliSeconds || ticks < TimeSpan.MinMilliSeconds) {
+ result = 0;
+ return false;
+ }
+
+ // Normalize the fraction component
+ //
+ // string representation => (zeroes,num) => resultant fraction ticks
+ // --------------------- ------------ ------------------------
+ // ".9999999" => (0,9999999) => 9,999,999 ticks (same as constant maxFraction)
+ // ".1" => (0,1) => 1,000,000 ticks
+ // ".01" => (1,1) => 100,000 ticks
+ // ".001" => (2,1) => 10,000 ticks
+ long f = fraction.num;
+ if (f != 0) {
+ long lowerLimit = TimeSpan.TicksPerTenthSecond;
+ if (fraction.zeroes > 0) {
+ long divisor = (long)Math.Pow(10, fraction.zeroes);
+ lowerLimit = lowerLimit / divisor;
+ }
+ while (f < lowerLimit) {
+ f *= 10;
+ }
+ }
+ result = ((long)ticks * TimeSpan.TicksPerMillisecond) + f;
+ if (positive && result < 0) {
+ result = 0;
+ return false;
+ }
+ return true;
+ }
+ #endregion
+
+
+ // ---- SECTION: internal static methods called by System.TimeSpan ---------*
+ //
+ // [Try]Parse, [Try]ParseExact, and [Try]ParseExactMultiple
+ //
+ // Actions: Main methods called from TimeSpan.Parse
+ #region ParseMethods
+ internal static TimeSpan Parse(String input, IFormatProvider formatProvider) {
+ TimeSpanResult parseResult = new TimeSpanResult();
+ parseResult.Init(TimeSpanThrowStyle.All);
+
+ if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult)) {
+ return parseResult.parsedTimeSpan;
+ }
+ else {
+ throw parseResult.GetTimeSpanParseException();
+ }
+ }
+ internal static Boolean TryParse(String input, IFormatProvider formatProvider, out TimeSpan result) {
+ TimeSpanResult parseResult = new TimeSpanResult();
+ parseResult.Init(TimeSpanThrowStyle.None);
+
+ if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult)) {
+ result = parseResult.parsedTimeSpan;
+ return true;
+ }
+ else {
+ result = default(TimeSpan);
+ return false;
+ }
+ }
+ internal static TimeSpan ParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles) {
+ TimeSpanResult parseResult = new TimeSpanResult();
+ parseResult.Init(TimeSpanThrowStyle.All);
+
+ if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult)) {
+ return parseResult.parsedTimeSpan;
+ }
+ else {
+ throw parseResult.GetTimeSpanParseException();
+ }
+ }
+ internal static Boolean TryParseExact(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) {
+ TimeSpanResult parseResult = new TimeSpanResult();
+ parseResult.Init(TimeSpanThrowStyle.None);
+
+ if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult)) {
+ result = parseResult.parsedTimeSpan;
+ return true;
+ }
+ else {
+ result = default(TimeSpan);
+ return false;
+ }
+ }
+ internal static TimeSpan ParseExactMultiple(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles) {
+ TimeSpanResult parseResult = new TimeSpanResult();
+ parseResult.Init(TimeSpanThrowStyle.All);
+
+ if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult)) {
+ return parseResult.parsedTimeSpan;
+ }
+ else {
+ throw parseResult.GetTimeSpanParseException();
+ }
+ }
+ internal static Boolean TryParseExactMultiple(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) {
+ TimeSpanResult parseResult = new TimeSpanResult();
+ parseResult.Init(TimeSpanThrowStyle.None);
+
+ if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult)) {
+ result = parseResult.parsedTimeSpan;
+ return true;
+ }
+ else {
+ result = default(TimeSpan);
+ return false;
+ }
+ }
+ #endregion
+
+
+ // ---- SECTION: private static methods that do the actual work ---------*
+ #region TryParseTimeSpan
+ //
+ // TryParseTimeSpan
+ //
+ // Actions: Common private Parse method called by both Parse and TryParse
+ //
+ private static Boolean TryParseTimeSpan(String input, TimeSpanStandardStyles style, IFormatProvider formatProvider, ref TimeSpanResult result) {
+ if (input == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
+ return false;
+ }
+
+ input = input.Trim();
+ if (input == String.Empty) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+ TimeSpanTokenizer tokenizer = new TimeSpanTokenizer();
+ tokenizer.Init(input);
+
+ TimeSpanRawInfo raw = new TimeSpanRawInfo();
+ raw.Init(DateTimeFormatInfo.GetInstance(formatProvider));
+
+ TimeSpanToken tok = tokenizer.GetNextToken();
+
+ /* The following loop will break out when we reach the end of the str or
+ * when we can determine that the input is invalid. */
+ while (tok.ttt != TTT.End) {
+ if (!raw.ProcessToken(ref tok, ref result)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ tok = tokenizer.GetNextToken();
+ }
+ if (!tokenizer.EOL) {
+ // embedded nulls in the input string
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ if (!ProcessTerminalState(ref raw, style, ref result)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ return true;
+ }
+
+
+
+ //
+ // ProcessTerminalState
+ //
+ // Actions: Validate the terminal state of a standard format parse.
+ // Sets result.parsedTimeSpan on success.
+ //
+ // Calculates the resultant TimeSpan from the TimeSpanRawInfo
+ //
+ // try => +InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern
+ // 1) Verify Start matches
+ // 2) Verify End matches
+ // 3) 1 number => d
+ // 2 numbers => h:m
+ // 3 numbers => h:m:s | d.h:m | h:m:.f
+ // 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
+ // 5 numbers => d.h:m:s.f
+ private static Boolean ProcessTerminalState(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
+ if (raw.lastSeenTTT == TTT.Num) {
+ TimeSpanToken tok = new TimeSpanToken();
+ tok.ttt = TTT.Sep;
+ tok.sep = String.Empty;
+ if (!raw.ProcessToken(ref tok, ref result)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ }
+
+ switch (raw.NumCount) {
+ case 1:
+ return ProcessTerminal_D(ref raw, style, ref result);
+ case 2:
+ return ProcessTerminal_HM(ref raw, style, ref result);
+ case 3:
+ return ProcessTerminal_HM_S_D(ref raw, style, ref result);
+ case 4:
+ return ProcessTerminal_HMS_F_D(ref raw, style, ref result);
+ case 5:
+ return ProcessTerminal_DHMSF(ref raw, style, ref result);
+ default:
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ }
+
+ //
+ // ProcessTerminal_DHMSF
+ //
+ // Actions: Validate the 5-number "Days.Hours:Minutes:Seconds.Fraction" terminal case.
+ // Sets result.parsedTimeSpan on success.
+ //
+ private static Boolean ProcessTerminal_DHMSF(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
+ if (raw.SepCount != 6 || raw.NumCount != 5) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+ bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
+ bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
+
+ bool positive = false;
+ bool match = false;
+
+ if (inv) {
+ if (raw.FullMatch(raw.PositiveInvariant)) {
+ match = true;
+ positive = true;
+ }
+ if (!match && raw.FullMatch(raw.NegativeInvariant)) {
+ match = true;
+ positive = false;
+ }
+ }
+ if (loc) {
+ if (!match && raw.FullMatch(raw.PositiveLocalized)) {
+ match = true;
+ positive = true;
+ }
+ if (!match && raw.FullMatch(raw.NegativeLocalized)) {
+ match = true;
+ positive = false;
+ }
+ }
+ long ticks;
+ if (match) {
+ if (!TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], raw.numbers[4], out ticks)) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ if (!positive) {
+ ticks = -ticks;
+ if (ticks > 0) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ }
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+ //
+ // ProcessTerminal_HMS_F_D
+ //
+ // Actions: Validate the ambiguous 4-number "Hours:Minutes:Seconds.Fraction", "Days.Hours:Minutes:Seconds", or "Days.Hours:Minutes:.Fraction" terminal case.
+ // Sets result.parsedTimeSpan on success.
+ //
+ private static Boolean ProcessTerminal_HMS_F_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
+ if (raw.SepCount != 5 || raw.NumCount != 4 || (style & TimeSpanStandardStyles.RequireFull) != 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+ bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
+ bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
+
+ long ticks = 0;
+ bool positive = false;
+ bool match = false;
+ bool overflow = false;
+
+ if (inv) {
+ if (raw.FullHMSFMatch(raw.PositiveInvariant)) {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullDHMSMatch(raw.PositiveInvariant)) {
+ positive = true;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullAppCompatMatch(raw.PositiveInvariant)) {
+ positive = true;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullHMSFMatch(raw.NegativeInvariant)) {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullDHMSMatch(raw.NegativeInvariant)) {
+ positive = false;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullAppCompatMatch(raw.NegativeInvariant)) {
+ positive = false;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
+ overflow = overflow || !match;
+ }
+ }
+ if (loc) {
+ if (!match && raw.FullHMSFMatch(raw.PositiveLocalized)) {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullDHMSMatch(raw.PositiveLocalized)) {
+ positive = true;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullAppCompatMatch(raw.PositiveLocalized)) {
+ positive = true;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullHMSFMatch(raw.NegativeLocalized)) {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullDHMSMatch(raw.NegativeLocalized)) {
+ positive = false;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], raw.numbers[3], zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullAppCompatMatch(raw.NegativeLocalized)) {
+ positive = false;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, raw.numbers[3], out ticks);
+ overflow = overflow || !match;
+ }
+ }
+
+ if (match) {
+ if (!positive) {
+ ticks = -ticks;
+ if (ticks > 0) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ }
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ if (overflow) {
+ // we found at least one literal pattern match but the numbers just didn't fit
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ else {
+ // we couldn't find a thing
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ }
+
+ //
+ // ProcessTerminal_HM_S_D
+ //
+ // Actions: Validate the ambiguous 3-number "Hours:Minutes:Seconds", "Days.Hours:Minutes", or "Hours:Minutes:.Fraction" terminal case
+ //
+ private static Boolean ProcessTerminal_HM_S_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
+ if (raw.SepCount != 4 || raw.NumCount != 3 || (style & TimeSpanStandardStyles.RequireFull) != 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+ bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
+ bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
+
+ bool positive = false;
+ bool match = false;
+ bool overflow = false;
+
+ long ticks = 0;
+
+ if (inv) {
+ if (raw.FullHMSMatch(raw.PositiveInvariant)) {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullDHMMatch(raw.PositiveInvariant)) {
+ positive = true;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.PartialAppCompatMatch(raw.PositiveInvariant)) {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullHMSMatch(raw.NegativeInvariant)) {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullDHMMatch(raw.NegativeInvariant)) {
+ positive = false;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.PartialAppCompatMatch(raw.NegativeInvariant)) {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
+ overflow = overflow || !match;
+ }
+ }
+ if (loc) {
+ if (!match && raw.FullHMSMatch(raw.PositiveLocalized)) {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullDHMMatch(raw.PositiveLocalized)) {
+ positive = true;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.PartialAppCompatMatch(raw.PositiveLocalized)) {
+ positive = true;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullHMSMatch(raw.NegativeLocalized)) {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.FullDHMMatch(raw.NegativeLocalized)) {
+ positive = false;
+ match = TryTimeToTicks(positive, raw.numbers[0], raw.numbers[1], raw.numbers[2], zero, zero, out ticks);
+ overflow = overflow || !match;
+ }
+ if (!match && raw.PartialAppCompatMatch(raw.NegativeLocalized)) {
+ positive = false;
+ match = TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, raw.numbers[2], out ticks);
+ overflow = overflow || !match;
+ }
+ }
+
+ if (match) {
+ if (!positive) {
+ ticks = -ticks;
+ if (ticks > 0) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ }
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ if (overflow) {
+ // we found at least one literal pattern match but the numbers just didn't fit
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ else {
+ // we couldn't find a thing
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ }
+
+ //
+ // ProcessTerminal_HM
+ //
+ // Actions: Validate the 2-number "Hours:Minutes" terminal case
+ //
+ private static Boolean ProcessTerminal_HM(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
+ if (raw.SepCount != 3 || raw.NumCount != 2 || (style & TimeSpanStandardStyles.RequireFull) != 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+ bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
+ bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
+
+ bool positive = false;
+ bool match = false;
+
+ if (inv) {
+ if (raw.FullHMMatch(raw.PositiveInvariant)) {
+ match = true;
+ positive = true;
+ }
+ if (!match && raw.FullHMMatch(raw.NegativeInvariant)) {
+ match = true;
+ positive = false;
+ }
+ }
+ if (loc) {
+ if (!match && raw.FullHMMatch(raw.PositiveLocalized)) {
+ match = true;
+ positive = true;
+ }
+ if (!match && raw.FullHMMatch(raw.NegativeLocalized)) {
+ match = true;
+ positive = false;
+ }
+ }
+
+ long ticks = 0;
+ if (match) {
+ if (!TryTimeToTicks(positive, zero, raw.numbers[0], raw.numbers[1], zero, zero, out ticks)) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ if (!positive) {
+ ticks = -ticks;
+ if (ticks > 0) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ }
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+
+ //
+ // ProcessTerminal_D
+ //
+ // Actions: Validate the 1-number "Days" terminal case
+ //
+ private static Boolean ProcessTerminal_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result) {
+ if (raw.SepCount != 2 || raw.NumCount != 1 || (style & TimeSpanStandardStyles.RequireFull) != 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+ bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
+ bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
+
+ bool positive = false;
+ bool match = false;
+
+ if (inv) {
+ if (raw.FullDMatch(raw.PositiveInvariant)) {
+ match = true;
+ positive = true;
+ }
+ if (!match && raw.FullDMatch(raw.NegativeInvariant)) {
+ match = true;
+ positive = false;
+ }
+ }
+ if (loc) {
+ if (!match && raw.FullDMatch(raw.PositiveLocalized)) {
+ match = true;
+ positive = true;
+ }
+ if (!match && raw.FullDMatch(raw.NegativeLocalized)) {
+ match = true;
+ positive = false;
+ }
+ }
+
+ long ticks = 0;
+ if (match) {
+ if (!TryTimeToTicks(positive, raw.numbers[0], zero, zero, zero, zero, out ticks)) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ if (!positive) {
+ ticks = -ticks;
+ if (ticks > 0) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ }
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ #endregion
+
+ #region TryParseExactTimeSpan
+ //
+ // TryParseExactTimeSpan
+ //
+ // Actions: Common private ParseExact method called by both ParseExact and TryParseExact
+ //
+ private static Boolean TryParseExactTimeSpan(String input, String format, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result) {
+ if (input == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
+ return false;
+ }
+ if (format == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "format");
+ return false;
+ }
+ if (format.Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
+ return false;
+ }
+
+ if (format.Length == 1) {
+ TimeSpanStandardStyles style = TimeSpanStandardStyles.None;
+
+ if (format[0] == 'c' || format[0] == 't' || format[0] == 'T') {
+ // fast path for legacy style TimeSpan formats.
+ return TryParseTimeSpanConstant(input, ref result);
+ }
+ else if (format[0] == 'g') {
+ style = TimeSpanStandardStyles.Localized;
+ }
+ else if (format[0] == 'G') {
+ style = TimeSpanStandardStyles.Localized | TimeSpanStandardStyles.RequireFull;
+ }
+ else {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
+ return false;
+ }
+ return TryParseTimeSpan(input, style, formatProvider, ref result);
+ }
+
+ return TryParseByFormat(input, format, styles, ref result);
+ }
+
+ //
+ // TryParseByFormat
+ //
+ // Actions: Parse the TimeSpan instance using the specified format. Used by TryParseExactTimeSpan.
+ //
+ private static Boolean TryParseByFormat(String input, String format, TimeSpanStyles styles, ref TimeSpanResult result) {
+ Contract.Assert(input != null, "input != null");
+ Contract.Assert(format != null, "format != null");
+
+ bool seenDD = false; // already processed days?
+ bool seenHH = false; // already processed hours?
+ bool seenMM = false; // already processed minutes?
+ bool seenSS = false; // already processed seconds?
+ bool seenFF = false; // already processed fraction?
+ int dd = 0; // parsed days
+ int hh = 0; // parsed hours
+ int mm = 0; // parsed minutes
+ int ss = 0; // parsed seconds
+ int leadingZeroes = 0; // number of leading zeroes in the parsed fraction
+ int ff = 0; // parsed fraction
+ int i = 0; // format string position
+ int tokenLen = 0; // length of current format token, used to update index 'i'
+
+ TimeSpanTokenizer tokenizer = new TimeSpanTokenizer();
+ tokenizer.Init(input, -1);
+
+ while (i < format.Length) {
+ char ch = format[i];
+ int nextFormatChar;
+ switch (ch) {
+ case 'h':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2 || seenHH || !ParseExactDigits(ref tokenizer, tokenLen, out hh)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ seenHH = true;
+ break;
+ case 'm':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2 || seenMM || !ParseExactDigits(ref tokenizer, tokenLen, out mm)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ seenMM = true;
+ break;
+ case 's':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > 2 || seenSS || !ParseExactDigits(ref tokenizer, tokenLen, out ss)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ seenSS = true;
+ break;
+ case 'f':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF || !ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ seenFF = true;
+ break;
+ case 'F':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF) {
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff);
+ seenFF = true;
+ break;
+ case 'd':
+ tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
+ int tmp = 0;
+ if (tokenLen > 8 || seenDD || !ParseExactDigits(ref tokenizer, (tokenLen<2) ? 1 : tokenLen, (tokenLen<2) ? 8 : tokenLen, out tmp, out dd)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ seenDD = true;
+ break;
+ case '\'':
+ case '\"':
+ StringBuilder enquotedString = new StringBuilder();
+ if (!DateTimeParse.TryParseQuoteString(format, i, enquotedString, out tokenLen)) {
+ result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadQuote", ch);
+ return false;
+ }
+ if (!ParseExactLiteral(ref tokenizer, enquotedString)) {
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ break;
+ case '%':
+ // Optional format character.
+ // For example, format string "%d" will print day
+ // Most of the cases, "%" can be ignored.
+ nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
+ // nextFormatChar will be -1 if we already reach the end of the format string.
+ // Besides, we will not allow "%%" appear in the pattern.
+ if (nextFormatChar >= 0 && nextFormatChar != (int)'%') {
+ tokenLen = 1; // skip the '%' and process the format character
+ break;
+ }
+ else {
+ // This means that '%' is at the end of the format string or
+ // "%%" appears in the format string.
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ case '\\':
+ // Escaped character. Can be used to insert character into the format string.
+ // For example, "\d" will insert the character 'd' into the string.
+ //
+ nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
+ if (nextFormatChar >= 0 && tokenizer.NextChar == (char)nextFormatChar) {
+ tokenLen = 2;
+ }
+ else {
+ // This means that '\' is at the end of the format string or the literal match failed.
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ break;
+ default:
+ result.SetFailure(ParseFailureKind.Format, "Format_InvalidString");
+ return false;
+ }
+ i += tokenLen;
+ }
+
+
+ if (!tokenizer.EOL) {
+ // the custom format didn't consume the entire input
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+ long ticks = 0;
+ bool positive = (styles & TimeSpanStyles.AssumeNegative) == 0;
+ if (TryTimeToTicks(positive, new TimeSpanToken(dd),
+ new TimeSpanToken(hh),
+ new TimeSpanToken(mm),
+ new TimeSpanToken(ss),
+ new TimeSpanToken(leadingZeroes, ff),
+ out ticks)) {
+ if (!positive) ticks = -ticks;
+ result.parsedTimeSpan._ticks = ticks;
+ return true;
+ }
+ else {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+
+ }
+ }
+
+ private static Boolean ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, out int result) {
+ result = 0;
+ int zeroes = 0;
+ int maxDigitLength = (minDigitLength == 1) ? 2 : minDigitLength;
+ return ParseExactDigits(ref tokenizer, minDigitLength, maxDigitLength, out zeroes, out result);
+ }
+ private static Boolean ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, int maxDigitLength, out int zeroes, out int result) {
+ result = 0;
+ zeroes = 0;
+
+ int tokenLength = 0;
+ while (tokenLength < maxDigitLength) {
+ char ch = tokenizer.NextChar;
+ if (ch < '0' || ch > '9') {
+ tokenizer.BackOne();
+ break;
+ }
+ result = result * 10 + (ch - '0');
+ if (result == 0) zeroes++;
+ tokenLength++;
+ }
+ return (tokenLength >= minDigitLength);
+ }
+ private static Boolean ParseExactLiteral(ref TimeSpanTokenizer tokenizer, StringBuilder enquotedString) {
+ for (int i = 0; i < enquotedString.Length; i++) {
+ if (enquotedString[i] != tokenizer.NextChar)
+ return false;
+ }
+ return true;
+ }
+ #endregion
+
+ #region TryParseTimeSpanConstant
+ //
+ // TryParseTimeSpanConstant
+ //
+ // Actions: Parses the "c" (constant) format. This code is 100% identical to the non-globalized v1.0-v3.5 TimeSpan.Parse() routine
+ // and exists for performance/appcompat with legacy callers who cannot move onto the globalized Parse overloads.
+ //
+ private static Boolean TryParseTimeSpanConstant(String input, ref TimeSpanResult result) {
+ return (new StringParser().TryParse(input, ref result));
+ }
+
+ private struct StringParser {
+ private String str;
+ private char ch;
+ private int pos;
+ private int len;
+
+ internal void NextChar() {
+ if (pos < len) pos++;
+ ch = pos < len? str[pos]: (char) 0;
+ }
+
+ internal char NextNonDigit() {
+ int i = pos;
+ while (i < len) {
+ char ch = str[i];
+ if (ch < '0' || ch > '9') return ch;
+ i++;
+ }
+ return (char) 0;
+ }
+
+ internal bool TryParse(String input, ref TimeSpanResult result) {
+ result.parsedTimeSpan._ticks = 0;
+
+ if (input == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
+ return false;
+ }
+ str = input;
+ len = input.Length;
+ pos = -1;
+ NextChar();
+ SkipBlanks();
+ bool negative = false;
+ if (ch == '-') {
+ negative = true;
+ NextChar();
+ }
+ long time;
+ if (NextNonDigit() == ':') {
+ if (!ParseTime(out time, ref result)) {
+ return false;
+ };
+ }
+ else {
+ int days;
+ if (!ParseInt((int)(0x7FFFFFFFFFFFFFFFL / TimeSpan.TicksPerDay), out days, ref result)) {
+ return false;
+ }
+ time = days * TimeSpan.TicksPerDay;
+ if (ch == '.') {
+ NextChar();
+ long remainingTime;
+ if (!ParseTime(out remainingTime, ref result)) {
+ return false;
+ };
+ time += remainingTime;
+ }
+ }
+ if (negative) {
+ time = -time;
+ // Allow -0 as well
+ if (time > 0) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ }
+ else {
+ if (time < 0) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ }
+ SkipBlanks();
+ if (pos < len) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ result.parsedTimeSpan._ticks = time;
+ return true;
+ }
+
+ internal bool ParseInt(int max, out int i, ref TimeSpanResult result) {
+ i = 0;
+ int p = pos;
+ while (ch >= '0' && ch <= '9') {
+ if ((i & 0xF0000000) != 0) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ i = i * 10 + ch - '0';
+ if (i < 0) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ NextChar();
+ }
+ if (p == pos) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ if (i > max) {
+ result.SetFailure(ParseFailureKind.Overflow, "Overflow_TimeSpanElementTooLarge");
+ return false;
+ }
+ return true;
+ }
+
+ internal bool ParseTime(out long time, ref TimeSpanResult result) {
+ time = 0;
+ int unit;
+ if (!ParseInt(23, out unit, ref result)) {
+ return false;
+ }
+ time = unit * TimeSpan.TicksPerHour;
+ if (ch != ':') {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+ NextChar();
+ if (!ParseInt(59, out unit, ref result)) {
+ return false;
+ }
+ time += unit * TimeSpan.TicksPerMinute;
+ if (ch == ':') {
+ NextChar();
+ // allow seconds with the leading zero
+ if (ch != '.') {
+ if (!ParseInt(59, out unit, ref result)) {
+ return false;
+ }
+ time += unit * TimeSpan.TicksPerSecond;
+ }
+ if (ch == '.') {
+ NextChar();
+ int f = (int)TimeSpan.TicksPerSecond;
+ while (f > 1 && ch >= '0' && ch <= '9') {
+ f /= 10;
+ time += (ch - '0') * f;
+ NextChar();
+ }
+ }
+ }
+ return true;
+ }
+
+ internal void SkipBlanks() {
+ while (ch == ' ' || ch == '\t') NextChar();
+ }
+ }
+ #endregion
+
+ #region TryParseExactMultipleTimeSpan
+ //
+ // TryParseExactMultipleTimeSpan
+ //
+ // Actions: Common private ParseExactMultiple method called by both ParseExactMultiple and TryParseExactMultiple
+ //
+ private static Boolean TryParseExactMultipleTimeSpan(String input, String[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result) {
+ if (input == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "input");
+ return false;
+ }
+ if (formats == null) {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "formats");
+ return false;
+ }
+
+ if (input.Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return false;
+ }
+
+ if (formats.Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
+ return false;
+ }
+
+ //
+ // Do a loop through the provided formats and see if we can parse succesfully in
+ // one of the formats.
+ //
+ for (int i = 0; i < formats.Length; i++) {
+ if (formats[i] == null || formats[i].Length == 0) {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier");
+ return false;
+ }
+
+ // Create a new non-throwing result each time to ensure the runs are independent.
+ TimeSpanResult innerResult = new TimeSpanResult();
+ innerResult.Init(TimeSpanThrowStyle.None);
+
+ if(TryParseExactTimeSpan(input, formats[i], formatProvider, styles, ref innerResult)) {
+ result.parsedTimeSpan = innerResult.parsedTimeSpan;
+ return true;
+ }
+ }
+
+ result.SetFailure(ParseFailureKind.Format, "Format_BadTimeSpan");
+ return (false);
+ }
+ #endregion
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/TimeSpanStyles.cs b/src/mscorlib/src/System/Globalization/TimeSpanStyles.cs
new file mode 100644
index 0000000000..7ba5327324
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/TimeSpanStyles.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+
+ [Flags]
+ public enum TimeSpanStyles {
+ None = 0x00000000,
+ AssumeNegative = 0x00000001,
+ }
+}
diff --git a/src/mscorlib/src/System/Globalization/UmAlQuraCalendar.cs b/src/mscorlib/src/System/Globalization/UmAlQuraCalendar.cs
new file mode 100644
index 0000000000..94b235085e
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/UmAlQuraCalendar.cs
@@ -0,0 +1,849 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Globalization {
+ using System;
+ using System.Diagnostics.Contracts;
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Notes about UmAlQuraCalendar
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /*
+ ** Calendar support range:
+ ** Calendar Minimum Maximum
+ ** ========== ========== ==========
+ ** Gregorian 1900/04/30 2077/11/17
+ ** UmAlQura 1318/01/01 1500/12/30
+ */
+
+ [Serializable]
+ public class UmAlQuraCalendar : Calendar {
+
+ internal const int MinCalendarYear = 1318;
+ internal const int MaxCalendarYear = 1500;
+
+ internal struct DateMapping
+ {
+ internal DateMapping(int MonthsLengthFlags, int GYear, int GMonth, int GDay)
+ {
+ HijriMonthsLengthFlags = MonthsLengthFlags;
+ GregorianDate = new DateTime(GYear, GMonth, GDay);
+ }
+ internal int HijriMonthsLengthFlags;
+ internal DateTime GregorianDate;
+ }
+
+ static readonly DateMapping [] HijriYearInfo = InitDateMapping();
+
+ static DateMapping[] InitDateMapping()
+ {
+ short[] rawData = new short[] {
+//These data is taken from Tables/Excel/UmAlQura.xls please make sure that the two places are in sync
+/* DaysPerM GY GM GD D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12
+1318*/0x02EA, 1900, 4, 30,/* 0 1 0 1 0 1 1 1 0 1 0 0 4/30/1900
+1319*/0x06E9, 1901, 4, 19,/* 1 0 0 1 0 1 1 1 0 1 1 0 4/19/1901
+1320*/0x0ED2, 1902, 4, 9,/* 0 1 0 0 1 0 1 1 0 1 1 1 4/9/1902
+1321*/0x0EA4, 1903, 3, 30,/* 0 0 1 0 0 1 0 1 0 1 1 1 3/30/1903
+1322*/0x0D4A, 1904, 3, 18,/* 0 1 0 1 0 0 1 0 1 0 1 1 3/18/1904
+1323*/0x0A96, 1905, 3, 7,/* 0 1 1 0 1 0 0 1 0 1 0 1 3/7/1905
+1324*/0x0536, 1906, 2, 24,/* 0 1 1 0 1 1 0 0 1 0 1 0 2/24/1906
+1325*/0x0AB5, 1907, 2, 13,/* 1 0 1 0 1 1 0 1 0 1 0 1 2/13/1907
+1326*/0x0DAA, 1908, 2, 3,/* 0 1 0 1 0 1 0 1 1 0 1 1 2/3/1908
+1327*/0x0BA4, 1909, 1, 23,/* 0 0 1 0 0 1 0 1 1 1 0 1 1/23/1909
+1328*/0x0B49, 1910, 1, 12,/* 1 0 0 1 0 0 1 0 1 1 0 1 1/12/1910
+1329*/0x0A93, 1911, 1, 1,/* 1 1 0 0 1 0 0 1 0 1 0 1 1/1/1911
+1330*/0x052B, 1911, 12, 21,/* 1 1 0 1 0 1 0 0 1 0 1 0 12/21/1911
+1331*/0x0A57, 1912, 12, 9,/* 1 1 1 0 1 0 1 0 0 1 0 1 12/9/1912
+1332*/0x04B6, 1913, 11, 29,/* 0 1 1 0 1 1 0 1 0 0 1 0 11/29/1913
+1333*/0x0AB5, 1914, 11, 18,/* 1 0 1 0 1 1 0 1 0 1 0 1 11/18/1914
+1334*/0x05AA, 1915, 11, 8,/* 0 1 0 1 0 1 0 1 1 0 1 0 11/8/1915
+1335*/0x0D55, 1916, 10, 27,/* 1 0 1 0 1 0 1 0 1 0 1 1 10/27/1916
+1336*/0x0D2A, 1917, 10, 17,/* 0 1 0 1 0 1 0 0 1 0 1 1 10/17/1917
+1337*/0x0A56, 1918, 10, 6,/* 0 1 1 0 1 0 1 0 0 1 0 1 10/6/1918
+1338*/0x04AE, 1919, 9, 25,/* 0 1 1 1 0 1 0 1 0 0 1 0 9/25/1919
+1339*/0x095D, 1920, 9, 13,/* 1 0 1 1 1 0 1 0 1 0 0 1 9/13/1920
+1340*/0x02EC, 1921, 9, 3,/* 0 0 1 1 0 1 1 1 0 1 0 0 9/3/1921
+1341*/0x06D5, 1922, 8, 23,/* 1 0 1 0 1 0 1 1 0 1 1 0 8/23/1922
+1342*/0x06AA, 1923, 8, 13,/* 0 1 0 1 0 1 0 1 0 1 1 0 8/13/1923
+1343*/0x0555, 1924, 8, 1,/* 1 0 1 0 1 0 1 0 1 0 1 0 8/1/1924
+1344*/0x04AB, 1925, 7, 21,/* 1 1 0 1 0 1 0 1 0 0 1 0 7/21/1925
+1345*/0x095B, 1926, 7, 10,/* 1 1 0 1 1 0 1 0 1 0 0 1 7/10/1926
+1346*/0x02BA, 1927, 6, 30,/* 0 1 0 1 1 1 0 1 0 1 0 0 6/30/1927
+1347*/0x0575, 1928, 6, 18,/* 1 0 1 0 1 1 1 0 1 0 1 0 6/18/1928
+1348*/0x0BB2, 1929, 6, 8,/* 0 1 0 0 1 1 0 1 1 1 0 1 6/8/1929
+1349*/0x0764, 1930, 5, 29,/* 0 0 1 0 0 1 1 0 1 1 1 0 5/29/1930
+1350*/0x0749, 1931, 5, 18,/* 1 0 0 1 0 0 1 0 1 1 1 0 5/18/1931
+1351*/0x0655, 1932, 5, 6,/* 1 0 1 0 1 0 1 0 0 1 1 0 5/6/1932
+1352*/0x02AB, 1933, 4, 25,/* 1 1 0 1 0 1 0 1 0 1 0 0 4/25/1933
+1353*/0x055B, 1934, 4, 14,/* 1 1 0 1 1 0 1 0 1 0 1 0 4/14/1934
+1354*/0x0ADA, 1935, 4, 4,/* 0 1 0 1 1 0 1 1 0 1 0 1 4/4/1935
+1355*/0x06D4, 1936, 3, 24,/* 0 0 1 0 1 0 1 1 0 1 1 0 3/24/1936
+1356*/0x0EC9, 1937, 3, 13,/* 1 0 0 1 0 0 1 1 0 1 1 1 3/13/1937
+1357*/0x0D92, 1938, 3, 3,/* 0 1 0 0 1 0 0 1 1 0 1 1 3/3/1938
+1358*/0x0D25, 1939, 2, 20,/* 1 0 1 0 0 1 0 0 1 0 1 1 2/20/1939
+1359*/0x0A4D, 1940, 2, 9,/* 1 0 1 1 0 0 1 0 0 1 0 1 2/9/1940
+1360*/0x02AD, 1941, 1, 28,/* 1 0 1 1 0 1 0 1 0 1 0 0 1/28/1941
+1361*/0x056D, 1942, 1, 17,/* 1 0 1 1 0 1 1 0 1 0 1 0 1/17/1942
+1362*/0x0B6A, 1943, 1, 7,/* 0 1 0 1 0 1 1 0 1 1 0 1 1/7/1943
+1363*/0x0B52, 1943, 12, 28,/* 0 1 0 0 1 0 1 0 1 1 0 1 12/28/1943
+1364*/0x0AA5, 1944, 12, 16,/* 1 0 1 0 0 1 0 1 0 1 0 1 12/16/1944
+1365*/0x0A4B, 1945, 12, 5,/* 1 1 0 1 0 0 1 0 0 1 0 1 12/5/1945
+1366*/0x0497, 1946, 11, 24,/* 1 1 1 0 1 0 0 1 0 0 1 0 11/24/1946
+1367*/0x0937, 1947, 11, 13,/* 1 1 1 0 1 1 0 0 1 0 0 1 11/13/1947
+1368*/0x02B6, 1948, 11, 2,/* 0 1 1 0 1 1 0 1 0 1 0 0 11/2/1948
+1369*/0x0575, 1949, 10, 22,/* 1 0 1 0 1 1 1 0 1 0 1 0 10/22/1949
+1370*/0x0D6A, 1950, 10, 12,/* 0 1 0 1 0 1 1 0 1 0 1 1 10/12/1950
+1371*/0x0D52, 1951, 10, 2,/* 0 1 0 0 1 0 1 0 1 0 1 1 10/2/1951
+1372*/0x0A96, 1952, 9, 20,/* 0 1 1 0 1 0 0 1 0 1 0 1 9/20/1952
+1373*/0x092D, 1953, 9, 9,/* 1 0 1 1 0 1 0 0 1 0 0 1 9/9/1953
+1374*/0x025D, 1954, 8, 29,/* 1 0 1 1 1 0 1 0 0 1 0 0 8/29/1954
+1375*/0x04DD, 1955, 8, 18,/* 1 0 1 1 1 0 1 1 0 0 1 0 8/18/1955
+1376*/0x0ADA, 1956, 8, 7,/* 0 1 0 1 1 0 1 1 0 1 0 1 8/7/1956
+1377*/0x05D4, 1957, 7, 28,/* 0 0 1 0 1 0 1 1 1 0 1 0 7/28/1957
+1378*/0x0DA9, 1958, 7, 17,/* 1 0 0 1 0 1 0 1 1 0 1 1 7/17/1958
+1379*/0x0D52, 1959, 7, 7,/* 0 1 0 0 1 0 1 0 1 0 1 1 7/7/1959
+1380*/0x0AAA, 1960, 6, 25,/* 0 1 0 1 0 1 0 1 0 1 0 1 6/25/1960
+1381*/0x04D6, 1961, 6, 14,/* 0 1 1 0 1 0 1 1 0 0 1 0 6/14/1961
+1382*/0x09B6, 1962, 6, 3,/* 0 1 1 0 1 1 0 1 1 0 0 1 6/3/1962
+1383*/0x0374, 1963, 5, 24,/* 0 0 1 0 1 1 1 0 1 1 0 0 5/24/1963
+1384*/0x0769, 1964, 5, 12,/* 1 0 0 1 0 1 1 0 1 1 1 0 5/12/1964
+1385*/0x0752, 1965, 5, 2,/* 0 1 0 0 1 0 1 0 1 1 1 0 5/2/1965
+1386*/0x06A5, 1966, 4, 21,/* 1 0 1 0 0 1 0 1 0 1 1 0 4/21/1966
+1387*/0x054B, 1967, 4, 10,/* 1 1 0 1 0 0 1 0 1 0 1 0 4/10/1967
+1388*/0x0AAB, 1968, 3, 29,/* 1 1 0 1 0 1 0 1 0 1 0 1 3/29/1968
+1389*/0x055A, 1969, 3, 19,/* 0 1 0 1 1 0 1 0 1 0 1 0 3/19/1969
+1390*/0x0AD5, 1970, 3, 8,/* 1 0 1 0 1 0 1 1 0 1 0 1 3/8/1970
+1391*/0x0DD2, 1971, 2, 26,/* 0 1 0 0 1 0 1 1 1 0 1 1 2/26/1971
+1392*/0x0DA4, 1972, 2, 16,/* 0 0 1 0 0 1 0 1 1 0 1 1 2/16/1972
+1393*/0x0D49, 1973, 2, 4,/* 1 0 0 1 0 0 1 0 1 0 1 1 2/4/1973
+1394*/0x0A95, 1974, 1, 24,/* 1 0 1 0 1 0 0 1 0 1 0 1 1/24/1974
+1395*/0x052D, 1975, 1, 13,/* 1 0 1 1 0 1 0 0 1 0 1 0 1/13/1975
+1396*/0x0A5D, 1976, 1, 2,/* 1 0 1 1 1 0 1 0 0 1 0 1 1/2/1976
+1397*/0x055A, 1976, 12, 22,/* 0 1 0 1 1 0 1 0 1 0 1 0 12/22/1976
+1398*/0x0AD5, 1977, 12, 11,/* 1 0 1 0 1 0 1 1 0 1 0 1 12/11/1977
+1399*/0x06AA, 1978, 12, 1,/* 0 1 0 1 0 1 0 1 0 1 1 0 12/1/1978
+1400*/0x0695, 1979, 11, 20,/* 1 0 1 0 1 0 0 1 0 1 1 0 11/20/1979
+1401*/0x052B, 1980, 11, 8,/* 1 1 0 1 0 1 0 0 1 0 1 0 11/8/1980
+1402*/0x0A57, 1981, 10, 28,/* 1 1 1 0 1 0 1 0 0 1 0 1 10/28/1981
+1403*/0x04AE, 1982, 10, 18,/* 0 1 1 1 0 1 0 1 0 0 1 0 10/18/1982
+1404*/0x0976, 1983, 10, 7,/* 0 1 1 0 1 1 1 0 1 0 0 1 10/7/1983
+1405*/0x056C, 1984, 9, 26,/* 0 0 1 1 0 1 1 0 1 0 1 0 9/26/1984
+1406*/0x0B55, 1985, 9, 15,/* 1 0 1 0 1 0 1 0 1 1 0 1 9/15/1985
+1407*/0x0AAA, 1986, 9, 5,/* 0 1 0 1 0 1 0 1 0 1 0 1 9/5/1986
+1408*/0x0A55, 1987, 8, 25,/* 1 0 1 0 1 0 1 0 0 1 0 1 8/25/1987
+1409*/0x04AD, 1988, 8, 13,/* 1 0 1 1 0 1 0 1 0 0 1 0 8/13/1988
+1410*/0x095D, 1989, 8, 2,/* 1 0 1 1 1 0 1 0 1 0 0 1 8/2/1989
+1411*/0x02DA, 1990, 7, 23,/* 0 1 0 1 1 0 1 1 0 1 0 0 7/23/1990
+1412*/0x05D9, 1991, 7, 12,/* 1 0 0 1 1 0 1 1 1 0 1 0 7/12/1991
+1413*/0x0DB2, 1992, 7, 1,/* 0 1 0 0 1 1 0 1 1 0 1 1 7/1/1992
+1414*/0x0BA4, 1993, 6, 21,/* 0 0 1 0 0 1 0 1 1 1 0 1 6/21/1993
+1415*/0x0B4A, 1994, 6, 10,/* 0 1 0 1 0 0 1 0 1 1 0 1 6/10/1994
+1416*/0x0A55, 1995, 5, 30,/* 1 0 1 0 1 0 1 0 0 1 0 1 5/30/1995
+1417*/0x02B5, 1996, 5, 18,/* 1 0 1 0 1 1 0 1 0 1 0 0 5/18/1996
+1418*/0x0575, 1997, 5, 7,/* 1 0 1 0 1 1 1 0 1 0 1 0 5/7/1997
+1419*/0x0B6A, 1998, 4, 27,/* 0 1 0 1 0 1 1 0 1 1 0 1 4/27/1998
+1420*/0x0BD2, 1999, 4, 17,/* 0 1 0 0 1 0 1 1 1 1 0 1 4/17/1999
+1421*/0x0BC4, 2000, 4, 6,/* 0 0 1 0 0 0 1 1 1 1 0 1 4/6/2000
+1422*/0x0B89, 2001, 3, 26,/* 1 0 0 1 0 0 0 1 1 1 0 1 3/26/2001
+1423*/0x0A95, 2002, 3, 15,/* 1 0 1 0 1 0 0 1 0 1 0 1 3/15/2002
+1424*/0x052D, 2003, 3, 4,/* 1 0 1 1 0 1 0 0 1 0 1 0 3/4/2003
+1425*/0x05AD, 2004, 2, 21,/* 1 0 1 1 0 1 0 1 1 0 1 0 2/21/2004
+1426*/0x0B6A, 2005, 2, 10,/* 0 1 0 1 0 1 1 0 1 1 0 1 2/10/2005
+1427*/0x06D4, 2006, 1, 31,/* 0 0 1 0 1 0 1 1 0 1 1 0 1/31/2006
+1428*/0x0DC9, 2007, 1, 20,/* 1 0 0 1 0 0 1 1 1 0 1 1 1/20/2007
+1429*/0x0D92, 2008, 1, 10,/* 0 1 0 0 1 0 0 1 1 0 1 1 1/10/2008
+1430*/0x0AA6, 2008, 12, 29,/* 0 1 1 0 0 1 0 1 0 1 0 1 12/29/2008
+1431*/0x0956, 2009, 12, 18,/* 0 1 1 0 1 0 1 0 1 0 0 1 12/18/2009
+1432*/0x02AE, 2010, 12, 7,/* 0 1 1 1 0 1 0 1 0 1 0 0 12/7/2010
+1433*/0x056D, 2011, 11, 26,/* 1 0 1 1 0 1 1 0 1 0 1 0 11/26/2011
+1434*/0x036A, 2012, 11, 15,/* 0 1 0 1 0 1 1 0 1 1 0 0 11/15/2012
+1435*/0x0B55, 2013, 11, 4,/* 1 0 1 0 1 0 1 0 1 1 0 1 11/4/2013
+1436*/0x0AAA, 2014, 10, 25,/* 0 1 0 1 0 1 0 1 0 1 0 1 10/25/2014
+1437*/0x094D, 2015, 10, 14,/* 1 0 1 1 0 0 1 0 1 0 0 1 10/14/2015
+1438*/0x049D, 2016, 10, 2,/* 1 0 1 1 1 0 0 1 0 0 1 0 10/2/2016
+1439*/0x095D, 2017, 9, 21,/* 1 0 1 1 1 0 1 0 1 0 0 1 9/21/2017
+1440*/0x02BA, 2018, 9, 11,/* 0 1 0 1 1 1 0 1 0 1 0 0 9/11/2018
+1441*/0x05B5, 2019, 8, 31,/* 1 0 1 0 1 1 0 1 1 0 1 0 8/31/2019
+1442*/0x05AA, 2020, 8, 20,/* 0 1 0 1 0 1 0 1 1 0 1 0 8/20/2020
+1443*/0x0D55, 2021, 8, 9,/* 1 0 1 0 1 0 1 0 1 0 1 1 8/9/2021
+1444*/0x0A9A, 2022, 7, 30,/* 0 1 0 1 1 0 0 1 0 1 0 1 7/30/2022
+1445*/0x092E, 2023, 7, 19,/* 0 1 1 1 0 1 0 0 1 0 0 1 7/19/2023
+1446*/0x026E, 2024, 7, 7,/* 0 1 1 1 0 1 1 0 0 1 0 0 7/7/2024
+1447*/0x055D, 2025, 6, 26,/* 1 0 1 1 1 0 1 0 1 0 1 0 6/26/2025
+1448*/0x0ADA, 2026, 6, 16,/* 0 1 0 1 1 0 1 1 0 1 0 1 6/16/2026
+1449*/0x06D4, 2027, 6, 6,/* 0 0 1 0 1 0 1 1 0 1 1 0 6/6/2027
+1450*/0x06A5, 2028, 5, 25,/* 1 0 1 0 0 1 0 1 0 1 1 0 5/25/2028
+1451*/0x054B, 2029, 5, 14,/* 1 1 0 1 0 0 1 0 1 0 1 0 5/14/2029
+1452*/0x0A97, 2030, 5, 3,/* 1 1 1 0 1 0 0 1 0 1 0 1 5/3/2030
+1453*/0x054E, 2031, 4, 23,/* 0 1 1 1 0 0 1 0 1 0 1 0 4/23/2031
+1454*/0x0AAE, 2032, 4, 11,/* 0 1 1 1 0 1 0 1 0 1 0 1 4/11/2032
+1455*/0x05AC, 2033, 4, 1,/* 0 0 1 1 0 1 0 1 1 0 1 0 4/1/2033
+1456*/0x0BA9, 2034, 3, 21,/* 1 0 0 1 0 1 0 1 1 1 0 1 3/21/2034
+1457*/0x0D92, 2035, 3, 11,/* 0 1 0 0 1 0 0 1 1 0 1 1 3/11/2035
+1458*/0x0B25, 2036, 2, 28,/* 1 0 1 0 0 1 0 0 1 1 0 1 2/28/2036
+1459*/0x064B, 2037, 2, 16,/* 1 1 0 1 0 0 1 0 0 1 1 0 2/16/2037
+1460*/0x0CAB, 2038, 2, 5,/* 1 1 0 1 0 1 0 1 0 0 1 1 2/5/2038
+1461*/0x055A, 2039, 1, 26,/* 0 1 0 1 1 0 1 0 1 0 1 0 1/26/2039
+1462*/0x0B55, 2040, 1, 15,/* 1 0 1 0 1 0 1 0 1 1 0 1 1/15/2040
+1463*/0x06D2, 2041, 1, 4,/* 0 1 0 0 1 0 1 1 0 1 1 0 1/4/2041
+1464*/0x0EA5, 2041, 12, 24,/* 1 0 1 0 0 1 0 1 0 1 1 1 12/24/2041
+1465*/0x0E4A, 2042, 12, 14,/* 0 1 0 1 0 0 1 0 0 1 1 1 12/14/2042
+1466*/0x0A95, 2043, 12, 3,/* 1 0 1 0 1 0 0 1 0 1 0 1 12/3/2043
+1467*/0x052D, 2044, 11, 21,/* 1 0 1 1 0 1 0 0 1 0 1 0 11/21/2044
+1468*/0x0AAD, 2045, 11, 10,/* 1 0 1 1 0 1 0 1 0 1 0 1 11/10/2045
+1469*/0x036C, 2046, 10, 31,/* 0 0 1 1 0 1 1 0 1 1 0 0 10/31/2046
+1470*/0x0759, 2047, 10, 20,/* 1 0 0 1 1 0 1 0 1 1 1 0 10/20/2047
+1471*/0x06D2, 2048, 10, 9,/* 0 1 0 0 1 0 1 1 0 1 1 0 10/9/2048
+1472*/0x0695, 2049, 9, 28,/* 1 0 1 0 1 0 0 1 0 1 1 0 9/28/2049
+1473*/0x052D, 2050, 9, 17,/* 1 0 1 1 0 1 0 0 1 0 1 0 9/17/2050
+1474*/0x0A5B, 2051, 9, 6,/* 1 1 0 1 1 0 1 0 0 1 0 1 9/6/2051
+1475*/0x04BA, 2052, 8, 26,/* 0 1 0 1 1 1 0 1 0 0 1 0 8/26/2052
+1476*/0x09BA, 2053, 8, 15,/* 0 1 0 1 1 1 0 1 1 0 0 1 8/15/2053
+1477*/0x03B4, 2054, 8, 5,/* 0 0 1 0 1 1 0 1 1 1 0 0 8/5/2054
+1478*/0x0B69, 2055, 7, 25,/* 1 0 0 1 0 1 1 0 1 1 0 1 7/25/2055
+1479*/0x0B52, 2056, 7, 14,/* 0 1 0 0 1 0 1 0 1 1 0 1 7/14/2056
+1480*/0x0AA6, 2057, 7, 3,/* 0 1 1 0 0 1 0 1 0 1 0 1 7/3/2057
+1481*/0x04B6, 2058, 6, 22,/* 0 1 1 0 1 1 0 1 0 0 1 0 6/22/2058
+1482*/0x096D, 2059, 6, 11,/* 1 0 1 1 0 1 1 0 1 0 0 1 6/11/2059
+1483*/0x02EC, 2060, 5, 31,/* 0 0 1 1 0 1 1 1 0 1 0 0 5/31/2060
+1484*/0x06D9, 2061, 5, 20,/* 1 0 0 1 1 0 1 1 0 1 1 0 5/20/2061
+1485*/0x0EB2, 2062, 5, 10,/* 0 1 0 0 1 1 0 1 0 1 1 1 5/10/2062
+1486*/0x0D54, 2063, 4, 30,/* 0 0 1 0 1 0 1 0 1 0 1 1 4/30/2063
+1487*/0x0D2A, 2064, 4, 18,/* 0 1 0 1 0 1 0 0 1 0 1 1 4/18/2064
+1488*/0x0A56, 2065, 4, 7,/* 0 1 1 0 1 0 1 0 0 1 0 1 4/7/2065
+1489*/0x04AE, 2066, 3, 27,/* 0 1 1 1 0 1 0 1 0 0 1 0 3/27/2066
+1490*/0x096D, 2067, 3, 16,/* 1 0 1 1 0 1 1 0 1 0 0 1 3/16/2067
+1491*/0x0D6A, 2068, 3, 5,/* 0 1 0 1 0 1 1 0 1 0 1 1 3/5/2068
+1492*/0x0B54, 2069, 2, 23,/* 0 0 1 0 1 0 1 0 1 1 0 1 2/23/2069
+1493*/0x0B29, 2070, 2, 12,/* 1 0 0 1 0 1 0 0 1 1 0 1 2/12/2070
+1494*/0x0A93, 2071, 2, 1,/* 1 1 0 0 1 0 0 1 0 1 0 1 2/1/2071
+1495*/0x052B, 2072, 1, 21,/* 1 1 0 1 0 1 0 0 1 0 1 0 1/21/2072
+1496*/0x0A57, 2073, 1, 9,/* 1 1 1 0 1 0 1 0 0 1 0 1 1/9/2073
+1497*/0x0536, 2073, 12, 30,/* 0 1 1 0 1 1 0 0 1 0 1 0 12/30/2073
+1498*/0x0AB5, 2074, 12, 19,/* 1 0 1 0 1 1 0 1 0 1 0 1 12/19/2074
+1499*/0x06AA, 2075, 12, 9,/* 0 1 0 1 0 1 0 1 0 1 1 0 12/9/2075
+1500*/0x0E93, 2076, 11, 27,/* 1 1 0 0 1 0 0 1 0 1 1 1 11/27/2076
+1501*/ 0, 2077, 11, 17,/* 0 0 0 0 0 0 0 0 0 0 0 0 11/17/2077
+
+*/ };
+
+ // Direct inline initialization of DateMapping array would produce a lot of code bloat.
+
+ // We take advantage of C# compiler compiles inline initialization of primitive type array into very compact code.
+ // So we start with raw data stored in primitive type array, and initialize the DateMapping out of it
+
+ DateMapping[] mapping = new DateMapping[rawData.Length / 4];
+ for (int i = 0; i < mapping.Length; i++)
+ mapping[i] = new DateMapping(rawData[i * 4], rawData[i * 4 + 1], rawData[i * 4 + 2], rawData[i * 4 + 3]);
+ return mapping;
+ }
+
+ public const int UmAlQuraEra = 1;
+
+ internal const int DateCycle = 30;
+ internal const int DatePartYear = 0;
+ internal const int DatePartDayOfYear = 1;
+ internal const int DatePartMonth = 2;
+ internal const int DatePartDay = 3;
+
+ //internal static Calendar m_defaultInstance;
+
+
+ // This is the minimal Gregorian date that we support in the UmAlQuraCalendar.
+ internal static DateTime minDate = new DateTime(1900, 4, 30);
+ internal static DateTime maxDate = new DateTime((new DateTime(2077, 11, 16, 23, 59, 59, 999)).Ticks + 9999);
+
+ /*=================================GetDefaultInstance==========================
+ **Action: Internal method to provide a default intance of UmAlQuraCalendar. Used by NLS+ implementation
+ ** and other calendars.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+ /*
+ internal static Calendar GetDefaultInstance() {
+ if (m_defaultInstance == null) {
+ m_defaultInstance = new UmAlQuraCalendar();
+ }
+ return (m_defaultInstance);
+ }
+ */
+
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (maxDate);
+ }
+ }
+
+
+ // Return the type of the UmAlQura calendar.
+ //
+
+
+ public override CalendarAlgorithmType AlgorithmType {
+ get {
+ return CalendarAlgorithmType.LunarCalendar;
+ }
+ }
+
+ // Construct an instance of UmAlQura calendar.
+
+ public UmAlQuraCalendar() {
+ }
+
+ internal override int BaseCalendarID {
+ get {
+ return (CAL_HIJRI);
+ }
+ }
+
+ internal override int ID {
+ get {
+ return (CAL_UMALQURA);
+ }
+ }
+
+ protected override int DaysInYearBeforeMinSupportedYear
+ {
+ get
+ {
+ // HijriCalendar has same number of days as UmAlQuraCalendar for any given year
+ // HijriCalendar says year 1317 has 355 days.
+ return 355;
+ }
+ }
+
+ /*==========================ConvertHijriToGregorian==========================
+ ** Purpose: convert Hdate(year,month,day) to Gdate(year,month,day)
+ ** Arguments:
+ ** Input: Hijrah date: year:HijriYear, month:HijriMonth, day:HijriDay
+ ** Output: Gregorian date: year:yg, month:mg, day:dg
+ =========================ConvertHijriToGregorian============================*/
+ static void ConvertHijriToGregorian(int HijriYear, int HijriMonth, int HijriDay, ref int yg, ref int mg, ref int dg)
+ {
+ Contract.Assert( (HijriYear >= MinCalendarYear) && (HijriYear <= MaxCalendarYear), "Hijri year is out of range.");
+ Contract.Assert( HijriMonth >= 1, "Hijri month is out of range.");
+ Contract.Assert( HijriDay >= 1, "Hijri day is out of range.");
+ int index, b, nDays = HijriDay-1;
+ DateTime dt;
+
+
+ index = HijriYear - MinCalendarYear;
+ dt = HijriYearInfo[index].GregorianDate;
+
+
+ b = HijriYearInfo[index].HijriMonthsLengthFlags;
+
+ for(int m = 1; m < HijriMonth; m++)
+ {
+ nDays += 29 + (b & 0x1);
+ b = b >> 1;
+ }
+
+ dt = dt.AddDays(nDays);
+ yg = dt.Year;
+ mg = dt.Month;
+ dg = dt.Day;
+ }
+
+ /*=================================GetAbsoluteDateUmAlQura==========================
+ **Action: Gets the Absolute date for the given UmAlQura date. The absolute date means
+ ** the number of days from January 1st, 1 A.D.
+ **Returns:
+ **Arguments:
+ **Exceptions:
+ ============================================================================*/
+ static long GetAbsoluteDateUmAlQura(int year, int month, int day) {
+ //Caller should check the validaty of year, month and day.
+
+ int yg=0,mg=0,dg=0;
+ ConvertHijriToGregorian(year, month, day, ref yg, ref mg, ref dg);
+ return GregorianCalendar.GetAbsoluteDate(yg,mg,dg);
+ }
+
+ static internal void CheckTicksRange(long ticks) {
+ if (ticks < minDate.Ticks || ticks > maxDate.Ticks) {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_CalendarRange"),
+ minDate,
+ maxDate));
+ }
+ }
+
+ static internal void CheckEraRange(int era) {
+ if (era != CurrentEra && era != UmAlQuraEra) {
+ throw new ArgumentOutOfRangeException("era", Environment.GetResourceString("ArgumentOutOfRange_InvalidEraValue"));
+ }
+ }
+
+ static internal void CheckYearRange(int year, int era) {
+ CheckEraRange(era);
+ if (year < MinCalendarYear || year > MaxCalendarYear) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ MinCalendarYear,
+ MaxCalendarYear));
+ }
+ }
+
+ static internal void CheckYearMonthRange(int year, int month, int era) {
+ CheckYearRange(year, era);
+ if (month < 1 || month > 12) {
+ throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
+ }
+ }
+
+ /*========================ConvertGregorianToHijri============================
+ ** Purpose: convert DateTime to Hdate(year,month,day)
+ ** Arguments:
+ ** Input: DateTime
+ ** Output: Hijrah date: year:HijriYear, month:HijriMonth, day:HijriDay
+ ============================================================================*/
+ static void ConvertGregorianToHijri(DateTime time, ref int HijriYear, ref int HijriMonth, ref int HijriDay)
+ {
+
+ int index, b, DaysPerThisMonth;
+ double nDays;
+ TimeSpan ts;
+ int yh1=0, mh1=0, dh1=0;
+
+ Contract.Assert((time.Ticks >= minDate.Ticks) && (time.Ticks <= maxDate.Ticks), "Gregorian date is out of range.");
+
+ // Find the index where we should start our search by quessing the Hijri year that we will be in HijriYearInfo.
+ // A Hijri year is 354 or 355 days. Use 355 days so that we will search from a lower index.
+
+ index = (int)((time.Ticks - minDate.Ticks) / Calendar.TicksPerDay) / 355;
+ do
+ {
+ } while (time.CompareTo(HijriYearInfo[++index].GregorianDate)>0); //while greater
+
+ if (time.CompareTo(HijriYearInfo[index].GregorianDate) != 0)
+ {
+ index--;
+ }
+
+ ts = time.Subtract(HijriYearInfo[index].GregorianDate);
+ yh1 = index + MinCalendarYear;
+
+ mh1 = 1;
+ dh1 = 1;
+ nDays = ts.TotalDays;
+ b = HijriYearInfo[index].HijriMonthsLengthFlags;
+ DaysPerThisMonth = 29 + (b&1);
+
+ while (nDays >= DaysPerThisMonth)
+ {
+ nDays -= DaysPerThisMonth;
+ b = b >> 1;
+ DaysPerThisMonth = 29 + (b&1);
+ mh1++;
+ }
+ dh1 += (int)nDays;
+
+ HijriDay = dh1;
+ HijriMonth = mh1;
+ HijriYear = yh1;
+ }
+
+ /*=================================GetDatePart==========================
+ **Action: Returns a given date part of this <i>DateTime</i>. This method is used
+ ** to compute the year, day-of-year, month, or day part.
+ **Returns:
+ **Arguments:
+ **Exceptions: ArgumentException if part is incorrect.
+ **Notes:
+ ** First, we get the absolute date (the number of days from January 1st, 1 A.C) for the given ticks.
+ ** Use the formula (((AbsoluteDate - 226894) * 33) / (33 * 365 + 8)) + 1, we can a rough value for the UmAlQura year.
+ ** In order to get the exact UmAlQura year, we compare the exact absolute date for UmAlQuraYear and (UmAlQuraYear + 1).
+ ** From here, we can get the correct UmAlQura year.
+ ============================================================================*/
+
+ internal virtual int GetDatePart(DateTime time, int part) {
+ int UmAlQuraYear=0; // UmAlQura year
+ int UmAlQuraMonth=0; // UmAlQura month
+ int UmAlQuraDay=0; // UmAlQura day
+ long ticks = time.Ticks;
+ CheckTicksRange(ticks);
+
+ ConvertGregorianToHijri(time, ref UmAlQuraYear, ref UmAlQuraMonth, ref UmAlQuraDay);
+
+ if (part == DatePartYear)
+ return (UmAlQuraYear);
+
+ if (part == DatePartMonth)
+ return (UmAlQuraMonth);
+
+ if (part == DatePartDay)
+ return (UmAlQuraDay);
+
+ if (part == DatePartDayOfYear)
+ return (int)(GetAbsoluteDateUmAlQura(UmAlQuraYear, UmAlQuraMonth, UmAlQuraDay) - GetAbsoluteDateUmAlQura(UmAlQuraYear, 1, 1) + 1);
+
+ // Incorrect part value.
+ throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_DateTimeParsing"));
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // months to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year and month parts of the specified DateTime by
+ // value months, and, if required, adjusting the day part of the
+ // resulting date downwards to the last day of the resulting month in the
+ // resulting year. The time-of-day part of the result is the same as the
+ // time-of-day part of the specified DateTime.
+ //
+ // In more precise terms, considering the specified DateTime to be of the
+ // form y / m / d + t, where y is the
+ // year, m is the month, d is the day, and t is the
+ // time-of-day, the result is y1 / m1 / d1 + t,
+ // where y1 and m1 are computed by adding value months
+ // to y and m, and d1 is the largest value less than
+ // or equal to d that denotes a valid day in month m1 of year
+ // y1.
+ //
+
+
+ public override DateTime AddMonths(DateTime time, int months) {
+ if (months < -120000 || months > 120000) {
+ throw new ArgumentOutOfRangeException(
+ "months",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ -120000,
+ 120000));
+ }
+ Contract.EndContractBlock();
+ // Get the date in UmAlQura calendar.
+ int y = GetDatePart(time, DatePartYear);
+ int m = GetDatePart(time, DatePartMonth);
+ int d = GetDatePart(time, DatePartDay);
+ int i = m - 1 + months;
+
+ if (i >= 0) {
+ m = i % 12 + 1;
+ y = y + i / 12;
+ } else {
+ m = 12 + (i + 1) % 12;
+ y = y + (i - 11) / 12;
+ }
+
+ if (d>29)
+ {
+ int days = GetDaysInMonth(y, m);
+ if (d > days) {
+ d = days;
+ }
+ }
+ CheckYearRange(y, UmAlQuraEra);
+ DateTime dt = new DateTime(GetAbsoluteDateUmAlQura(y, m, d) * TicksPerDay + time.Ticks % TicksPerDay);
+ Calendar.CheckAddResult(dt.Ticks, MinSupportedDateTime, MaxSupportedDateTime);
+ return (dt);
+ }
+
+ // Returns the DateTime resulting from adding the given number of
+ // years to the specified DateTime. The result is computed by incrementing
+ // (or decrementing) the year part of the specified DateTime by value
+ // years. If the month and day of the specified DateTime is 2/29, and if the
+ // resulting year is not a leap year, the month and day of the resulting
+ // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
+ // parts of the result are the same as those of the specified DateTime.
+ //
+
+
+ public override DateTime AddYears(DateTime time, int years) {
+ return (AddMonths(time, years * 12));
+ }
+
+ // Returns the day-of-month part of the specified DateTime. The returned
+ // value is an integer between 1 and 31.
+ //
+
+
+ public override int GetDayOfMonth(DateTime time) {
+ return (GetDatePart(time, DatePartDay));
+ }
+
+ // Returns the day-of-week part of the specified DateTime. The returned value
+ // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
+ // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
+ // Thursday, 5 indicates Friday, and 6 indicates Saturday.
+ //
+
+
+ public override DayOfWeek GetDayOfWeek(DateTime time) {
+ return ((DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7));
+ }
+
+ // Returns the day-of-year part of the specified DateTime. The returned value
+ // is an integer between 1 and 354 or 355.
+ //
+
+
+ public override int GetDayOfYear(DateTime time) {
+ return (GetDatePart(time, DatePartDayOfYear));
+ }
+
+ /*
+ internal bool CouldBeLeapYear(int year)
+ {
+ return ((((year * 11) + 14) % 30) < 11);
+ }
+ */
+
+ // Returns the number of days in the month given by the year and
+ // month arguments.
+ //
+
+
+ public override int GetDaysInMonth(int year, int month, int era) {
+ CheckYearMonthRange(year, month, era);
+
+ if ((HijriYearInfo[year-MinCalendarYear].HijriMonthsLengthFlags & (1<<month-1))==0)
+ return 29;
+ else
+ return 30;
+ }
+
+ static internal int RealGetDaysInYear(int year)
+ {
+ int days = 0, b;
+
+ Contract.Assert( (year >= MinCalendarYear) && (year <= MaxCalendarYear), "Hijri year is out of range.");
+
+ b = HijriYearInfo[year-MinCalendarYear].HijriMonthsLengthFlags;
+
+ for(int m = 1; m <= 12; m++)
+ {
+ days += 29 + (b & 0x1);
+ b = b >> 1;
+ }
+ Contract.Assert((days == 354)||(days == 355), "Hijri year has to be 354 or 355 days.");
+ return days;
+ }
+
+ // Returns the number of days in the year given by the year argument for the current era.
+ //
+
+
+ public override int GetDaysInYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (RealGetDaysInYear(year));
+ }
+
+ // Returns the era for the specified DateTime value.
+
+
+ public override int GetEra(DateTime time) {
+ CheckTicksRange(time.Ticks);
+ return (UmAlQuraEra);
+ }
+
+
+
+ public override int[] Eras {
+ get {
+ return (new int[] {UmAlQuraEra});
+ }
+ }
+
+ // Returns the month part of the specified DateTime. The returned value is an
+ // integer between 1 and 12.
+ //
+
+
+ public override int GetMonth(DateTime time) {
+ return (GetDatePart(time, DatePartMonth));
+ }
+
+ // Returns the number of months in the specified year and era.
+
+
+ public override int GetMonthsInYear(int year, int era) {
+ CheckYearRange(year, era);
+ return (12);
+ }
+
+ // Returns the year part of the specified DateTime. The returned value is an
+ // integer between MinCalendarYear and MaxCalendarYear.
+ //
+
+
+ public override int GetYear(DateTime time) {
+ return (GetDatePart(time, DatePartYear));
+ }
+
+ // Checks whether a given day in the specified era is a leap day. This method returns true if
+ // the date is a leap day, or false if not.
+ //
+
+
+ public override bool IsLeapDay(int year, int month, int day, int era) {
+ if (day>=1 && day <=29)
+ {
+ CheckYearMonthRange(year, month, era);
+ return (false);
+ }
+
+ // The year/month/era value checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+ if (day < 1 || day > daysInMonth) {
+ throw new ArgumentOutOfRangeException(
+ "day",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Day"),
+ daysInMonth,
+ month));
+ }
+ return (false);
+ }
+
+ // Returns the leap month in a calendar year of the specified era. This method returns 0
+ // if this calendar does not have leap month, or this year is not a leap year.
+ //
+
+
+ public override int GetLeapMonth(int year, int era)
+ {
+ CheckYearRange(year, era);
+ return (0);
+ }
+
+ // Checks whether a given month in the specified era is a leap month. This method returns true if
+ // month is a leap month, or false if not.
+ //
+
+
+ public override bool IsLeapMonth(int year, int month, int era) {
+ CheckYearMonthRange(year, month, era);
+ return (false);
+ }
+
+ // Checks whether a given year in the specified era is a leap year. This method returns true if
+ // year is a leap year, or false if not.
+ //
+
+
+ public override bool IsLeapYear(int year, int era)
+ {
+ CheckYearRange(year, era);
+ if (RealGetDaysInYear(year) == 355)
+ return true;
+ else
+ return false;
+ }
+
+ // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid.
+ //
+
+
+ public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) {
+ if (day >= 1 && day <= 29)
+ {
+ CheckYearMonthRange(year, month, era);
+ goto DayInRang;
+ }
+
+ // The year/month/era value checking is done in GetDaysInMonth().
+ int daysInMonth = GetDaysInMonth(year, month, era);
+
+ if (day < 1 || day > daysInMonth) {
+ BCLDebug.Log("year = " + year + ", month = " + month + ", day = " + day);
+ throw new ArgumentOutOfRangeException(
+ "day",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Day"),
+ daysInMonth,
+ month));
+ }
+DayInRang:
+ long lDate = GetAbsoluteDateUmAlQura(year, month, day);
+
+ if (lDate >= 0) {
+ return (new DateTime(lDate * GregorianCalendar.TicksPerDay + TimeToTicks(hour, minute, second, millisecond)));
+ } else {
+ throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
+ }
+ }
+
+ private const int DEFAULT_TWO_DIGIT_YEAR_MAX = 1451;
+
+
+
+ public override int TwoDigitYearMax {
+ get {
+ if (twoDigitYearMax == -1) {
+ twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DEFAULT_TWO_DIGIT_YEAR_MAX);
+ }
+ return (twoDigitYearMax);
+ }
+
+ set {
+ if (value != 99 && (value < MinCalendarYear || value > MaxCalendarYear)) {
+ throw new ArgumentOutOfRangeException(
+ "value",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ MinCalendarYear,
+ MaxCalendarYear));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+ // We allow year 99 to be set so that one can make ToFourDigitYearMax a no-op by setting TwoDigitYearMax to 99.
+ twoDigitYearMax = value;
+ }
+ }
+
+
+
+ public override int ToFourDigitYear(int year) {
+ if (year < 0) {
+ throw new ArgumentOutOfRangeException("year",
+ Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ }
+ Contract.EndContractBlock();
+
+ if (year < 100) {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if ((year < MinCalendarYear) || (year > MaxCalendarYear)) {
+ throw new ArgumentOutOfRangeException(
+ "year",
+ String.Format(
+ CultureInfo.CurrentCulture,
+ Environment.GetResourceString("ArgumentOutOfRange_Range"),
+ MinCalendarYear,
+ MaxCalendarYear));
+ }
+ return (year);
+ }
+ }
+}
+
diff --git a/src/mscorlib/src/System/Globalization/UnicodeCategory.cs b/src/mscorlib/src/System/Globalization/UnicodeCategory.cs
new file mode 100644
index 0000000000..3848b6142f
--- /dev/null
+++ b/src/mscorlib/src/System/Globalization/UnicodeCategory.cs
@@ -0,0 +1,79 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+** Purpose:
+**
+**
+============================================================*/
+namespace System.Globalization {
+
+ [Serializable]
+[System.Runtime.InteropServices.ComVisible(true)]
+ public enum UnicodeCategory {
+
+ UppercaseLetter = 0,
+
+ LowercaseLetter = 1,
+
+ TitlecaseLetter = 2,
+
+ ModifierLetter = 3,
+
+ OtherLetter = 4,
+
+ NonSpacingMark = 5,
+
+ SpacingCombiningMark = 6,
+
+ EnclosingMark = 7,
+
+ DecimalDigitNumber = 8,
+
+ LetterNumber = 9,
+
+ OtherNumber = 10,
+
+ SpaceSeparator = 11,
+
+ LineSeparator = 12,
+
+ ParagraphSeparator = 13,
+
+ Control = 14,
+
+ Format = 15,
+
+ Surrogate = 16,
+
+ PrivateUse = 17,
+
+ ConnectorPunctuation = 18,
+
+ DashPunctuation = 19,
+
+ OpenPunctuation = 20,
+
+ ClosePunctuation = 21,
+
+ InitialQuotePunctuation = 22,
+
+ FinalQuotePunctuation = 23,
+
+ OtherPunctuation = 24,
+
+ MathSymbol = 25,
+
+ CurrencySymbol = 26,
+
+ ModifierSymbol = 27,
+
+ OtherSymbol = 28,
+
+ OtherNotAssigned = 29,
+ }
+}