summaryrefslogtreecommitdiff
path: root/src/mscorlib/shared/System/Globalization
diff options
context:
space:
mode:
Diffstat (limited to 'src/mscorlib/shared/System/Globalization')
-rw-r--r--src/mscorlib/shared/System/Globalization/CalendarAlgorithmType.cs18
-rw-r--r--src/mscorlib/shared/System/Globalization/CalendarWeekRule.cs16
-rw-r--r--src/mscorlib/shared/System/Globalization/CalendricalCalculationsHelper.cs412
-rw-r--r--src/mscorlib/shared/System/Globalization/ChineseLunisolarCalendar.cs390
-rw-r--r--src/mscorlib/shared/System/Globalization/CultureNotFoundException.cs120
-rw-r--r--src/mscorlib/shared/System/Globalization/CultureTypes.cs27
-rw-r--r--src/mscorlib/shared/System/Globalization/DateTimeFormat.cs1206
-rw-r--r--src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs3028
-rw-r--r--src/mscorlib/shared/System/Globalization/DateTimeFormatInfoScanner.cs739
-rw-r--r--src/mscorlib/shared/System/Globalization/DateTimeParse.cs5672
-rw-r--r--src/mscorlib/shared/System/Globalization/DateTimeStyles.cs49
-rw-r--r--src/mscorlib/shared/System/Globalization/DaylightTime.cs50
-rw-r--r--src/mscorlib/shared/System/Globalization/DigitShapes.cs13
-rw-r--r--src/mscorlib/shared/System/Globalization/EastAsianLunisolarCalendar.cs710
-rw-r--r--src/mscorlib/shared/System/Globalization/GregorianCalendarTypes.cs19
-rw-r--r--src/mscorlib/shared/System/Globalization/HebrewCalendar.cs1129
-rw-r--r--src/mscorlib/shared/System/Globalization/HebrewNumber.cs457
-rw-r--r--src/mscorlib/shared/System/Globalization/HijriCalendar.cs677
-rw-r--r--src/mscorlib/shared/System/Globalization/InternalGlobalizationHelper.cs48
-rw-r--r--src/mscorlib/shared/System/Globalization/JapaneseCalendar.cs409
-rw-r--r--src/mscorlib/shared/System/Globalization/JapaneseLunisolarCalendar.cs305
-rw-r--r--src/mscorlib/shared/System/Globalization/JulianCalendar.cs443
-rw-r--r--src/mscorlib/shared/System/Globalization/KoreanCalendar.cs266
-rw-r--r--src/mscorlib/shared/System/Globalization/KoreanLunisolarCalendar.cs1323
-rw-r--r--src/mscorlib/shared/System/Globalization/LocaleData.Unix.cs4572
-rw-r--r--src/mscorlib/shared/System/Globalization/NumberStyles.cs65
-rw-r--r--src/mscorlib/shared/System/Globalization/PersianCalendar.cs606
-rw-r--r--src/mscorlib/shared/System/Globalization/SortVersion.cs98
-rw-r--r--src/mscorlib/shared/System/Globalization/TaiwanCalendar.cs285
-rw-r--r--src/mscorlib/shared/System/Globalization/TaiwanLunisolarCalendar.cs325
-rw-r--r--src/mscorlib/shared/System/Globalization/ThaiBuddhistCalendar.cs236
-rw-r--r--src/mscorlib/shared/System/Globalization/TimeSpanStyles.cs13
-rw-r--r--src/mscorlib/shared/System/Globalization/UmAlQuraCalendar.cs865
-rw-r--r--src/mscorlib/shared/System/Globalization/UnicodeCategory.cs40
34 files changed, 24631 insertions, 0 deletions
diff --git a/src/mscorlib/shared/System/Globalization/CalendarAlgorithmType.cs b/src/mscorlib/shared/System/Globalization/CalendarAlgorithmType.cs
new file mode 100644
index 0000000000..4ddc307abf
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/CalendarAlgorithmType.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
+{
+ 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/shared/System/Globalization/CalendarWeekRule.cs b/src/mscorlib/shared/System/Globalization/CalendarWeekRule.cs
new file mode 100644
index 0000000000..b381b5c544
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/CalendarWeekRule.cs
@@ -0,0 +1,16 @@
+// 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]
+ 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/shared/System/Globalization/CalendricalCalculationsHelper.cs b/src/mscorlib/shared/System/Globalization/CalendricalCalculationsHelper.cs
new file mode 100644
index 0000000000..7de75d6aee
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/CalendricalCalculationsHelper.cs
@@ -0,0 +1,412 @@
+// 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.Diagnostics;
+
+namespace System.Globalization
+{
+ internal class CalendricalCalculationsHelper
+ {
+ private const double FullCircleOfArc = 360.0; // 360.0;
+ private const int HalfCircleOfArc = 180;
+ private const double TwelveHours = 0.5; // half a day
+ private const double Noon2000Jan01 = 730120.5;
+ internal const double MeanTropicalYearInDays = 365.242189;
+ private const double MeanSpeedOfSun = MeanTropicalYearInDays / FullCircleOfArc;
+ private const double LongitudeSpring = 0.0;
+ private const double TwoDegreesAfterSpring = 2.0;
+ private const int SecondsPerDay = 24 * 60 * 60; // 24 hours * 60 minutes * 60 seconds
+
+ private const int DaysInUniformLengthCentury = 36525;
+ private const int SecondsPerMinute = 60;
+ private const int MinutesPerDegree = 60;
+
+ private static readonly long s_startOf1810 = GetNumberOfDays(new DateTime(1810, 1, 1));
+ private static readonly long s_startOf1900Century = GetNumberOfDays(new DateTime(1900, 1, 1));
+
+ private static readonly double[] s_coefficients1900to1987 = new double[] { -0.00002, 0.000297, 0.025184, -0.181133, 0.553040, -0.861938, 0.677066, -0.212591 };
+ private static readonly double[] s_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 };
+ private static readonly double[] s_coefficients1700to1799 = new double[] { 8.118780842, -0.005092142, 0.003336121, -0.0000266484 };
+ private static readonly double[] s_coefficients1620to1699 = new double[] { 196.58333, -4.0675, 0.0219167 };
+ private static readonly double[] s_lambdaCoefficients = new double[] { 280.46645, 36000.76983, 0.0003032 };
+ private static readonly double[] s_anomalyCoefficients = new double[] { 357.52910, 35999.05030, -0.0001559, -0.00000048 };
+ private static readonly double[] s_eccentricityCoefficients = new double[] { 0.016708617, -0.000042037, -0.0000001236 };
+ private static readonly double[] s_coefficients = new double[] { Angle(23, 26, 21.448), Angle(0, 0, -46.8150), Angle(0, 0, -0.00059), Angle(0, 0, 0.001813) };
+ private static readonly double[] s_coefficientsA = new double[] { 124.90, -1934.134, 0.002063 };
+ private static readonly double[] s_coefficientsB = new double[] { 201.11, 72001.5377, 0.00057 };
+
+ private static double RadiansFromDegrees(double degree)
+ {
+ return degree * Math.PI / 180;
+ }
+
+ private static double SinOfDegree(double degree)
+ {
+ return Math.Sin(RadiansFromDegrees(degree));
+ }
+
+ private static double CosOfDegree(double degree)
+ {
+ return Math.Cos(RadiansFromDegrees(degree));
+ }
+ private 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;
+ }
+
+ private static double Obliquity(double julianCenturies)
+ {
+ return PolynomialSum(s_coefficients, julianCenturies);
+ }
+
+ internal static long GetNumberOfDays(DateTime date)
+ {
+ return date.Ticks / GregorianCalendar.TicksPerDay;
+ }
+
+ private static int GetGregorianYear(double numberOfDays)
+ {
+ return new DateTime(Math.Min((long)(Math.Floor(numberOfDays) * GregorianCalendar.TicksPerDay), DateTime.MaxValue.Ticks)).Year;
+ }
+
+ private enum CorrectionAlgorithm
+ {
+ Default,
+ Year1988to2019,
+ Year1900to1987,
+ Year1800to1899,
+ Year1700to1799,
+ Year1620to1699
+ }
+
+ private struct EphemerisCorrectionAlgorithmMap
+ {
+ public EphemerisCorrectionAlgorithmMap(int year, CorrectionAlgorithm algorithm)
+ {
+ _lowestYear = year;
+ _algorithm = algorithm;
+ }
+
+ internal int _lowestYear;
+ internal CorrectionAlgorithm _algorithm;
+ };
+
+ private static readonly EphemerisCorrectionAlgorithmMap[] s_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
+ };
+
+ private static double Reminder(double divisor, double dividend)
+ {
+ double whole = Math.Floor(divisor / dividend);
+ return divisor - (dividend * whole);
+ }
+
+ private static double NormalizeLongitude(double longitude)
+ {
+ longitude = Reminder(longitude, FullCircleOfArc);
+ if (longitude < 0)
+ {
+ longitude += FullCircleOfArc;
+ }
+ return longitude;
+ }
+
+ public static double AsDayFraction(double longitude)
+ {
+ return longitude / FullCircleOfArc;
+ }
+
+ private 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;
+ }
+
+ private static double CenturiesFrom1900(int gregorianYear)
+ {
+ long july1stOfYear = GetNumberOfDays(new DateTime(gregorianYear, 7, 1));
+ return (double)(july1stOfYear - s_startOf1900Century) / DaysInUniformLengthCentury;
+ }
+
+ // the following formulas defines a polynomial function which gives us the amount that the earth is slowing down for specific year ranges
+ private static double DefaultEphemerisCorrection(int gregorianYear)
+ {
+ Debug.Assert(gregorianYear < 1620 || 2020 <= gregorianYear);
+ long january1stOfYear = GetNumberOfDays(new DateTime(gregorianYear, 1, 1));
+ double daysSinceStartOf1810 = january1stOfYear - s_startOf1810;
+ double x = TwelveHours + daysSinceStartOf1810;
+ return ((Math.Pow(x, 2) / 41048480) - 15) / SecondsPerDay;
+ }
+
+ private static double EphemerisCorrection1988to2019(int gregorianYear)
+ {
+ Debug.Assert(1988 <= gregorianYear && gregorianYear <= 2019);
+ return (double)(gregorianYear - 1933) / SecondsPerDay;
+ }
+
+ private static double EphemerisCorrection1900to1987(int gregorianYear)
+ {
+ Debug.Assert(1900 <= gregorianYear && gregorianYear <= 1987);
+ double centuriesFrom1900 = CenturiesFrom1900(gregorianYear);
+ return PolynomialSum(s_coefficients1900to1987, centuriesFrom1900);
+ }
+
+ private static double EphemerisCorrection1800to1899(int gregorianYear)
+ {
+ Debug.Assert(1800 <= gregorianYear && gregorianYear <= 1899);
+ double centuriesFrom1900 = CenturiesFrom1900(gregorianYear);
+ return PolynomialSum(s_coefficients1800to1899, centuriesFrom1900);
+ }
+
+ private static double EphemerisCorrection1700to1799(int gregorianYear)
+ {
+ Debug.Assert(1700 <= gregorianYear && gregorianYear <= 1799);
+ double yearsSince1700 = gregorianYear - 1700;
+ return PolynomialSum(s_coefficients1700to1799, yearsSince1700) / SecondsPerDay;
+ }
+
+ private static double EphemerisCorrection1620to1699(int gregorianYear)
+ {
+ Debug.Assert(1620 <= gregorianYear && gregorianYear <= 1699);
+ double yearsSince1600 = gregorianYear - 1600;
+ return PolynomialSum(s_coefficients1620to1699, yearsSince1600) / SecondsPerDay;
+ }
+
+ // ephemeris-correction: correction to account for the slowing down of the rotation of the earth
+ private static double EphemerisCorrection(double time)
+ {
+ int year = GetGregorianYear(time);
+ foreach (EphemerisCorrectionAlgorithmMap map in s_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
+ }
+ }
+
+ Debug.Assert(false, "Not expected to come here");
+ return DefaultEphemerisCorrection(year);
+ }
+
+ public static double JulianCenturies(double moment)
+ {
+ double dynamicalMoment = moment + EphemerisCorrection(moment);
+ return (dynamicalMoment - Noon2000Jan01) / DaysInUniformLengthCentury;
+ }
+
+ private static bool IsNegative(double value)
+ {
+ return Math.Sign(value) == -1;
+ }
+
+ private 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
+ private static double EquationOfTime(double time)
+ {
+ double julianCenturies = JulianCenturies(time);
+ double lambda = PolynomialSum(s_lambdaCoefficients, julianCenturies);
+ double anomaly = PolynomialSum(s_anomalyCoefficients, julianCenturies);
+ double eccentricity = PolynomialSum(s_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);
+ }
+
+ private 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
+ public static double Midday(double date, double longitude)
+ {
+ return AsLocalTime(date + TwelveHours, longitude) - AsDayFraction(longitude);
+ }
+
+ private static double InitLongitude(double longitude)
+ {
+ return NormalizeLongitude(longitude + HalfCircleOfArc) - HalfCircleOfArc;
+ }
+
+ // midday-in-tehran
+ public static double MiddayAtPersianObservationSite(double date)
+ {
+ return Midday(date, InitLongitude(52.5)); // 52.5 degrees east - longitude of UTC+3:30 which defines Iranian Standard Time
+ }
+
+ private static double PeriodicTerm(double julianCenturies, int x, double y, double z)
+ {
+ return x * SinOfDegree(y + z * julianCenturies);
+ }
+
+ private 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;
+ }
+
+ private static double Aberration(double julianCenturies)
+ {
+ return (0.0000974 * CosOfDegree(177.63 + (35999.01848 * julianCenturies))) - 0.005575;
+ }
+
+ private static double Nutation(double julianCenturies)
+ {
+ double a = PolynomialSum(s_coefficientsA, julianCenturies);
+ double b = PolynomialSum(s_coefficientsB, julianCenturies);
+ return (-0.004778 * SinOfDegree(a)) - (0.0003667 * SinOfDegree(b));
+ }
+
+ public static 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);
+ }
+
+ public static double AsSeason(double longitude)
+ {
+ return (longitude < 0) ? (longitude + FullCircleOfArc) : longitude;
+ }
+
+ private 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;
+ }
+ }
+ Debug.Assert(day != upperBoundNewYearDay);
+
+ return day - 1;
+ }
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/ChineseLunisolarCalendar.cs b/src/mscorlib/shared/System/Globalization/ChineseLunisolarCalendar.cs
new file mode 100644
index 0000000000..404add0936
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/ChineseLunisolarCalendar.cs
@@ -0,0 +1,390 @@
+// 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.Diagnostics.Contracts;
+
+namespace System.Globalization
+{
+ /*
+ ** 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 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);
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (minDate);
+ }
+ }
+
+
+ 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;
+ }
+ }
+
+
+ private static readonly int[,] s_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,
+ SR.ArgumentOutOfRange_Range, MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR));
+ }
+ Contract.EndContractBlock();
+
+ return s_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(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ if (year < MIN_LUNISOLAR_YEAR || year > MAX_LUNISOLAR_YEAR)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range, MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR));
+ }
+ Contract.EndContractBlock();
+
+ return year;
+ }
+
+ public ChineseLunisolarCalendar()
+ {
+ }
+
+ public override int GetEra(DateTime time)
+ {
+ CheckTicksRange(time.Ticks);
+ return (ChineseEra);
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return (CalendarId.CHINESELUNISOLAR);
+ }
+ }
+
+ internal override CalendarId BaseCalendarID
+ {
+ get
+ {
+ //Use CAL_GREGORIAN just to get CurrentEraValue as 1 since we do not have data under the ID CAL_ChineseLunisolar yet
+ return (CalendarId.GREGORIAN);
+ }
+ }
+
+
+ public override int[] Eras
+ {
+ get
+ {
+ return (new int[] { ChineseEra });
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/CultureNotFoundException.cs b/src/mscorlib/shared/System/Globalization/CultureNotFoundException.cs
new file mode 100644
index 0000000000..929f4bb000
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/CultureNotFoundException.cs
@@ -0,0 +1,120 @@
+// 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.Runtime.Serialization;
+
+namespace System.Globalization
+{
+ [Serializable]
+ public class CultureNotFoundException : ArgumentException
+ {
+ private string _invalidCultureName; // unrecognized culture name
+ private int? _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, string invalidCultureName, String message)
+ : base(message, paramName)
+ {
+ _invalidCultureName = invalidCultureName;
+ }
+
+ public CultureNotFoundException(String message, string invalidCultureName, Exception innerException)
+ : base(message, innerException)
+ {
+ _invalidCultureName = invalidCultureName;
+ }
+
+ public CultureNotFoundException(string message, int invalidCultureId, Exception innerException)
+ : base(message, innerException)
+ {
+ _invalidCultureId = invalidCultureId;
+ }
+
+ public CultureNotFoundException(string paramName, int invalidCultureId, string message)
+ : base(message, paramName)
+ {
+ _invalidCultureId = invalidCultureId;
+ }
+
+ protected CultureNotFoundException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ _invalidCultureId = (int?)info.GetValue("InvalidCultureId", typeof(int?));
+ _invalidCultureName = (string)info.GetValue("InvalidCultureName", typeof(string));
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("InvalidCultureId", _invalidCultureId, typeof(int?));
+ info.AddValue("InvalidCultureName", _invalidCultureName, typeof(string));
+ }
+
+ public virtual Nullable<int> InvalidCultureId
+ {
+ get { return _invalidCultureId; }
+ }
+
+ public virtual string InvalidCultureName
+ {
+ get { return _invalidCultureName; }
+ }
+
+ private static String DefaultMessage
+ {
+ get
+ {
+ return SR.Argument_CultureNotSupported;
+ }
+ }
+
+ private String FormatedInvalidCultureId
+ {
+ get
+ {
+ return InvalidCultureId != null ?
+ String.Format(CultureInfo.InvariantCulture, "{0} (0x{0:x4})", (int)InvalidCultureId) :
+ InvalidCultureName;
+ }
+ }
+
+ public override String Message
+ {
+ get
+ {
+ String s = base.Message;
+ if (_invalidCultureId != null || _invalidCultureName != null)
+ {
+ String valueMessage = SR.Format(SR.Argument_CultureInvalidIdentifier, FormatedInvalidCultureId);
+ if (s == null)
+ {
+ return valueMessage;
+ }
+
+ return s + Environment.NewLine + valueMessage;
+ }
+ return s;
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/CultureTypes.cs b/src/mscorlib/shared/System/Globalization/CultureTypes.cs
new file mode 100644
index 0000000000..f52ac82a83
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/CultureTypes.cs
@@ -0,0 +1,27 @@
+// 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().
+// On Linux platforms, the only enum values used there is NeutralCultures and SpecificCultures
+// the rest are obsolete or not valid on Linux
+
+namespace System.Globalization
+{
+ [Flags]
+ 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/shared/System/Globalization/DateTimeFormat.cs b/src/mscorlib/shared/System/Globalization/DateTimeFormat.cs
new file mode 100644
index 0000000000..840409f55a
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/DateTimeFormat.cs
@@ -0,0 +1,1206 @@
+// 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;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Text;
+
+namespace System
+{
+ /*
+ 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 readonly DateTimeFormatInfo InvariantFormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
+ internal static readonly string[] InvariantAbbreviatedMonthNames = InvariantFormatInfo.AbbreviatedMonthNames;
+ internal static readonly string[] InvariantAbbreviatedDayNames = InvariantFormatInfo.AbbreviatedDayNames;
+ internal const string Gmt = "GMT";
+
+ 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)
+ {
+ Debug.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0");
+ FormatDigits(outputBuffer, value, len, false);
+ }
+
+ internal unsafe static void FormatDigits(StringBuilder outputBuffer, int value, int len, bool overrideLengthLimit)
+ {
+ Debug.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)
+ {
+ Debug.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)
+ {
+ Debug.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)
+ {
+ Debug.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(SR.Format_InvalidString);
+ }
+ }
+ else
+ {
+ result.Append(ch);
+ }
+ }
+
+ if (!foundQuote)
+ {
+ // Here we can't find the matching quote.
+ throw new FormatException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.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 == CalendarId.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(SR.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 == CalendarId.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(SR.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(SR.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)
+ {
+ offset = TimeSpan.Zero;
+ }
+ 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();
+ }
+
+ AppendNumber(result, offset.Hours, 2);
+ result.Append(':');
+ AppendNumber(result, offset.Minutes, 2);
+ }
+
+
+ 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(SR.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(SR.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 CalendarId.JAPAN:
+ case CalendarId.TAIWAN:
+ case CalendarId.HIJRI:
+ case CalendarId.HEBREW:
+ case CalendarId.JULIAN:
+ case CalendarId.UMALQURA:
+ case CalendarId.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)
+ {
+ switch (format[0])
+ {
+ case 'O':
+ case 'o':
+ return FastFormatRoundtrip(dateTime, offset);
+ case 'R':
+ case 'r':
+ return FastFormatRfc1123(dateTime, offset, dtfi);
+ }
+
+ format = ExpandPredefinedFormat(format, ref dateTime, ref dtfi, ref offset);
+ }
+
+ return (FormatCustomized(dateTime, format, dtfi, offset));
+ }
+
+ internal static string FastFormatRfc1123(DateTime dateTime, TimeSpan offset, DateTimeFormatInfo dtfi)
+ {
+ // ddd, dd MMM yyyy HH:mm:ss GMT
+ const int Rfc1123FormatLength = 29;
+ StringBuilder result = StringBuilderCache.Acquire(Rfc1123FormatLength);
+
+ if (offset != NullOffset)
+ {
+ // Convert to UTC invariants
+ dateTime = dateTime - offset;
+ }
+
+ result.Append(InvariantAbbreviatedDayNames[(int)dateTime.DayOfWeek]);
+ result.Append(',');
+ result.Append(' ');
+ AppendNumber(result, dateTime.Day, 2);
+ result.Append(' ');
+ result.Append(InvariantAbbreviatedMonthNames[dateTime.Month - 1]);
+ result.Append(' ');
+ AppendNumber(result, dateTime.Year, 4);
+ result.Append(' ');
+ AppendHHmmssTimeOfDay(result, dateTime);
+ result.Append(' ');
+ result.Append(Gmt);
+
+ return StringBuilderCache.GetStringAndRelease(result);
+ }
+
+ internal static string FastFormatRoundtrip(DateTime dateTime, TimeSpan offset)
+ {
+ // yyyy-MM-ddTHH:mm:ss.fffffffK
+ const int roundTripFormatLength = 28;
+ StringBuilder result = StringBuilderCache.Acquire(roundTripFormatLength);
+
+ AppendNumber(result, dateTime.Year, 4);
+ result.Append('-');
+ AppendNumber(result, dateTime.Month, 2);
+ result.Append('-');
+ AppendNumber(result, dateTime.Day, 2);
+ result.Append('T');
+ AppendHHmmssTimeOfDay(result, dateTime);
+ result.Append('.');
+
+ long fraction = dateTime.Ticks % TimeSpan.TicksPerSecond;
+ AppendNumber(result, fraction, 7);
+
+ FormatCustomizedRoundripTimeZone(dateTime, offset, result);
+
+ return StringBuilderCache.GetStringAndRelease(result);
+ }
+
+ private static void AppendHHmmssTimeOfDay(StringBuilder result, DateTime dateTime)
+ {
+ // HH:mm:ss
+ AppendNumber(result, dateTime.Hour, 2);
+ result.Append(':');
+ AppendNumber(result, dateTime.Minute, 2);
+ result.Append(':');
+ AppendNumber(result, dateTime.Second, 2);
+ }
+
+ internal static void AppendNumber(StringBuilder builder, long val, int digits)
+ {
+ for (int i = 0; i < digits; i++)
+ {
+ builder.Append('0');
+ }
+
+ int index = 1;
+ while (val > 0 && index <= digits)
+ {
+ builder[builder.Length - index] = (char)('0' + (val % 10));
+ val = val / 10;
+ index++;
+ }
+
+ Debug.Assert(val == 0, "DateTimeFormat.AppendNumber(): digits less than size of val");
+ }
+
+ 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(SR.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)
+ {
+ }
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs b/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs
new file mode 100644
index 0000000000..d3f1ea9a45
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs
@@ -0,0 +1,3028 @@
+// 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;
+using System.Diagnostics.Contracts;
+using System.Runtime.Serialization;
+
+namespace System.Globalization
+{
+ //
+ // 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]
+ public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable
+ {
+ // cache for the invariant culture.
+ // invariantInfo is constant irrespective of your current culture.
+ private static volatile DateTimeFormatInfo s_invariantInfo;
+
+ // an index which points to a record in Culture Data Table.
+ [NonSerialized]
+ private CultureData _cultureData;
+
+ // The culture name used to create this DTFI.
+
+ [OptionalField(VersionAdded = 2)]
+ private String _name = null;
+
+ // The language name of the culture used to create this DTFI.
+ [NonSerialized]
+ private String _langName = null;
+
+ // CompareInfo usually used by the parser.
+ [NonSerialized]
+ private CompareInfo _compareInfo = null;
+
+ // Culture matches current DTFI. mainly used for string comparisons during parsing.
+ [NonSerialized]
+ private CultureInfo _cultureInfo = null;
+
+ //
+ // Caches for various properties.
+ //
+
+ private String amDesignator = null;
+ private String pmDesignator = null;
+
+ private String dateSeparator = null; // derived from short date (whidbey expects, arrowhead doesn't)
+
+ private String generalShortTimePattern = null; // short date + short time (whidbey expects, arrowhead doesn't)
+
+ private String generalLongTimePattern = null; // short date + long time (whidbey expects, arrowhead doesn't)
+
+ private String timeSeparator = null; // derived from long time (whidbey expects, arrowhead doesn't)
+ private String monthDayPattern = null;
+ // added in .NET Framework Release {2.0SP1/3.0SP1/3.5RTM}
+ private String dateTimeOffsetPattern = null;
+
+ //
+ // The following are constant values.
+ //
+ private const String rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
+
+ // The sortable pattern is based on ISO 8601.
+ private const String sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
+ private const String universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
+
+ //
+ // The following are affected by calendar settings.
+ //
+ private Calendar calendar = null;
+
+ private int firstDayOfWeek = -1;
+ private int calendarWeekRule = -1;
+
+
+ private String fullDateTimePattern = null; // long date + long time (whidbey expects, arrowhead doesn't)
+
+ private String[] abbreviatedDayNames = null;
+
+
+ private String[] m_superShortDayNames = null;
+
+ private String[] dayNames = null;
+ private String[] abbreviatedMonthNames = null;
+ private String[] monthNames = null;
+ // Cache the genitive month names that we retrieve from the data table.
+
+ private String[] genitiveMonthNames = null;
+
+ // Cache the abbreviated genitive month names that we retrieve from the data table.
+
+ private String[] m_genitiveAbbreviatedMonthNames = null;
+
+ // Cache the month names of a leap year that we retrieve from the data table.
+
+ private 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
+ private String longDatePattern = null;
+ private String shortDatePattern = null;
+ private String yearMonthPattern = null;
+ private String longTimePattern = null;
+ private String shortTimePattern = null;
+
+ [OptionalField(VersionAdded = 3)]
+ private String[] allYearMonthPatterns = null;
+
+ private String[] allShortDatePatterns = null;
+ private String[] allLongDatePatterns = null;
+ private String[] allShortTimePatterns = null;
+ private String[] allLongTimePatterns = null;
+
+ // Cache the era names for this DateTimeFormatInfo instance.
+ private String[] m_eraNames = null;
+ private String[] m_abbrevEraNames = null;
+ private String[] m_abbrevEnglishEraNames = null;
+
+ private CalendarId[] optionalCalendars = null;
+
+ private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
+
+ // CultureInfo updates this
+ internal bool _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.
+
+ private DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized;
+
+ private String CultureName
+ {
+ get
+ {
+ if (_name == null)
+ {
+ _name = _cultureData.CultureName;
+ }
+ return (_name);
+ }
+ }
+
+ private CultureInfo Culture
+ {
+ get
+ {
+ if (_cultureInfo == null)
+ {
+ _cultureInfo = CultureInfo.GetCultureInfo(this.CultureName);
+ }
+ return _cultureInfo;
+ }
+ }
+
+ // TODO: This ignores other cultures that might want to do something similar
+ private String LanguageName
+ {
+ get
+ {
+ if (_langName == null)
+ {
+ _langName = _cultureData.SISO639LANGNAME;
+ }
+ return (_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 = _cultureData.AbbreviatedDayNames(Calendar.ID);
+ Debug.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 = _cultureData.SuperShortDayNames(Calendar.ID);
+ Debug.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 = _cultureData.DayNames(Calendar.ID);
+ Debug.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 = _cultureData.AbbreviatedMonthNames(Calendar.ID);
+ Debug.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 = _cultureData.MonthNames(Calendar.ID);
+ Debug.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._cultureData, GregorianCalendar.GetDefaultInstance())
+ {
+ }
+
+ internal DateTimeFormatInfo(CultureData cultureData, Calendar cal)
+ {
+ Debug.Assert(cultureData != null);
+ Debug.Assert(cal != null);
+
+ // Remember our culture
+ _cultureData = cultureData;
+
+ this.Calendar = cal;
+ }
+
+ private void InitializeOverridableProperties(CultureData cultureData, CalendarId calendarId)
+ {
+ Debug.Assert(cultureData != null);
+ Debug.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initalized calendarId");
+
+ 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 = _cultureData.LongTimes;
+ Debug.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns");
+
+ this.allShortTimePatterns = _cultureData.ShortTimes;
+ Debug.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns");
+
+ this.allLongDatePatterns = cultureData.LongDates(calendarId);
+ Debug.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns");
+
+ this.allShortDatePatterns = cultureData.ShortDates(calendarId);
+ Debug.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns");
+
+ this.allYearMonthPatterns = cultureData.YearMonths(calendarId);
+ Debug.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns");
+ }
+
+ [OptionalField(VersionAdded = 1)]
+ private bool _useUserOverride;
+
+ // 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
+
+ internal String[] m_dateWords = null;
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+ _name = this.CultureName; // make sure the _name is initialized.
+ _useUserOverride = _cultureData.UseUserOverride;
+
+ // 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;
+ }
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ if (_name != null)
+ {
+ _cultureData = CultureData.GetCultureData(_name, _useUserOverride);
+ if (_cultureData == null)
+ {
+ throw new CultureNotFoundException("_name", _name, SR.Argument_CultureNotSupported);
+ }
+ }
+
+ if (calendar == null)
+ {
+ calendar = (Calendar)GregorianCalendar.GetDefaultInstance().Clone();
+ calendar.SetReadOnlyState(_isReadOnly);
+ }
+
+ InitializeOverridableProperties(_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 = _isReadOnly;
+ _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;
+
+ _isReadOnly = isReadOnly;
+ }
+
+ // 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 (s_invariantInfo == null)
+ {
+ DateTimeFormatInfo info = new DateTimeFormatInfo();
+ info.Calendar.SetReadOnlyState(true);
+ info._isReadOnly = true;
+ s_invariantInfo = info;
+ }
+ return (s_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.Globalization.CultureInfo.CurrentCulture;
+ if (!culture._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._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._isReadOnly = false;
+ return n;
+ }
+
+
+ public String AMDesignator
+ {
+ get
+ {
+ if (this.amDesignator == null)
+ {
+ this.amDesignator = _cultureData.SAM1159;
+ }
+ Debug.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null");
+ return (this.amDesignator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+ Contract.EndContractBlock();
+ ClearTokenHashTable();
+ amDesignator = value;
+ }
+ }
+
+
+ public Calendar Calendar
+ {
+ get
+ {
+ Contract.Ensures(Contract.Result<Calendar>() != null);
+
+ Debug.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null");
+ return (this.calendar);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj);
+ }
+ Contract.EndContractBlock();
+ if (value == calendar)
+ {
+ return;
+ }
+
+ 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
+
+ // remember to reload tokens
+ ClearTokenHashTable();
+ }
+
+ // Remember the new calendar
+ calendar = value;
+ InitializeOverridableProperties(_cultureData, calendar.ID);
+
+ // We succeeded, return
+ return;
+ }
+ }
+
+ // The assigned calendar is not a valid calendar for this culture, throw
+ throw new ArgumentOutOfRangeException(nameof(value), SR.Argument_InvalidCalendar);
+ }
+ }
+
+ private CalendarId[] OptionalCalendars
+ {
+ get
+ {
+ if (this.optionalCalendars == null)
+ {
+ this.optionalCalendars = _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(nameof(eraName),
+ SR.ArgumentNull_String);
+ }
+ Contract.EndContractBlock();
+
+ // 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 (this.Culture.CompareInfo.Compare(eraName, m_eraNames[i], 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 (this.Culture.CompareInfo.Compare(eraName, m_abbrevEraNames[i], 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 (CultureInfo.InvariantCulture.CompareInfo.Compare(eraName, m_abbrevEnglishEraNames[i], CompareOptions.IgnoreCase) == 0)
+ {
+ return (i + 1);
+ }
+ }
+ return (-1);
+ }
+
+
+ internal String[] EraNames
+ {
+ get
+ {
+ if (this.m_eraNames == null)
+ {
+ this.m_eraNames = _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(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ internal String[] AbbreviatedEraNames
+ {
+ get
+ {
+ if (this.m_abbrevEraNames == null)
+ {
+ this.m_abbrevEraNames = _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(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ internal String[] AbbreviatedEnglishEraNames
+ {
+ get
+ {
+ if (this.m_abbrevEnglishEraNames == null)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0");
+ this.m_abbrevEnglishEraNames = _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 (dateSeparator == null)
+ {
+ dateSeparator = _cultureData.DateSeparator(Calendar.ID);
+ }
+ Debug.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null");
+ return dateSeparator;
+ }
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String);
+ }
+ Contract.EndContractBlock();
+ ClearTokenHashTable();
+ dateSeparator = value;
+ }
+ }
+
+ public DayOfWeek FirstDayOfWeek
+ {
+ get
+ {
+ if (this.firstDayOfWeek == -1)
+ {
+ this.firstDayOfWeek = _cultureData.IFIRSTDAYOFWEEK;
+ }
+ Debug.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1");
+
+ return ((DayOfWeek)this.firstDayOfWeek);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value >= DayOfWeek.Sunday && value <= DayOfWeek.Saturday)
+ {
+ firstDayOfWeek = (int)value;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(value), SR.Format(SR.ArgumentOutOfRange_Range,
+ DayOfWeek.Sunday, DayOfWeek.Saturday));
+ }
+ }
+ }
+
+ public CalendarWeekRule CalendarWeekRule
+ {
+ get
+ {
+ if (this.calendarWeekRule == -1)
+ {
+ this.calendarWeekRule = _cultureData.IFIRSTWEEKOFYEAR;
+ }
+ Debug.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1");
+ return ((CalendarWeekRule)this.calendarWeekRule);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value >= CalendarWeekRule.FirstDay && value <= CalendarWeekRule.FirstFourDayWeek)
+ {
+ calendarWeekRule = (int)value;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(value), SR.Format(SR.ArgumentOutOfRange_Range,
+ CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));
+ }
+ }
+ }
+
+ public String FullDateTimePattern
+ {
+ get
+ {
+ if (fullDateTimePattern == null)
+ {
+ fullDateTimePattern = LongDatePattern + " " + LongTimePattern;
+ }
+ return (fullDateTimePattern);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.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(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.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(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.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)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0");
+ this.monthDayPattern = _cultureData.MonthDay(Calendar.ID);
+ }
+ Debug.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null");
+ return (this.monthDayPattern);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_String);
+ }
+ Contract.EndContractBlock();
+
+ this.monthDayPattern = value;
+ }
+ }
+
+
+ public String PMDesignator
+ {
+ get
+ {
+ if (this.pmDesignator == null)
+ {
+ this.pmDesignator = _cultureData.SPM2359;
+ }
+ Debug.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null");
+ return (this.pmDesignator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.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(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ throw new ArgumentNullException(nameof(value),
+ SR.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(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.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 (timeSeparator == null)
+ {
+ timeSeparator = _cultureData.TimeSeparator;
+ }
+ Debug.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null");
+ return (timeSeparator);
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value), SR.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(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.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"
+ //
+ private static void CheckNullValue(String[] values, int length)
+ {
+ Debug.Assert(values != null, "value != null");
+ Debug.Assert(values.Length >= length);
+ for (int i = 0; i < length; i++)
+ {
+ if (values[i] == null)
+ {
+ throw new ArgumentNullException("value",
+ SR.ArgumentNull_ArrayValue);
+ }
+ }
+ }
+
+
+ public String[] AbbreviatedDayNames
+ {
+ get
+ {
+ return ((String[])internalGetAbbreviatedDayOfWeekNames().Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length);
+ ClearTokenHashTable();
+
+ abbreviatedDayNames = value;
+ }
+ }
+
+ // Returns the string array of the one-letter day of week names.
+ public String[] ShortestDayNames
+ {
+ get
+ {
+ return ((String[])internalGetSuperShortDayNames().Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(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(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 7)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value));
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length);
+ ClearTokenHashTable();
+
+ dayNames = value;
+ }
+ }
+
+
+ public String[] AbbreviatedMonthNames
+ {
+ get
+ {
+ return ((String[])internalGetAbbreviatedMonthNames().Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(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(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(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.
+ private static readonly char[] s_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(
+ nameof(month), SR.Format(SR.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 = _cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID);
+ Debug.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 = _cultureData.GenitiveMonthNames(this.Calendar.ID);
+ Debug.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)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0");
+ this.leapYearMonthNames = _cultureData.LeapYearMonthNames(Calendar.ID);
+ Debug.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(
+ nameof(dayofweek), SR.Format(SR.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.
+ public string GetShortestDayName(DayOfWeek dayOfWeek)
+ {
+ if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(dayOfWeek), SR.Format(SR.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
+ private static String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString)
+ {
+ Debug.Assert(patterns1 != null);
+ Debug.Assert(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[] { 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(SR.Format_BadFormatSpecifier, nameof(format));
+ }
+ return (result);
+ }
+
+
+ public String GetDayName(DayOfWeek dayofweek)
+ {
+ if ((int)dayofweek < 0 || (int)dayofweek > 6)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(dayofweek), SR.Format(SR.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(
+ nameof(month), SR.Format(SR.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(
+ nameof(month), SR.Format(SR.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)
+ {
+ Debug.Assert(patterns != null && patterns.Length > 0,
+ "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern");
+ Debug.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;
+ }
+
+ // Needed by DateTimeFormatInfo and DateTimeFormat
+ internal const String RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
+ internal const String RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz";
+
+ // 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 (allYearMonthPatterns == null)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0");
+ this.allYearMonthPatterns = _cultureData.YearMonths(this.Calendar.ID);
+ Debug.Assert(this.allYearMonthPatterns.Length > 0,
+ "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns");
+ }
+
+ return 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)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0");
+ this.allShortDatePatterns = _cultureData.ShortDates(this.Calendar.ID);
+ Debug.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)
+ {
+ Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0");
+ this.allLongDatePatterns = _cultureData.LongDates(this.Calendar.ID);
+ Debug.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 = _cultureData.ShortTimes;
+ Debug.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 = _cultureData.LongTimes;
+ Debug.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(nameof(dtfi),
+ SR.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._isReadOnly = true;
+ return (newInfo);
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return (_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.
+ public string NativeCalendarName
+ {
+ get
+ {
+ return _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.
+ //
+ public void SetAllDateTimePatterns(String[] patterns, char format)
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+
+ if (patterns == null)
+ {
+ throw new ArgumentNullException(nameof(patterns), SR.ArgumentNull_Array);
+ }
+
+ if (patterns.Length == 0)
+ {
+ throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(patterns));
+ }
+
+ Contract.EndContractBlock();
+
+ for (int i = 0; i < patterns.Length; i++)
+ {
+ if (patterns[i] == null)
+ {
+ throw new ArgumentNullException("patterns[" + i + "]", SR.ArgumentNull_ArrayValue);
+ }
+ }
+
+ // Remember the patterns, and use the 1st as default
+ switch (format)
+ {
+ case 'd':
+ allShortDatePatterns = patterns;
+ shortDatePattern = allShortDatePatterns[0];
+ break;
+
+ case 'D':
+ allLongDatePatterns = patterns;
+ longDatePattern = allLongDatePatterns[0];
+ break;
+
+ case 't':
+ allShortTimePatterns = patterns;
+ shortTimePattern = allShortTimePatterns[0];
+ break;
+
+ case 'T':
+ allLongTimePatterns = patterns;
+ longTimePattern = allLongTimePatterns[0];
+ break;
+
+ case 'y':
+ case 'Y':
+ allYearMonthPatterns = patterns;
+ yearMonthPattern = allYearMonthPatterns[0];
+ break;
+
+ default:
+ throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format));
+ }
+
+ // Clear the token hash table, note that even short dates could require this
+ ClearTokenHashTable();
+ }
+
+ public String[] AbbreviatedMonthGenitiveNames
+ {
+ get
+ {
+ return ((String[])internalGetGenitiveMonthNames(true).Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length - 1);
+ ClearTokenHashTable();
+ this.m_genitiveAbbreviatedMonthNames = value;
+ }
+ }
+
+ public String[] MonthGenitiveNames
+ {
+ get
+ {
+ return ((String[])internalGetGenitiveMonthNames(false).Clone());
+ }
+
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value),
+ SR.ArgumentNull_Array);
+ }
+ if (value.Length != 13)
+ {
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value));
+ }
+ Contract.EndContractBlock();
+ CheckNullValue(value, value.Length - 1);
+ genitiveMonthNames = value;
+ ClearTokenHashTable();
+ }
+ }
+
+ //
+ // Positive TimeSpan Pattern
+ //
+ [NonSerialized]
+ private string _fullTimeSpanPositivePattern;
+ internal String FullTimeSpanPositivePattern
+ {
+ get
+ {
+ if (_fullTimeSpanPositivePattern == null)
+ {
+ CultureData cultureDataWithoutUserOverrides;
+ if (_cultureData.UseUserOverride)
+ cultureDataWithoutUserOverrides = CultureData.GetCultureData(_cultureData.CultureName, false);
+ else
+ cultureDataWithoutUserOverrides = _cultureData;
+ String decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator;
+
+ _fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF";
+ }
+ return _fullTimeSpanPositivePattern;
+ }
+ }
+
+ //
+ // Negative TimeSpan Pattern
+ //
+ [NonSerialized]
+ private string _fullTimeSpanNegativePattern;
+ internal String FullTimeSpanNegativePattern
+ {
+ get
+ {
+ if (_fullTimeSpanNegativePattern == null)
+ _fullTimeSpanNegativePattern = "'-'" + FullTimeSpanPositivePattern;
+ return _fullTimeSpanNegativePattern;
+ }
+ }
+
+ //
+ // Get suitable CompareInfo from current DTFI object.
+ //
+ internal CompareInfo CompareInfo
+ {
+ get
+ {
+ if (_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 _cultureData.
+ _compareInfo = CompareInfo.GetCompareInfo(_cultureData.SCOMPAREINFO);
+ }
+
+ return _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(SR.Argument_InvalidDateTimeStyles, parameterName);
+ }
+ if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0))
+ {
+ throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName);
+ }
+ Contract.EndContractBlock();
+ if (((style & DateTimeStyles.RoundtripKind) != 0)
+ && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0))
+ {
+ throw new ArgumentException(SR.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)
+ {
+ // Handle Japanese and Taiwan cases.
+ // 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 (CalendarId.JAPAN):
+ case (CalendarId.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]
+ private TokenHashValue[] _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 GMTName = "GMT";
+ internal const String ZuluName = "Z";
+
+ 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);
+ }
+
+ // Create a Taiwan DTFI which uses TaiwanCalendar. This is used to parse
+ // date string with era name correctly even when the supplied DTFI
+ // does not use Taiwan calendar.
+ // The created instance is stored in global s_zhtwDTFI.
+ 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()
+ {
+ _dtfiTokenHash = null;
+ formatFlags = DateTimeFormatFlags.NotInitialized;
+ }
+
+ internal TokenHashValue[] CreateTokenHashTable()
+ {
+ TokenHashValue[] temp = _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);
+
+ // TODO: This ignores similar custom cultures
+ 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);
+
+ // TODO: This ignores other custom cultures that might want to do something similar
+ 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();
+ 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 ignorable symbol.
+ useDateSepAsIgnorableSymbol = true;
+ }
+ break;
+ default:
+ InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0);
+ // TODO: This ignores similar custom cultures
+ 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);
+ }
+
+ // TODO: This ignores other cultures that might want to do something similar
+ 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);
+ }
+ }
+ }
+ // TODO: This prohibits similar custom cultures, but we hard coded the name
+ 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, GMTName, TokenType.TimeZoneToken, 0);
+ InsertHash(temp, ZuluName, TokenType.TimeZoneToken, 0);
+
+ InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0);
+ InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0);
+
+ _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.
+ Debug.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');
+ }
+
+ internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue,
+ ref __DTString str)
+ {
+ tokenType = TokenType.UnknownToken;
+ tokenValue = 0;
+
+ TokenHashValue value;
+ Debug.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length");
+
+ char ch = str.m_current;
+ bool isLetter = Char.IsLetter(ch);
+ if (isLetter)
+ {
+ ch = this.Culture.TextInfo.ToLower(ch);
+ 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 = _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)
+ {
+ bool compareStrings = true;
+ 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.
+ // Also an optimization to avoid string comparison
+ int nextCharIndex = str.Index + value.tokenString.Length;
+ if (nextCharIndex > str.len)
+ {
+ compareStrings = false;
+ }
+ else if (nextCharIndex < str.len)
+ {
+ // Check word boundary. The next character should NOT be a letter.
+ char nextCh = str.Value[nextCharIndex];
+ compareStrings = !(Char.IsLetter(nextCh));
+ }
+ }
+ if (compareStrings && CompareStringIgnoreCaseOptimized(str.Value, str.Index, value.tokenString.Length, value.tokenString, 0, value.tokenString.Length))
+ {
+ tokenType = value.tokenType & TokenMask;
+ tokenValue = value.tokenValue;
+ str.Advance(value.tokenString.Length);
+ return (true);
+ }
+ else if ((value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) ||
+ (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames))
+ {
+ // For month or day token, we will match the 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);
+ }
+
+ private 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 && this.Culture.TextInfo.ToLower(temp.tokenString[0]) != 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;
+ };
+ Debug.Assert(false, "The hashtable is full. This should not happen.");
+ }
+
+ private 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 = this.Culture.TextInfo.ToLower(str[0]);
+ 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 (this.CompareStringIgnoreCaseOptimized(str, 0, value.tokenString.Length, value.tokenString, 0, value.tokenString.Length))
+ {
+ 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 folowing is the fix for the issue of throwing FormatException when "mar" is passed in string of the short date format dd/MMM/yyyy for es-MX
+ //
+
+ 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;
+ }
+ }
+ // The token to be inserted is already in the table. Skip it.
+ return;
+ }
+ }
+ }
+ }
+ //// 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);
+ Debug.Assert(false, "The hashtable is full. This should not happen.");
+ }
+
+ private bool CompareStringIgnoreCaseOptimized(string string1, int offset1, int length1, string string2, int offset2, int length2)
+ {
+ // Optimize for one character cases which are common due to date and time separators (/ and :)
+ if (length1 == 1 && length2 == 1 && string1[offset1] == string2[offset2])
+ {
+ return true;
+ }
+
+ return (this.Culture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, CompareOptions.IgnoreCase) == 0);
+ }
+
+ // 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/shared/System/Globalization/DateTimeFormatInfoScanner.cs b/src/mscorlib/shared/System/Globalization/DateTimeFormatInfoScanner.cs
new file mode 100644
index 0000000000..15af1b7d84
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/DateTimeFormatInfoScanner.cs
@@ -0,0 +1,739 @@
+// 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.
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System.Collections.Generic;
+using System.Text;
+
+namespace System.Globalization
+{
+
+#if CORECLR
+ using StringStringDictionary = Dictionary<string, string>;
+ using StringList = List<string>;
+#else
+ using StringStringDictionary = LowLevelDictionary<string, string>;
+ using StringList = LowLevelList<string>;
+#endif
+
+ //
+ // 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.
+ }
+
+ internal enum CalendarId : ushort
+ {
+ UNINITIALIZED_VALUE = 0,
+ 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 StringList m_dateWords = new StringList();
+ // Hashtable for the known words.
+ private static volatile StringStringDictionary s_knownWords;
+
+ static StringStringDictionary KnownWords
+ {
+ get
+ {
+ if (s_knownWords == null)
+ {
+ StringStringDictionary temp = new StringStringDictionary();
+ // 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 StringList();
+ }
+ 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 StringList();
+ }
+ // Add the ignorable 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.
+ //
+ private 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.
+ private FoundDatePattern _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.
+ _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");
+ }
+ }
+ _ymdFlags |= FoundDatePattern.FoundMonthPatternFlag;
+ break;
+ case 'y':
+ i = ScanRepeatChar(pattern, 'y', i, out chCount);
+ _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.
+ _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 (_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(".");
+ _ymdFlags = FoundDatePattern.None;
+ }
+ i++;
+ break;
+ default:
+ if (_ymdFlags == FoundDatePattern.FoundYMDPatternFlag && !Char.IsWhiteSpace(ch))
+ {
+ // We are not seeing "." after YMD. Clear the flag.
+ _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.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ 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);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // 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/shared/System/Globalization/DateTimeParse.cs b/src/mscorlib/shared/System/Globalization/DateTimeParse.cs
new file mode 100644
index 0000000000..910fbf2ff0
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/DateTimeParse.cs
@@ -0,0 +1,5672 @@
+// 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.Diagnostics;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Text;
+
+namespace System
+{
+ 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);
+
+ 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, nameof(s));
+ return false;
+ }
+ if (format == null)
+ {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, nameof(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;
+ }
+
+ Debug.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, nameof(s));
+ return false;
+ }
+ if (formats == null)
+ {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, nameof(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;
+ }
+
+ Debug.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;
+ }
+ Debug.Assert(hourOffset >= 0 && hourOffset <= 99, "hourOffset >= 0 && hourOffset <= 99");
+ Debug.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.
+ //
+ 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 (!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 ((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)
+ {
+ Debug.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;
+ }
+ Debug.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.
+ //
+ internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, ref DateTimeResult result)
+ {
+ if (s == null)
+ {
+ result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, nameof(s));
+ return false;
+ }
+ if (s.Length == 0)
+ {
+ result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
+ return false;
+ }
+
+ Debug.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);
+ }
+ 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;
+ }
+ }
+
+ // 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
+ Debug.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)
+ {
+ Debug.Assert(minDigitLen > 0, "minDigitLen > 0");
+ Debug.Assert(maxDigitLen < 9, "maxDigitLen < 9");
+ Debug.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 == CalendarId.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, SR.GetResourceString(result.failureMessageID));
+ case ParseFailureKind.Format:
+ return new FormatException(SR.GetResourceString(result.failureMessageID));
+ case ParseFailureKind.FormatWithParameter:
+ return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), result.failureMessageFormatArgument));
+ case ParseFailureKind.FormatBadDateTimeCalendar:
+ return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), result.calendar));
+ default:
+ Debug.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 = CultureInfo.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)
+ {
+ Debug.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.
+ 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);
+ }
+ }
+
+ 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()
+ {
+ Debug.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()
+ {
+ Debug.Assert(Index >= 0 && Index < len, "Index >= 0 && Index < len");
+ Debug.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';
+ Debug.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)
+ {
+ Debug.Assert(sub.index == Index, "sub.index == Index");
+ Debug.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
+ {
+ 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 void Init(int* numberBuffer)
+ {
+ month = -1;
+ year = -1;
+ dayOfWeek = -1;
+ era = -1;
+ timeMark = DateTimeParse.TM.NotSet;
+ fraction = -1;
+ num = numberBuffer;
+ }
+ internal unsafe void AddNumber(int value)
+ {
+ num[numCount++] = value;
+ }
+ 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/shared/System/Globalization/DateTimeStyles.cs b/src/mscorlib/shared/System/Globalization/DateTimeStyles.cs
new file mode 100644
index 0000000000..79232ff199
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/DateTimeStyles.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.
+
+/*============================================================
+**
+**
+**
+** Purpose: Contains valid formats for DateTime recognized by
+** the DateTime class' parsing code.
+**
+**
+===========================================================*/
+
+namespace System.Globalization
+{
+ [Flags]
+ 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/shared/System/Globalization/DaylightTime.cs b/src/mscorlib/shared/System/Globalization/DaylightTime.cs
new file mode 100644
index 0000000000..b3c70e1d10
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/DaylightTime.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.
+
+namespace System.Globalization
+{
+ // This class represents a starting/ending time for a period of daylight saving time.
+ [Serializable]
+ public class DaylightTime
+ {
+ private readonly DateTime _start;
+ private readonly DateTime _end;
+ private readonly TimeSpan _delta;
+
+ private DaylightTime()
+ {
+ }
+
+ public DaylightTime(DateTime start, DateTime end, TimeSpan delta)
+ {
+ _start = start;
+ _end = end;
+ _delta = delta;
+ }
+
+ // The start date of a daylight saving period.
+ public DateTime Start => _start;
+
+ // The end date of a daylight saving period.
+ public DateTime End => _end;
+
+ // Delta to stardard offset in ticks.
+ public TimeSpan Delta => _delta;
+ }
+
+ // Value type version of DaylightTime
+ internal struct DaylightTimeStruct
+ {
+ public DaylightTimeStruct(DateTime start, DateTime end, TimeSpan delta)
+ {
+ Start = start;
+ End = end;
+ Delta = delta;
+ }
+
+ public readonly DateTime Start;
+ public readonly DateTime End;
+ public readonly TimeSpan Delta;
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/DigitShapes.cs b/src/mscorlib/shared/System/Globalization/DigitShapes.cs
new file mode 100644
index 0000000000..1ce45dbeb6
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/DigitShapes.cs
@@ -0,0 +1,13 @@
+// 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
+{
+ 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
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/EastAsianLunisolarCalendar.cs b/src/mscorlib/shared/System/Globalization/EastAsianLunisolarCalendar.cs
new file mode 100644
index 0000000000..d06b13cd7d
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/EastAsianLunisolarCalendar.cs
@@ -0,0 +1,710 @@
+// 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.Diagnostics.Contracts;
+
+namespace System.Globalization
+{
+ [Serializable]
+ 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;
+
+ 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(
+ nameof(sexagenaryYear),
+ SR.Format(SR.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(
+ nameof(sexagenaryYear),
+ SR.Format(SR.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(nameof(era), SR.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(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+
+ internal EastAsianLunisolarCalendar()
+ {
+ }
+
+ internal void CheckTicksRange(long ticks)
+ {
+ if (ticks < MinSupportedDateTime.Ticks || ticks > MaxSupportedDateTime.Ticks)
+ {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(CultureInfo.InvariantCulture, SR.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(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ }
+
+ internal int CheckYearRange(int year, int era)
+ {
+ CheckEraRange(era);
+ year = GetGregorianYear(year, era);
+
+ if ((year < MinCalendarYear) || (year > MaxCalendarYear))
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ SR.Format(SR.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(nameof(month), SR.ArgumentOutOfRange_Month);
+ }
+
+ if (month < 1 || month > 13)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.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);
+ }
+
+ private 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)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ SR.Format(SR.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, SR.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(
+ nameof(months),
+ SR.Format(SR.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(
+ nameof(day),
+ SR.Format(SR.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(
+ nameof(value),
+ SR.Format(SR.ArgumentOutOfRange_Range, 99, MaxCalendarYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ Contract.EndContractBlock();
+
+ year = base.ToFourDigitYear(year);
+ CheckYearRange(year, CurrentEra);
+ return (year);
+ }
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/GregorianCalendarTypes.cs b/src/mscorlib/shared/System/Globalization/GregorianCalendarTypes.cs
new file mode 100644
index 0000000000..1b61e5256e
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/GregorianCalendarTypes.cs
@@ -0,0 +1,19 @@
+// 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
+{
+ // Note: The values of the members of this enum must match the coresponding values
+ // in the CalendarId enum (since we cast between GregorianCalendarTypes and CalendarId).
+ [Serializable]
+ public enum GregorianCalendarTypes
+ {
+ Localized = CalendarId.GREGORIAN,
+ USEnglish = CalendarId.GREGORIAN_US,
+ MiddleEastFrench = CalendarId.GREGORIAN_ME_FRENCH,
+ Arabic = CalendarId.GREGORIAN_ARABIC,
+ TransliteratedEnglish = CalendarId.GREGORIAN_XLIT_ENGLISH,
+ TransliteratedFrench = CalendarId.GREGORIAN_XLIT_FRENCH,
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/HebrewCalendar.cs b/src/mscorlib/shared/System/Globalization/HebrewCalendar.cs
new file mode 100644
index 0000000000..b4f54f8fbb
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/HebrewCalendar.cs
@@ -0,0 +1,1129 @@
+// 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.Diagnostics;
+using System.Diagnostics.Contracts;
+
+namespace System.Globalization
+{
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // 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]
+ 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 byte[] s_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)
+ };
+
+ private const int MaxMonthPlusOne = 14;
+
+ //
+ // The lunar calendar has 6 different variations of month lengths
+ // within a year.
+ //
+ private static readonly byte[] s_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 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);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.LunisolarCalendar;
+ }
+ }
+
+ public HebrewCalendar()
+ {
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return (CalendarId.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.
+ ============================================================================*/
+
+ private static void CheckHebrewYearValue(int y, int era, String varName)
+ {
+ CheckEraRange(era);
+ if (y > MaxHebrewYear || y < MinHebrewYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ varName,
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.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(
+ nameof(month),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.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(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ daysInMonth));
+ }
+ }
+
+ internal static void CheckEraRange(int era)
+ {
+ if (era != CurrentEra && era != HebrewEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ }
+
+ private static 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,
+ SR.ArgumentOutOfRange_CalendarRange,
+ calendarMinValue,
+ calendarMaxValue));
+ }
+ }
+
+ internal static 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(SR.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:
+ ============================================================================*/
+
+ internal static 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(nameof(gregorianYear));
+ }
+
+ index *= 2;
+ lunarDate.day = s_hebrewTable[index];
+
+ // Get the type of the year. The value is from 1 to 6
+ int LunarYearType = s_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)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + 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)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + lunarDate.month] - lunarDate.day);
+ Debug.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)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + result.month]))
+ {
+ //
+ // Adjust the number of days and move to the next month.
+ //
+ NumDays -= (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + result.month++]);
+
+ //
+ // See if we need to adjust the Year.
+ // Must handle both 12 and 13 month years.
+ //
+ if ((result.month > 13) || (s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + result.month] == 0))
+ {
+ //
+ // Adjust the Year.
+ //
+ result.year++;
+ hebrewYearType = s_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(
+ nameof(months),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.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, nameof(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));
+ }
+
+ internal static int GetHebrewYearType(int year, int era)
+ {
+ CheckHebrewYearValue(year, era, nameof(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 (s_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);
+
+ Debug.Assert(hebrewYearType >= 1 && hebrewYearType <= 6,
+ "hebrewYearType should be from 1 to 6, but now hebrewYearType = " + hebrewYearType + " for hebrew year " + year);
+ int monthDays = s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + month];
+ if (monthDays == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.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, nameof(year));
+ return (((7 * (long)year + 1) % 19) < 7);
+ }
+
+ // (month1, day1) - (month2, day2)
+ private 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 = s_lunarMonthLen[lunarYearType * MaxMonthPlusOne + month1] - day1;
+
+ // Move to next month.
+ month1++;
+
+ // Add up the days.
+ while (month1 < month2)
+ {
+ days += s_lunarMonthLen[lunarYearType * MaxMonthPlusOne + 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.
+ **
+ ============================================================================*/
+
+
+ private 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, nameof(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, nameof(value));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ Contract.EndContractBlock();
+
+ if (year < 100)
+ {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if (year > MaxHebrewYear || year < MinHebrewYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ MinHebrewYear,
+ MaxHebrewYear));
+ }
+ return (year);
+ }
+
+ internal class __DateBuffer
+ {
+ internal int year;
+ internal int month;
+ internal int day;
+ }
+ }
+}
+
diff --git a/src/mscorlib/shared/System/Globalization/HebrewNumber.cs b/src/mscorlib/shared/System/Globalization/HebrewNumber.cs
new file mode 100644
index 0000000000..1e8fff2bcb
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/HebrewNumber.cs
@@ -0,0 +1,457 @@
+// 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.Text;
+using System.Diagnostics;
+
+namespace System.Globalization
+{
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // 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;
+ }
+
+ Debug.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.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private enum HebrewToken : short
+ {
+ 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.
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ private struct HebrewValue
+ {
+ internal HebrewToken token;
+ internal short value;
+ internal HebrewValue(HebrewToken token, short 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.
+ //
+ private static readonly HebrewValue[] s_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;
+ };
+
+ private const int minHebrewNumberCh = 0x05d0;
+ private static char s_maxHebrewNumberCh = (char)(minHebrewNumberCh + s_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 : sbyte
+ {
+ _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.
+ //
+ private static readonly HS[] s_numberPasingState =
+ {
+ // 400 300/200 100 90~10 8~1 6, 7, 9, ' "
+ /* 0 */
+ HS.S400, HS.X00, HS.X00, HS.X0, HS.X, HS.X, HS.X, HS.S9, HS._err, HS._err,
+ /* 1: S400 */
+ 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 */
+ 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 */
+ 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 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.X0_DQ,
+ /* 5: X00_DQ */
+ HS._err, HS._err, HS._err, HS.END, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err,
+ /* 6: S400_X00_X0 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.X0_DQ,
+ /* 7: X0_DQ */
+ HS._err, HS._err, HS._err, HS._err, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err,
+ /* 8: X */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS._err,
+ /* 9: X0 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS.X0_DQ,
+ /* 10: X00 */
+ 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 */
+ HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err,
+ /* 12: S400_400_DQ*/
+ HS._err, HS._err, HS.END, HS.END, HS.END, HS.END, HS.END, HS.END, HS._err, HS._err,
+ /* 13: S400_400_100*/
+ 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 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS.S9_DQ,
+ /* 15: X00_S9 */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS._err, HS.S9_DQ,
+ /* 16: S9_DQ */
+ HS._err, HS._err, HS._err, HS._err, HS._err, HS.END, HS.END, HS._err, HS._err, HS._err
+ };
+
+ // Count of valid HebrewToken, column count in the NumberPasingState array
+ private const int HebrewTokenCount = 10;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // 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)
+ {
+ Debug.Assert(s_numberPasingState.Length == HebrewTokenCount * ((int)HS.S9_DQ + 1));
+
+ HebrewToken token;
+ if (ch == '\'')
+ {
+ token = HebrewToken.SingleQuote;
+ }
+ else if (ch == '\"')
+ {
+ token = HebrewToken.DoubleQuote;
+ }
+ else
+ {
+ int index = (int)ch - minHebrewNumberCh;
+ if (index >= 0 && index < s_hebrewValues.Length)
+ {
+ token = s_hebrewValues[index].token;
+ if (token == HebrewToken.Invalid)
+ {
+ return (HebrewNumberParsingState.NotHebrewDigit);
+ }
+ context.result += s_hebrewValues[index].value;
+ }
+ else
+ {
+ // Not in valid Hebrew digit range.
+ return (HebrewNumberParsingState.NotHebrewDigit);
+ }
+ }
+ context.state = s_numberPasingState[(int)context.state * (int)HebrewTokenCount + (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 <= s_maxHebrewNumberCh)
+ {
+ return (s_hebrewValues[ch - minHebrewNumberCh].value >= 0);
+ }
+ return (ch == '\'' || ch == '\"');
+ }
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/HijriCalendar.cs b/src/mscorlib/shared/System/Globalization/HijriCalendar.cs
new file mode 100644
index 0000000000..cafde5fbb8
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/HijriCalendar.cs
@@ -0,0 +1,677 @@
+// 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.Diagnostics.Contracts;
+
+namespace System.Globalization
+{
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // 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]
+ public partial 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 };
+
+ private int _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;
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (calendarMaxValue);
+ }
+ }
+
+ public override CalendarAlgorithmType AlgorithmType
+ {
+ get
+ {
+ return CalendarAlgorithmType.LunarCalendar;
+ }
+ }
+
+ public HijriCalendar()
+ {
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return CalendarId.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:
+ ============================================================================*/
+
+ private 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:
+ ============================================================================*/
+
+ private 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
+ {
+ get
+ {
+ if (_hijriAdvance == Int32.MinValue)
+ {
+ // Never been set before. Use the system value from registry.
+ _hijriAdvance = GetHijriDateAdjustment();
+ }
+ return (_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,
+ SR.ArgumentOutOfRange_Bounds_Lower_Upper,
+ MinAdvancedHijri,
+ MaxAdvancedHijri));
+ }
+ Contract.EndContractBlock();
+ VerifyWritable();
+
+ _hijriAdvance = value;
+ }
+ }
+
+ internal static void CheckTicksRange(long ticks)
+ {
+ if (ticks < calendarMinValue.Ticks || ticks > calendarMaxValue.Ticks)
+ {
+ throw new ArgumentOutOfRangeException(
+ "time",
+ String.Format(
+ CultureInfo.InvariantCulture,
+ SR.ArgumentOutOfRange_CalendarRange,
+ calendarMinValue,
+ calendarMaxValue));
+ }
+ }
+
+ internal static void CheckEraRange(int era)
+ {
+ if (era != CurrentEra && era != HijriEra)
+ {
+ throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
+ }
+ }
+
+ internal static void CheckYearRange(int year, int era)
+ {
+ CheckEraRange(era);
+ if (year < 1 || year > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxCalendarYear));
+ }
+ }
+
+ internal static void CheckYearMonthRange(int year, int month, int era)
+ {
+ CheckYearRange(year, era);
+ if (year == MaxCalendarYear)
+ {
+ if (month > MaxCalendarMonth)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(month),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxCalendarMonth));
+ }
+ }
+
+ if (month < 1 || month > 12)
+ {
+ throw new ArgumentOutOfRangeException(nameof(month), SR.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(SR.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(
+ nameof(months),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.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(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.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);
+ 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)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(day),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.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, SR.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(
+ nameof(value),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ MaxCalendarYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+
+
+ public override int ToFourDigitYear(int year)
+ {
+ if (year < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(year),
+ SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ Contract.EndContractBlock();
+
+ if (year < 100)
+ {
+ return (base.ToFourDigitYear(year));
+ }
+
+ if (year > MaxCalendarYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.ArgumentOutOfRange_Range,
+ 1,
+ MaxCalendarYear));
+ }
+ return (year);
+ }
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/InternalGlobalizationHelper.cs b/src/mscorlib/shared/System/Globalization/InternalGlobalizationHelper.cs
new file mode 100644
index 0000000000..f5eea1b629
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/InternalGlobalizationHelper.cs
@@ -0,0 +1,48 @@
+// 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
+{
+ internal class InternalGlobalizationHelper
+ {
+ // Copied from the TimeSpan to be used inside the globalization code and avoid internal dependancy on TimeSpan class
+ internal static long TimeToTicks(int hour, int minute, int second)
+ {
+ // totalSeconds is bounded by 2^31 * 2^12 + 2^31 * 2^8 + 2^31,
+ // which is less than 2^44, meaning we won't overflow totalSeconds.
+ long totalSeconds = (long)hour * 3600 + (long)minute * 60 + (long)second;
+ if (totalSeconds > MaxSeconds || totalSeconds < MinSeconds)
+ throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong);
+ return totalSeconds * TicksPerSecond;
+ }
+
+
+ //
+ // Define needed constants so globalization code can be independant from any other types
+ //
+
+ internal const long TicksPerMillisecond = 10000;
+ internal const long TicksPerTenthSecond = TicksPerMillisecond * 100;
+ internal const long TicksPerSecond = TicksPerMillisecond * 1000; // 10,000,000
+ internal const long MaxSeconds = Int64.MaxValue / TicksPerSecond;
+ internal const long MinSeconds = Int64.MinValue / TicksPerSecond;
+ private const int DaysPerYear = 365;
+ private const int DaysPer4Years = DaysPerYear * 4 + 1; // 1461
+ private const int DaysPer100Years = DaysPer4Years * 25 - 1; // 36524
+ private const int DaysPer400Years = DaysPer100Years * 4 + 1; // 146097
+ private const int DaysTo10000 = DaysPer400Years * 25 - 366; // 3652059
+ private const long TicksPerMinute = TicksPerSecond * 60;
+ private const long TicksPerHour = TicksPerMinute * 60;
+ private const long TicksPerDay = TicksPerHour * 24;
+ internal const long MaxTicks = DaysTo10000 * TicksPerDay - 1;
+ internal const long MinTicks = 0;
+ internal const long MaxMilliSeconds = Int64.MaxValue / TicksPerMillisecond;
+ internal const long MinMilliSeconds = Int64.MinValue / TicksPerMillisecond;
+
+ internal const int StringBuilderDefaultCapacity = 16;
+
+ internal const Int64 MaxOffset = TimeSpan.TicksPerHour * 14;
+ internal const Int64 MinOffset = -MaxOffset;
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/JapaneseCalendar.cs b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.cs
new file mode 100644
index 0000000000..4655e08a4e
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.cs
@@ -0,0 +1,409 @@
+// 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.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+
+namespace System.Globalization
+{
+ /*=================================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]
+ public partial class JapaneseCalendar : Calendar
+ {
+ internal static readonly DateTime calendarMinValue = new DateTime(1868, 9, 8);
+
+
+ public override DateTime MinSupportedDateTime
+ {
+ get
+ {
+ return (calendarMinValue);
+ }
+ }
+
+ public override DateTime MaxSupportedDateTime
+ {
+ get
+ {
+ return (DateTime.MaxValue);
+ }
+ }
+
+ 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.
+ internal static 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
+ // we don't read the registry and instead we call WinRT to get the needed informatio
+ //
+ // 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)
+ {
+ japaneseEraInfo = GetJapaneseEras();
+ // 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;
+ }
+
+ 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().ToString(), e);
+ }
+ helper = new GregorianCalendarHelper(this, GetEraInfo());
+ }
+
+ internal override CalendarId ID
+ {
+ get
+ {
+ return CalendarId.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.
+ 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.
+ //
+
+ 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(nameof(year),
+ SR.ArgumentOutOfRange_NeedPosNum);
+ }
+ Contract.EndContractBlock();
+
+ if (year > helper.MaxYear)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(year),
+ String.Format(
+ CultureInfo.CurrentCulture,
+ SR.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,
+ SR.ArgumentOutOfRange_Range,
+ 99,
+ helper.MaxYear));
+ }
+ twoDigitYearMax = value;
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/shared/System/Globalization/JapaneseLunisolarCalendar.cs b/src/mscorlib/shared/System/Globalization/JapaneseLunisolarCalendar.cs
new file mode 100644
index 0000000000..95e87f85d7
--- /dev/null
+++ b/src/mscorlib/shared/System/Globalization/JapaneseLunisolarCalendar.cs
@@ -0,0 +1,305 @@
+// 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.Diagnostics.Contracts;
+
+namespace System.Globalization
+{
+ /*
+ ** 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;
+ }
+ }
+
+ private static readonly int[,] s_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 ,