Search/replace Debug.Assert(false, => Debug.Fail(
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / Globalization / DateTimeParse.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Diagnostics;
6 using System.Globalization;
7 using System.Runtime.CompilerServices;
8 using System.Text;
9
10 namespace System
11 {
12     internal static class DateTimeParse
13     {
14         internal const Int32 MaxDateTimeNumberDigits = 8;
15
16         internal delegate bool MatchNumberDelegate(ref __DTString str, int digitLen, out int result);
17
18         internal static MatchNumberDelegate m_hebrewNumberParser = new MatchNumberDelegate(DateTimeParse.MatchHebrewDigits);
19
20         internal static DateTime ParseExact(ReadOnlySpan<char> s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style)
21         {
22             DateTimeResult result = new DateTimeResult();       // The buffer to store the parsing result.
23             result.Init();
24             if (TryParseExact(s, format, dtfi, style, ref result))
25             {
26                 return result.parsedDate;
27             }
28             else
29             {
30                 throw GetDateTimeParseException(ref result);
31             }
32         }
33
34         internal static DateTime ParseExact(ReadOnlySpan<char> s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, out TimeSpan offset)
35         {
36             DateTimeResult result = new DateTimeResult();       // The buffer to store the parsing result.
37             offset = TimeSpan.Zero;
38             result.Init();
39             result.flags |= ParseFlags.CaptureOffset;
40             if (TryParseExact(s, format, dtfi, style, ref result))
41             {
42                 offset = result.timeZoneOffset;
43                 return result.parsedDate;
44             }
45             else
46             {
47                 throw GetDateTimeParseException(ref result);
48             }
49         }
50
51         internal static bool TryParseExact(ReadOnlySpan<char> s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result)
52         {
53             result = DateTime.MinValue;
54             DateTimeResult resultData = new DateTimeResult();       // The buffer to store the parsing result.
55             resultData.Init();
56             if (TryParseExact(s, format, dtfi, style, ref resultData))
57             {
58                 result = resultData.parsedDate;
59                 return true;
60             }
61             return false;
62         }
63
64         internal static bool TryParseExact(ReadOnlySpan<char> s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result, out TimeSpan offset)
65         {
66             result = DateTime.MinValue;
67             offset = TimeSpan.Zero;
68             DateTimeResult resultData = new DateTimeResult();       // The buffer to store the parsing result.
69             resultData.Init();
70             resultData.flags |= ParseFlags.CaptureOffset;
71             if (TryParseExact(s, format, dtfi, style, ref resultData))
72             {
73                 result = resultData.parsedDate;
74                 offset = resultData.timeZoneOffset;
75                 return true;
76             }
77             return false;
78         }
79
80         internal static bool TryParseExact(ReadOnlySpan<char> s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result)
81         {
82             if (format == null)
83             {
84                 result.SetFailure(ParseFailureKind.ArgumentNull, nameof(SR.ArgumentNull_String), null, nameof(format));
85                 return false;
86             }
87             if (s.Length == 0)
88             {
89                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
90                 return false;
91             }
92
93             if (format.Length == 0)
94             {
95                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadFormatSpecifier), null);
96                 return false;
97             }
98
99             Debug.Assert(dtfi != null, "dtfi == null");
100
101             return DoStrictParse(s, format, style, dtfi, ref result);
102         }
103
104         internal static DateTime ParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
105                                                 DateTimeFormatInfo dtfi, DateTimeStyles style)
106         {
107             DateTimeResult result = new DateTimeResult();       // The buffer to store the parsing result.
108             result.Init();
109             if (TryParseExactMultiple(s, formats, dtfi, style, ref result))
110             {
111                 return result.parsedDate;
112             }
113             else
114             {
115                 throw GetDateTimeParseException(ref result);
116             }
117         }
118
119
120         internal static DateTime ParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
121                                                 DateTimeFormatInfo dtfi, DateTimeStyles style, out TimeSpan offset)
122         {
123             DateTimeResult result = new DateTimeResult();       // The buffer to store the parsing result.
124             offset = TimeSpan.Zero;
125             result.Init();
126             result.flags |= ParseFlags.CaptureOffset;
127             if (TryParseExactMultiple(s, formats, dtfi, style, ref result))
128             {
129                 offset = result.timeZoneOffset;
130                 return result.parsedDate;
131             }
132             else
133             {
134                 throw GetDateTimeParseException(ref result);
135             }
136         }
137
138         internal static bool TryParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
139                                                    DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result, out TimeSpan offset)
140         {
141             result = DateTime.MinValue;
142             offset = TimeSpan.Zero;
143             DateTimeResult resultData = new DateTimeResult();       // The buffer to store the parsing result.
144             resultData.Init();
145             resultData.flags |= ParseFlags.CaptureOffset;
146             if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData))
147             {
148                 result = resultData.parsedDate;
149                 offset = resultData.timeZoneOffset;
150                 return true;
151             }
152             return false;
153         }
154
155
156         internal static bool TryParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
157                                                    DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result)
158         {
159             result = DateTime.MinValue;
160             DateTimeResult resultData = new DateTimeResult();       // The buffer to store the parsing result.
161             resultData.Init();
162             if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData))
163             {
164                 result = resultData.parsedDate;
165                 return true;
166             }
167             return false;
168         }
169
170         internal static bool TryParseExactMultiple(ReadOnlySpan<char> s, String[] formats,
171                                                 DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result)
172         {
173             if (formats == null)
174             {
175                 result.SetFailure(ParseFailureKind.ArgumentNull, nameof(SR.ArgumentNull_String), null, nameof(formats));
176                 return false;
177             }
178
179             if (s.Length == 0)
180             {
181                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
182                 return false;
183             }
184
185             if (formats.Length == 0)
186             {
187                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadFormatSpecifier), null);
188                 return false;
189             }
190
191             Debug.Assert(dtfi != null, "dtfi == null");
192
193             //
194             // Do a loop through the provided formats and see if we can parse succesfully in
195             // one of the formats.
196             //
197             for (int i = 0; i < formats.Length; i++)
198             {
199                 if (formats[i] == null || formats[i].Length == 0)
200                 {
201                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadFormatSpecifier), null);
202                     return false;
203                 }
204                 // Create a new result each time to ensure the runs are independent. Carry through
205                 // flags from the caller and return the result.
206                 DateTimeResult innerResult = new DateTimeResult();       // The buffer to store the parsing result.
207                 innerResult.Init();
208                 innerResult.flags = result.flags;
209                 if (TryParseExact(s, formats[i], dtfi, style, ref innerResult))
210                 {
211                     result.parsedDate = innerResult.parsedDate;
212                     result.timeZoneOffset = innerResult.timeZoneOffset;
213                     return (true);
214                 }
215             }
216             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
217             return (false);
218         }
219
220         ////////////////////////////////////////////////////////////////////////////
221         // Date Token Types
222         //
223         // Following is the set of tokens that can be generated from a date
224         // string. Notice that the legal set of trailing separators have been
225         // folded in with the date number, and month name tokens. This set
226         // of tokens is chosen to reduce the number of date parse states.
227         //
228         ////////////////////////////////////////////////////////////////////////////
229
230         internal enum DTT : int
231         {
232             End = 0,    // '\0'
233             NumEnd = 1,    // Num[ ]*[\0]
234             NumAmpm = 2,    // Num[ ]+AmPm
235             NumSpace = 3,    // Num[ ]+^[Dsep|Tsep|'0\']
236             NumDatesep = 4,    // Num[ ]*Dsep
237             NumTimesep = 5,    // Num[ ]*Tsep
238             MonthEnd = 6,    // Month[ ]*'\0'
239             MonthSpace = 7,    // Month[ ]+^[Dsep|Tsep|'\0']
240             MonthDatesep = 8,    // Month[ ]*Dsep
241             NumDatesuff = 9,    // Month[ ]*DSuff
242             NumTimesuff = 10,   // Month[ ]*TSuff
243             DayOfWeek = 11,   // Day of week name
244             YearSpace = 12,   // Year+^[Dsep|Tsep|'0\']
245             YearDateSep = 13,  // Year+Dsep
246             YearEnd = 14,  // Year+['\0']
247             TimeZone = 15,  // timezone name
248             Era = 16,  // era name
249             NumUTCTimeMark = 17,      // Num + 'Z'
250             // When you add a new token which will be in the
251             // state table, add it after NumLocalTimeMark.
252             Unk = 18,   // unknown
253             NumLocalTimeMark = 19,    // Num + 'T'
254             Max = 20,   // marker
255         }
256
257         internal enum TM
258         {
259             NotSet = -1,
260             AM = 0,
261             PM = 1,
262         }
263
264
265         ////////////////////////////////////////////////////////////////////////////
266         //
267         // DateTime parsing state enumeration (DS.*)
268         //
269         ////////////////////////////////////////////////////////////////////////////
270
271         internal enum DS
272         {
273             BEGIN = 0,
274             N = 1,        // have one number
275             NN = 2,        // have two numbers
276
277             // The following are known to be part of a date
278
279             D_Nd = 3,        // date string: have number followed by date separator
280             D_NN = 4,        // date string: have two numbers
281             D_NNd = 5,        // date string: have two numbers followed by date separator
282
283             D_M = 6,        // date string: have a month
284             D_MN = 7,        // date string: have a month and a number
285             D_NM = 8,        // date string: have a number and a month
286             D_MNd = 9,        // date string: have a month and number followed by date separator
287             D_NDS = 10,       // date string: have one number followed a date suffix.
288
289             D_Y = 11,        // date string: have a year.
290             D_YN = 12,        // date string: have a year and a number
291             D_YNd = 13,        // date string: have a year and a number and a date separator
292             D_YM = 14,        // date string: have a year and a month
293             D_YMd = 15,        // date string: have a year and a month and a date separator
294             D_S = 16,       // have numbers followed by a date suffix.
295             T_S = 17,       // have numbers followed by a time suffix.
296
297             // The following are known to be part of a time
298
299             T_Nt = 18,          // have num followed by time separator
300             T_NNt = 19,       // have two numbers followed by time separator
301
302
303             ERROR = 20,
304
305             // The following are terminal states. These all have an action
306             // associated with them; and transition back to BEGIN.
307
308             DX_NN = 21,       // day from two numbers
309             DX_NNN = 22,       // day from three numbers
310             DX_MN = 23,       // day from month and one number
311             DX_NM = 24,       // day from month and one number
312             DX_MNN = 25,       // day from month and two numbers
313             DX_DS = 26,       // a set of date suffixed numbers.
314             DX_DSN = 27,       // day from date suffixes and one number.
315             DX_NDS = 28,       // day from one number and date suffixes .
316             DX_NNDS = 29,       // day from one number and date suffixes .
317
318             DX_YNN = 30,       // date string: have a year and two number
319             DX_YMN = 31,       // date string: have a year, a month, and a number.
320             DX_YN = 32,       // date string: have a year and one number
321             DX_YM = 33,       // date string: have a year, a month.
322             TX_N = 34,       // time from one number (must have ampm)
323             TX_NN = 35,       // time from two numbers
324             TX_NNN = 36,       // time from three numbers
325             TX_TS = 37,       // a set of time suffixed numbers.
326             DX_NNY = 38,
327         }
328
329         ////////////////////////////////////////////////////////////////////////////
330         //
331         // NOTE: The following state machine table is dependent on the order of the
332         // DS and DTT enumerations.
333         //
334         // For each non terminal state, the following table defines the next state
335         // for each given date token type.
336         //
337         ////////////////////////////////////////////////////////////////////////////
338
339         //          End       NumEnd      NumAmPm     NumSpace    NumDaySep   NumTimesep  MonthEnd    MonthSpace  MonthDSep   NumDateSuff NumTimeSuff     DayOfWeek     YearSpace   YearDateSep YearEnd     TimeZone   Era         UTCTimeMark   
340         private static DS[][] dateParsingStates = {
341 // DS.BEGIN                                                                             // DS.BEGIN
342 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},
343
344 // DS.N                                                                                 // DS.N
345 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},
346
347 // DS.NN                                                                                // DS.NN
348 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},
349
350 // DS.D_Nd                                                                              // DS.D_Nd
351 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},
352
353 // DS.D_NN                                                                              // DS.D_NN
354 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},
355
356 // DS.D_NNd                                                                             // DS.D_NNd
357 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},
358
359 // DS.D_M                                                                               // DS.D_M
360 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},
361
362 // DS.D_MN                                                                              // DS.D_MN
363 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},
364
365 // DS.D_NM                                                                              // DS.D_NM
366 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},
367
368 // DS.D_MNd                                                                             // DS.D_MNd
369 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},
370
371 // DS.D_NDS,                                                                            // DS.D_NDS,
372 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},
373
374 // DS.D_Y                                                                               // DS.D_Y
375 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},
376
377 // DS.D_YN                                                                              // DS.D_YN
378 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},
379
380 // DS.D_YNd                                                                             // DS.D_YNd
381 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},
382
383 // DS.D_YM                                                                              // DS.D_YM
384 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},
385
386 // DS.D_YMd                                                                             // DS.D_YMd
387 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},
388
389 // DS.D_S                                                                               // DS.D_S
390 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},
391
392 // DS.T_S                                                                               // DS.T_S
393 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},
394
395 // DS.T_Nt                                                                              // DS.T_Nt
396 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},
397
398 // DS.T_NNt                                                                             // DS.T_NNt
399 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},
400 };
401         //          End       NumEnd      NumAmPm     NumSpace    NumDaySep   NumTimesep  MonthEnd    MonthSpace  MonthDSep   NumDateSuff NumTimeSuff     DayOfWeek     YearSpace   YearDateSep YearEnd     TimeZone    Era        UTCMark
402
403         internal const String GMTName = "GMT";
404         internal const String ZuluName = "Z";
405
406         //
407         // Search from the index of str at str.Index to see if the target string exists in the str.
408         //
409         private static bool MatchWord(ref __DTString str, String target)
410         {
411             if (target.Length > (str.Value.Length - str.Index))
412             {
413                 return false;
414             }
415
416             if (str.CompareInfo.Compare(str.Value.Slice(str.Index, target.Length), target, CompareOptions.IgnoreCase) != 0)
417             {
418                 return (false);
419             }
420
421             int nextCharIndex = str.Index + target.Length;
422
423             if (nextCharIndex < str.Value.Length)
424             {
425                 char nextCh = str.Value[nextCharIndex];
426                 if (Char.IsLetter(nextCh))
427                 {
428                     return (false);
429                 }
430             }
431             str.Index = nextCharIndex;
432             if (str.Index < str.Length)
433             {
434                 str.m_current = str.Value[str.Index];
435             }
436
437             return (true);
438         }
439
440
441         //
442         // Check the word at the current index to see if it matches GMT name or Zulu name.
443         //
444         private static bool GetTimeZoneName(ref __DTString str)
445         {
446             if (MatchWord(ref str, GMTName))
447             {
448                 return (true);
449             }
450
451             if (MatchWord(ref str, ZuluName))
452             {
453                 return (true);
454             }
455
456             return (false);
457         }
458
459         internal static bool IsDigit(char ch) => (uint)(ch - '0') <= 9;
460
461         /*=================================ParseFraction==========================
462         **Action: Starting at the str.Index, which should be a decimal symbol.
463         ** if the current character is a digit, parse the remaining
464         **      numbers as fraction.  For example, if the sub-string starting at str.Index is "123", then
465         **      the method will return 0.123
466         **Returns:      The fraction number.
467         **Arguments:
468         **      str the parsing string
469         **Exceptions:
470         ============================================================================*/
471
472         private static bool ParseFraction(ref __DTString str, out double result)
473         {
474             result = 0;
475             double decimalBase = 0.1;
476             int digits = 0;
477             char ch;
478             while (str.GetNext()
479                    && IsDigit(ch = str.m_current))
480             {
481                 result += (ch - '0') * decimalBase;
482                 decimalBase *= 0.1;
483                 digits++;
484             }
485             return (digits > 0);
486         }
487
488         /*=================================ParseTimeZone==========================
489         **Action: Parse the timezone offset in the following format:
490         **          "+8", "+08", "+0800", "+0800"
491         **        This method is used by DateTime.Parse().
492         **Returns:      The TimeZone offset.
493         **Arguments:
494         **      str the parsing string
495         **Exceptions:
496         **      FormatException if invalid timezone format is found.
497         ============================================================================*/
498
499         private static bool ParseTimeZone(ref __DTString str, ref TimeSpan result)
500         {
501             // The hour/minute offset for timezone.
502             int hourOffset = 0;
503             int minuteOffset = 0;
504             DTSubString sub;
505
506             // Consume the +/- character that has already been read
507             sub = str.GetSubString();
508             if (sub.length != 1)
509             {
510                 return false;
511             }
512             char offsetChar = sub[0];
513             if (offsetChar != '+' && offsetChar != '-')
514             {
515                 return false;
516             }
517             str.ConsumeSubString(sub);
518
519             sub = str.GetSubString();
520             if (sub.type != DTSubStringType.Number)
521             {
522                 return false;
523             }
524             int value = sub.value;
525             int length = sub.length;
526             if (length == 1 || length == 2)
527             {
528                 // Parsing "+8" or "+08"
529                 hourOffset = value;
530                 str.ConsumeSubString(sub);
531                 // See if we have minutes
532                 sub = str.GetSubString();
533                 if (sub.length == 1 && sub[0] == ':')
534                 {
535                     // Parsing "+8:00" or "+08:00"
536                     str.ConsumeSubString(sub);
537                     sub = str.GetSubString();
538                     if (sub.type != DTSubStringType.Number || sub.length < 1 || sub.length > 2)
539                     {
540                         return false;
541                     }
542                     minuteOffset = sub.value;
543                     str.ConsumeSubString(sub);
544                 }
545             }
546             else if (length == 3 || length == 4)
547             {
548                 // Parsing "+800" or "+0800"
549                 hourOffset = value / 100;
550                 minuteOffset = value % 100;
551                 str.ConsumeSubString(sub);
552             }
553             else
554             {
555                 // Wrong number of digits
556                 return false;
557             }
558             Debug.Assert(hourOffset >= 0 && hourOffset <= 99, "hourOffset >= 0 && hourOffset <= 99");
559             Debug.Assert(minuteOffset >= 0 && minuteOffset <= 99, "minuteOffset >= 0 && minuteOffset <= 99");
560             if (minuteOffset < 0 || minuteOffset >= 60)
561             {
562                 return false;
563             }
564
565             result = new TimeSpan(hourOffset, minuteOffset, 0);
566             if (offsetChar == '-')
567             {
568                 result = result.Negate();
569             }
570             return true;
571         }
572
573         // This is the helper function to handle timezone in string in the format like +/-0800
574         private static bool HandleTimeZone(ref __DTString str, ref DateTimeResult result)
575         {
576             if ((str.Index < str.Length - 1))
577             {
578                 char nextCh = str.Value[str.Index];
579                 // Skip whitespace, but don't update the index unless we find a time zone marker
580                 int whitespaceCount = 0;
581                 while (Char.IsWhiteSpace(nextCh) && str.Index + whitespaceCount < str.Length - 1)
582                 {
583                     whitespaceCount++;
584                     nextCh = str.Value[str.Index + whitespaceCount];
585                 }
586                 if (nextCh == '+' || nextCh == '-')
587                 {
588                     str.Index += whitespaceCount;
589                     if ((result.flags & ParseFlags.TimeZoneUsed) != 0)
590                     {
591                         // Should not have two timezone offsets.
592                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
593                         return false;
594                     }
595                     result.flags |= ParseFlags.TimeZoneUsed;
596                     if (!ParseTimeZone(ref str, ref result.timeZoneOffset))
597                     {
598                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
599                         return false;
600                     }
601                 }
602             }
603             return true;
604         }
605
606         //
607         // This is the lexer. Check the character at the current index, and put the found token in dtok and
608         // some raw date/time information in raw.
609         //
610         private static Boolean Lex(DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi, DateTimeStyles styles)
611         {
612             TokenType tokenType;
613             int tokenValue;
614             int indexBeforeSeparator;
615             char charBeforeSeparator;
616
617             TokenType sep;
618             dtok.dtt = DTT.Unk;     // Assume the token is unkown.
619
620             str.GetRegularToken(out tokenType, out tokenValue, dtfi);
621
622 #if _LOGGING
623             // Builds with _LOGGING defined (x86dbg, amd64chk, etc) support tracing
624             // Set the following internal-only/unsupported environment variables to enable DateTime tracing to the console:
625             //
626             // COMPlus_LogEnable=1
627             // COMPlus_LogToConsole=1
628             // COMPlus_LogLevel=9
629             // COMPlus_ManagedLogFacility=0x00001000
630             if (_tracingEnabled)
631             {
632                 BCLDebug.Trace("DATETIME", "[DATETIME] Lex({0})\tpos:{1}({2}), {3}, DS.{4}", Hex(str.Value),
633                                str.Index, Hex(str.m_current), tokenType, dps);
634             }
635 #endif // _LOGGING
636
637             // Look at the regular token.
638             switch (tokenType)
639             {
640                 case TokenType.NumberToken:
641                 case TokenType.YearNumberToken:
642                     if (raw.numCount == 3 || tokenValue == -1)
643                     {
644                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
645                         LexTraceExit("0010", dps);
646                         return false;
647                     }
648                     //
649                     // This is a digit.
650                     //
651                     // If the previous parsing state is DS.T_NNt (like 12:01), and we got another number,
652                     // so we will have a terminal state DS.TX_NNN (like 12:01:02).
653                     // If the previous parsing state is DS.T_Nt (like 12:), and we got another number,
654                     // so we will have a terminal state DS.TX_NN (like 12:01).
655                     //
656                     // Look ahead to see if the following character is a decimal point or timezone offset.
657                     // This enables us to parse time in the forms of:
658                     //  "11:22:33.1234" or "11:22:33-08".
659                     if (dps == DS.T_NNt)
660                     {
661                         if ((str.Index < str.Length - 1))
662                         {
663                             char nextCh = str.Value[str.Index];
664                             if (nextCh == '.')
665                             {
666                                 // While ParseFraction can fail, it just means that there were no digits after
667                                 // the dot. In this case ParseFraction just removes the dot. This is actually
668                                 // valid for cultures like Albanian, that join the time marker to the time with
669                                 // with a dot: e.g. "9:03.MD"
670                                 ParseFraction(ref str, out raw.fraction);
671                             }
672                         }
673                     }
674                     if (dps == DS.T_NNt || dps == DS.T_Nt)
675                     {
676                         if ((str.Index < str.Length - 1))
677                         {
678                             if (false == HandleTimeZone(ref str, ref result))
679                             {
680                                 LexTraceExit("0020 (value like \"12:01\" or \"12:\" followed by a non-TZ number", dps);
681                                 return false;
682                             }
683                         }
684                     }
685
686                     dtok.num = tokenValue;
687                     if (tokenType == TokenType.YearNumberToken)
688                     {
689                         if (raw.year == -1)
690                         {
691                             raw.year = tokenValue;
692                             //
693                             // If we have number which has 3 or more digits (like "001" or "0001"),
694                             // we assume this number is a year. Save the currnet raw.numCount in
695                             // raw.year.
696                             //
697                             switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
698                             {
699                                 case TokenType.SEP_End:
700                                     dtok.dtt = DTT.YearEnd;
701                                     break;
702                                 case TokenType.SEP_Am:
703                                 case TokenType.SEP_Pm:
704                                     if (raw.timeMark == TM.NotSet)
705                                     {
706                                         raw.timeMark = (sep == TokenType.SEP_Am ? TM.AM : TM.PM);
707                                         dtok.dtt = DTT.YearSpace;
708                                     }
709                                     else
710                                     {
711                                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
712                                         LexTraceExit("0030 (TM.AM/TM.PM Happened more than 1x)", dps);
713                                     }
714                                     break;
715                                 case TokenType.SEP_Space:
716                                     dtok.dtt = DTT.YearSpace;
717                                     break;
718                                 case TokenType.SEP_Date:
719                                     dtok.dtt = DTT.YearDateSep;
720                                     break;
721                                 case TokenType.SEP_Time:
722                                     if (!raw.hasSameDateAndTimeSeparators)
723                                     {
724                                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
725                                         LexTraceExit("0040 (Invalid separator after number)", dps);
726                                         return false;
727                                     }
728
729                                     // we have the date and time separators are same and getting a year number, then change the token to YearDateSep as 
730                                     // we are sure we are not parsing time.
731                                     dtok.dtt = DTT.YearDateSep;
732                                     break;
733                                 case TokenType.SEP_DateOrOffset:
734                                     // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
735                                     // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
736                                     if ((dateParsingStates[(int)dps][(int)DTT.YearDateSep] == DS.ERROR)
737                                         && (dateParsingStates[(int)dps][(int)DTT.YearSpace] > DS.ERROR))
738                                     {
739                                         str.Index = indexBeforeSeparator;
740                                         str.m_current = charBeforeSeparator;
741                                         dtok.dtt = DTT.YearSpace;
742                                     }
743                                     else
744                                     {
745                                         dtok.dtt = DTT.YearDateSep;
746                                     }
747                                     break;
748                                 case TokenType.SEP_YearSuff:
749                                 case TokenType.SEP_MonthSuff:
750                                 case TokenType.SEP_DaySuff:
751                                     dtok.dtt = DTT.NumDatesuff;
752                                     dtok.suffix = sep;
753                                     break;
754                                 case TokenType.SEP_HourSuff:
755                                 case TokenType.SEP_MinuteSuff:
756                                 case TokenType.SEP_SecondSuff:
757                                     dtok.dtt = DTT.NumTimesuff;
758                                     dtok.suffix = sep;
759                                     break;
760                                 default:
761                                     // Invalid separator after number number.
762                                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
763                                     LexTraceExit("0040 (Invalid separator after number)", dps);
764                                     return false;
765                             }
766                             //
767                             // Found the token already. Return now.
768                             //
769                             LexTraceExit("0050 (success)", dps);
770                             return true;
771                         }
772                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
773                         LexTraceExit("0060", dps);
774                         return false;
775                     }
776                     switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
777                     {
778                         //
779                         // Note here we check if the numCount is less than three.
780                         // When we have more than three numbers, it will be caught as error in the state machine.
781                         //
782                         case TokenType.SEP_End:
783                             dtok.dtt = DTT.NumEnd;
784                             raw.AddNumber(dtok.num);
785                             break;
786                         case TokenType.SEP_Am:
787                         case TokenType.SEP_Pm:
788                             if (raw.timeMark == TM.NotSet)
789                             {
790                                 raw.timeMark = (sep == TokenType.SEP_Am ? TM.AM : TM.PM);
791                                 dtok.dtt = DTT.NumAmpm;
792                                 // Fix AM/PM parsing case, e.g. "1/10 5 AM"
793                                 if (dps == DS.D_NN)
794                                 {
795                                     if (!ProcessTerminaltState(DS.DX_NN, ref result, ref styles, ref raw, dtfi))
796                                     {
797                                         return false;
798                                     }
799                                 }
800
801                                 raw.AddNumber(dtok.num);
802                             }
803                             else
804                             {
805                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
806                                 break;
807                             }
808                             if (dps == DS.T_NNt || dps == DS.T_Nt)
809                             {
810                                 if (false == HandleTimeZone(ref str, ref result))
811                                 {
812                                     LexTraceExit("0070 (HandleTimeZone returned false)", dps);
813                                     return false;
814                                 }
815                             }
816                             break;
817                         case TokenType.SEP_Space:
818                             dtok.dtt = DTT.NumSpace;
819                             raw.AddNumber(dtok.num);
820                             break;
821                         case TokenType.SEP_Date:
822                             dtok.dtt = DTT.NumDatesep;
823                             raw.AddNumber(dtok.num);
824                             break;
825                         case TokenType.SEP_DateOrOffset:
826                             // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
827                             // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
828                             if ((dateParsingStates[(int)dps][(int)DTT.NumDatesep] == DS.ERROR)
829                                 && (dateParsingStates[(int)dps][(int)DTT.NumSpace] > DS.ERROR))
830                             {
831                                 str.Index = indexBeforeSeparator;
832                                 str.m_current = charBeforeSeparator;
833                                 dtok.dtt = DTT.NumSpace;
834                             }
835                             else
836                             {
837                                 dtok.dtt = DTT.NumDatesep;
838                             }
839                             raw.AddNumber(dtok.num);
840                             break;
841                         case TokenType.SEP_Time:
842                             if (raw.hasSameDateAndTimeSeparators &&
843                                 (dps == DS.D_Y || dps == DS.D_YN || dps == DS.D_YNd || dps == DS.D_YM || dps == DS.D_YMd))
844                             {
845                                 // we are parsing a date and we have the time separator same as date separator, so we mark the token as date separator
846                                 dtok.dtt = DTT.NumDatesep;
847                                 raw.AddNumber(dtok.num);
848                                 break;
849                             }
850                             dtok.dtt = DTT.NumTimesep;
851                             raw.AddNumber(dtok.num);
852                             break;
853                         case TokenType.SEP_YearSuff:
854                             try
855                             {
856                                 dtok.num = dtfi.Calendar.ToFourDigitYear(tokenValue);
857                             }
858                             catch (ArgumentOutOfRangeException e)
859                             {
860                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), e);
861                                 LexTraceExit("0075 (Calendar.ToFourDigitYear failed)", dps);
862                                 return false;
863                             }
864                             dtok.dtt = DTT.NumDatesuff;
865                             dtok.suffix = sep;
866                             break;
867                         case TokenType.SEP_MonthSuff:
868                         case TokenType.SEP_DaySuff:
869                             dtok.dtt = DTT.NumDatesuff;
870                             dtok.suffix = sep;
871                             break;
872                         case TokenType.SEP_HourSuff:
873                         case TokenType.SEP_MinuteSuff:
874                         case TokenType.SEP_SecondSuff:
875                             dtok.dtt = DTT.NumTimesuff;
876                             dtok.suffix = sep;
877                             break;
878                         case TokenType.SEP_LocalTimeMark:
879                             dtok.dtt = DTT.NumLocalTimeMark;
880                             raw.AddNumber(dtok.num);
881                             break;
882                         default:
883                             // Invalid separator after number number.
884                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
885                             LexTraceExit("0080", dps);
886                             return false;
887                     }
888                     break;
889                 case TokenType.HebrewNumber:
890                     if (tokenValue >= 100)
891                     {
892                         // This is a year number
893                         if (raw.year == -1)
894                         {
895                             raw.year = tokenValue;
896                             //
897                             // If we have number which has 3 or more digits (like "001" or "0001"),
898                             // we assume this number is a year. Save the currnet raw.numCount in
899                             // raw.year.
900                             //
901                             switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
902                             {
903                                 case TokenType.SEP_End:
904                                     dtok.dtt = DTT.YearEnd;
905                                     break;
906                                 case TokenType.SEP_Space:
907                                     dtok.dtt = DTT.YearSpace;
908                                     break;
909                                 case TokenType.SEP_DateOrOffset:
910                                     // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
911                                     // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
912                                     if (dateParsingStates[(int)dps][(int)DTT.YearSpace] > DS.ERROR)
913                                     {
914                                         str.Index = indexBeforeSeparator;
915                                         str.m_current = charBeforeSeparator;
916                                         dtok.dtt = DTT.YearSpace;
917                                         break;
918                                     }
919                                     goto default;
920                                 default:
921                                     // Invalid separator after number number.
922                                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
923                                     LexTraceExit("0090", dps);
924                                     return false;
925                             }
926                         }
927                         else
928                         {
929                             // Invalid separator after number number.
930                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
931                             LexTraceExit("0100", dps);
932                             return false;
933                         }
934                     }
935                     else
936                     {
937                         // This is a day number
938                         dtok.num = tokenValue;
939                         raw.AddNumber(dtok.num);
940
941                         switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
942                         {
943                             //
944                             // Note here we check if the numCount is less than three.
945                             // When we have more than three numbers, it will be caught as error in the state machine.
946                             //
947                             case TokenType.SEP_End:
948                                 dtok.dtt = DTT.NumEnd;
949                                 break;
950                             case TokenType.SEP_Space:
951                             case TokenType.SEP_Date:
952                                 dtok.dtt = DTT.NumDatesep;
953                                 break;
954                             case TokenType.SEP_DateOrOffset:
955                                 // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
956                                 // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
957                                 if ((dateParsingStates[(int)dps][(int)DTT.NumDatesep] == DS.ERROR)
958                                     && (dateParsingStates[(int)dps][(int)DTT.NumSpace] > DS.ERROR))
959                                 {
960                                     str.Index = indexBeforeSeparator;
961                                     str.m_current = charBeforeSeparator;
962                                     dtok.dtt = DTT.NumSpace;
963                                 }
964                                 else
965                                 {
966                                     dtok.dtt = DTT.NumDatesep;
967                                 }
968                                 break;
969                             default:
970                                 // Invalid separator after number number.
971                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
972                                 LexTraceExit("0110", dps);
973                                 return false;
974                         }
975                     }
976                     break;
977                 case TokenType.DayOfWeekToken:
978                     if (raw.dayOfWeek == -1)
979                     {
980                         //
981                         // This is a day of week name.
982                         //
983                         raw.dayOfWeek = tokenValue;
984                         dtok.dtt = DTT.DayOfWeek;
985                     }
986                     else
987                     {
988                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
989                         LexTraceExit("0120 (DayOfWeek seen more than 1x)", dps);
990                         return false;
991                     }
992                     break;
993                 case TokenType.MonthToken:
994                     if (raw.month == -1)
995                     {
996                         //
997                         // This is a month name
998                         //
999                         switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
1000                         {
1001                             case TokenType.SEP_End:
1002                                 dtok.dtt = DTT.MonthEnd;
1003                                 break;
1004                             case TokenType.SEP_Space:
1005                                 dtok.dtt = DTT.MonthSpace;
1006                                 break;
1007                             case TokenType.SEP_Date:
1008                                 dtok.dtt = DTT.MonthDatesep;
1009                                 break;
1010                             case TokenType.SEP_Time:
1011                                 if (!raw.hasSameDateAndTimeSeparators)
1012                                 {
1013                                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1014                                     LexTraceExit("0130 (Invalid separator after month name)", dps);
1015                                     return false;
1016                                 }
1017
1018                                 // we have the date and time separators are same and getting a Month name, then change the token to MonthDatesep as 
1019                                 // we are sure we are not parsing time.
1020                                 dtok.dtt = DTT.MonthDatesep;
1021                                 break;
1022                             case TokenType.SEP_DateOrOffset:
1023                                 // The separator is either a date separator or the start of a time zone offset. If the token will complete the date then
1024                                 // process just the number and roll back the index so that the outer loop can attempt to parse the time zone offset.
1025                                 if ((dateParsingStates[(int)dps][(int)DTT.MonthDatesep] == DS.ERROR)
1026                                     && (dateParsingStates[(int)dps][(int)DTT.MonthSpace] > DS.ERROR))
1027                                 {
1028                                     str.Index = indexBeforeSeparator;
1029                                     str.m_current = charBeforeSeparator;
1030                                     dtok.dtt = DTT.MonthSpace;
1031                                 }
1032                                 else
1033                                 {
1034                                     dtok.dtt = DTT.MonthDatesep;
1035                                 }
1036                                 break;
1037                             default:
1038                                 //Invalid separator after month name
1039                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1040                                 LexTraceExit("0130 (Invalid separator after month name)", dps);
1041                                 return false;
1042                         }
1043                         raw.month = tokenValue;
1044                     }
1045                     else
1046                     {
1047                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1048                         LexTraceExit("0140 (MonthToken seen more than 1x)", dps);
1049                         return false;
1050                     }
1051                     break;
1052                 case TokenType.EraToken:
1053                     if (result.era != -1)
1054                     {
1055                         result.era = tokenValue;
1056                         dtok.dtt = DTT.Era;
1057                     }
1058                     else
1059                     {
1060                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1061                         LexTraceExit("0150 (EraToken seen when result.era already set)", dps);
1062                         return false;
1063                     }
1064                     break;
1065                 case TokenType.JapaneseEraToken:
1066                     // Special case for Japanese.  We allow Japanese era name to be used even if the calendar is not Japanese Calendar.
1067                     result.calendar = JapaneseCalendar.GetDefaultInstance();
1068                     dtfi = DateTimeFormatInfo.GetJapaneseCalendarDTFI();
1069                     if (result.era != -1)
1070                     {
1071                         result.era = tokenValue;
1072                         dtok.dtt = DTT.Era;
1073                     }
1074                     else
1075                     {
1076                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1077                         LexTraceExit("0160 (JapaneseEraToken seen when result.era already set)", dps);
1078                         return false;
1079                     }
1080                     break;
1081                 case TokenType.TEraToken:
1082                     result.calendar = TaiwanCalendar.GetDefaultInstance();
1083                     dtfi = DateTimeFormatInfo.GetTaiwanCalendarDTFI();
1084                     if (result.era != -1)
1085                     {
1086                         result.era = tokenValue;
1087                         dtok.dtt = DTT.Era;
1088                     }
1089                     else
1090                     {
1091                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1092                         LexTraceExit("0170 (TEraToken seen when result.era already set)", dps);
1093                         return false;
1094                     }
1095                     break;
1096                 case TokenType.TimeZoneToken:
1097                     //
1098                     // This is a timezone designator
1099                     //
1100                     // NOTENOTE : for now, we only support "GMT" and "Z" (for Zulu time).
1101                     //
1102                     if ((result.flags & ParseFlags.TimeZoneUsed) != 0)
1103                     {
1104                         // Should not have two timezone offsets.
1105                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1106                         LexTraceExit("0180 (seen GMT or Z more than 1x)", dps);
1107                         return false;
1108                     }
1109                     dtok.dtt = DTT.TimeZone;
1110                     result.flags |= ParseFlags.TimeZoneUsed;
1111                     result.timeZoneOffset = new TimeSpan(0);
1112                     result.flags |= ParseFlags.TimeZoneUtc;
1113                     break;
1114                 case TokenType.EndOfString:
1115                     dtok.dtt = DTT.End;
1116                     break;
1117                 case TokenType.DateWordToken:
1118                 case TokenType.IgnorableSymbol:
1119                     // Date words and ignorable symbols can just be skipped over
1120                     break;
1121                 case TokenType.Am:
1122                 case TokenType.Pm:
1123                     if (raw.timeMark == TM.NotSet)
1124                     {
1125                         raw.timeMark = (TM)tokenValue;
1126                     }
1127                     else
1128                     {
1129                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1130                         LexTraceExit("0190 (AM/PM timeMark already set)", dps);
1131                         return false;
1132                     }
1133                     break;
1134                 case TokenType.UnknownToken:
1135                     if (Char.IsLetter(str.m_current))
1136                     {
1137                         result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_UnknowDateTimeWord), str.Index);
1138                         LexTraceExit("0200", dps);
1139                         return (false);
1140                     }
1141
1142                     if ((str.m_current == '-' || str.m_current == '+') && ((result.flags & ParseFlags.TimeZoneUsed) == 0))
1143                     {
1144                         Int32 originalIndex = str.Index;
1145                         if (ParseTimeZone(ref str, ref result.timeZoneOffset))
1146                         {
1147                             result.flags |= ParseFlags.TimeZoneUsed;
1148                             LexTraceExit("0220 (success)", dps);
1149                             return true;
1150                         }
1151                         else
1152                         {
1153                             // Time zone parse attempt failed. Fall through to punctuation handling.
1154                             str.Index = originalIndex;
1155                         }
1156                     }
1157
1158                     // Visual Basic implements string to date conversions on top of DateTime.Parse:
1159                     //   CDate("#10/10/95#")
1160                     //
1161                     if (VerifyValidPunctuation(ref str))
1162                     {
1163                         LexTraceExit("0230 (success)", dps);
1164                         return true;
1165                     }
1166
1167                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1168                     LexTraceExit("0240", dps);
1169                     return false;
1170             }
1171
1172             LexTraceExit("0250 (success)", dps);
1173             return true;
1174         }
1175
1176         private static Boolean VerifyValidPunctuation(ref __DTString str)
1177         {
1178             // Compatability Behavior. Allow trailing nulls and surrounding hashes
1179             Char ch = str.Value[str.Index];
1180             if (ch == '#')
1181             {
1182                 bool foundStart = false;
1183                 bool foundEnd = false;
1184                 for (int i = 0; i < str.Length; i++)
1185                 {
1186                     ch = str.Value[i];
1187                     if (ch == '#')
1188                     {
1189                         if (foundStart)
1190                         {
1191                             if (foundEnd)
1192                             {
1193                                 // Having more than two hashes is invalid
1194                                 return false;
1195                             }
1196                             else
1197                             {
1198                                 foundEnd = true;
1199                             }
1200                         }
1201                         else
1202                         {
1203                             foundStart = true;
1204                         }
1205                     }
1206                     else if (ch == '\0')
1207                     {
1208                         // Allow nulls only at the end
1209                         if (!foundEnd)
1210                         {
1211                             return false;
1212                         }
1213                     }
1214                     else if ((!Char.IsWhiteSpace(ch)))
1215                     {
1216                         // Anthyhing other than whitespace outside hashes is invalid
1217                         if (!foundStart || foundEnd)
1218                         {
1219                             return false;
1220                         }
1221                     }
1222                 }
1223                 if (!foundEnd)
1224                 {
1225                     // The has was un-paired
1226                     return false;
1227                 }
1228                 // Valid Hash usage: eat the hash and continue. 
1229                 str.GetNext();
1230                 return true;
1231             }
1232             else if (ch == '\0')
1233             {
1234                 for (int i = str.Index; i < str.Length; i++)
1235                 {
1236                     if (str.Value[i] != '\0')
1237                     {
1238                         // Nulls are only valid if they are the only trailing character
1239                         return false;
1240                     }
1241                 }
1242                 // Move to the end of the string
1243                 str.Index = str.Length;
1244                 return true;
1245             }
1246             return false;
1247         }
1248
1249         private const int ORDER_YMD = 0;     // The order of date is Year/Month/Day.
1250         private const int ORDER_MDY = 1;     // The order of date is Month/Day/Year.
1251         private const int ORDER_DMY = 2;     // The order of date is Day/Month/Year.
1252         private const int ORDER_YDM = 3;     // The order of date is Year/Day/Month
1253         private const int ORDER_YM = 4;     // Year/Month order.
1254         private const int ORDER_MY = 5;     // Month/Year order.
1255         private const int ORDER_MD = 6;     // Month/Day order.
1256         private const int ORDER_DM = 7;     // Day/Month order.
1257
1258         //
1259         // Decide the year/month/day order from the datePattern.
1260         //
1261         // Return 0 for YMD, 1 for MDY, 2 for DMY, otherwise -1.
1262         //
1263         private static Boolean GetYearMonthDayOrder(String datePattern, DateTimeFormatInfo dtfi, out int order)
1264         {
1265             int yearOrder = -1;
1266             int monthOrder = -1;
1267             int dayOrder = -1;
1268             int orderCount = 0;
1269
1270             bool inQuote = false;
1271
1272             for (int i = 0; i < datePattern.Length && orderCount < 3; i++)
1273             {
1274                 char ch = datePattern[i];
1275                 if (ch == '\\' || ch == '%')
1276                 {
1277                     i++;
1278                     continue;  // Skip next character that is escaped by this backslash
1279                 }
1280
1281                 if (ch == '\'' || ch == '"')
1282                 {
1283                     inQuote = !inQuote;
1284                 }
1285
1286                 if (!inQuote)
1287                 {
1288                     if (ch == 'y')
1289                     {
1290                         yearOrder = orderCount++;
1291
1292                         //
1293                         // Skip all year pattern charaters.
1294                         //
1295                         for (; i + 1 < datePattern.Length && datePattern[i + 1] == 'y'; i++)
1296                         {
1297                             // Do nothing here.
1298                         }
1299                     }
1300                     else if (ch == 'M')
1301                     {
1302                         monthOrder = orderCount++;
1303                         //
1304                         // Skip all month pattern characters.
1305                         //
1306                         for (; i + 1 < datePattern.Length && datePattern[i + 1] == 'M'; i++)
1307                         {
1308                             // Do nothing here.
1309                         }
1310                     }
1311                     else if (ch == 'd')
1312                     {
1313                         int patternCount = 1;
1314                         //
1315                         // Skip all day pattern characters.
1316                         //
1317                         for (; i + 1 < datePattern.Length && datePattern[i + 1] == 'd'; i++)
1318                         {
1319                             patternCount++;
1320                         }
1321                         //
1322                         // Make sure this is not "ddd" or "dddd", which means day of week.
1323                         //
1324                         if (patternCount <= 2)
1325                         {
1326                             dayOrder = orderCount++;
1327                         }
1328                     }
1329                 }
1330             }
1331
1332             if (yearOrder == 0 && monthOrder == 1 && dayOrder == 2)
1333             {
1334                 order = ORDER_YMD;
1335                 return true;
1336             }
1337             if (monthOrder == 0 && dayOrder == 1 && yearOrder == 2)
1338             {
1339                 order = ORDER_MDY;
1340                 return true;
1341             }
1342             if (dayOrder == 0 && monthOrder == 1 && yearOrder == 2)
1343             {
1344                 order = ORDER_DMY;
1345                 return true;
1346             }
1347             if (yearOrder == 0 && dayOrder == 1 && monthOrder == 2)
1348             {
1349                 order = ORDER_YDM;
1350                 return true;
1351             }
1352             order = -1;
1353             return false;
1354         }
1355
1356         //
1357         // Decide the year/month order from the pattern.
1358         //
1359         // Return 0 for YM, 1 for MY, otherwise -1.
1360         //
1361         private static Boolean GetYearMonthOrder(String pattern, DateTimeFormatInfo dtfi, out int order)
1362         {
1363             int yearOrder = -1;
1364             int monthOrder = -1;
1365             int orderCount = 0;
1366
1367             bool inQuote = false;
1368             for (int i = 0; i < pattern.Length && orderCount < 2; i++)
1369             {
1370                 char ch = pattern[i];
1371                 if (ch == '\\' || ch == '%')
1372                 {
1373                     i++;
1374                     continue;  // Skip next character that is escaped by this backslash
1375                 }
1376
1377                 if (ch == '\'' || ch == '"')
1378                 {
1379                     inQuote = !inQuote;
1380                 }
1381
1382                 if (!inQuote)
1383                 {
1384                     if (ch == 'y')
1385                     {
1386                         yearOrder = orderCount++;
1387
1388                         //
1389                         // Skip all year pattern charaters.
1390                         //
1391                         for (; i + 1 < pattern.Length && pattern[i + 1] == 'y'; i++)
1392                         {
1393                         }
1394                     }
1395                     else if (ch == 'M')
1396                     {
1397                         monthOrder = orderCount++;
1398                         //
1399                         // Skip all month pattern characters.
1400                         //
1401                         for (; i + 1 < pattern.Length && pattern[i + 1] == 'M'; i++)
1402                         {
1403                         }
1404                     }
1405                 }
1406             }
1407
1408             if (yearOrder == 0 && monthOrder == 1)
1409             {
1410                 order = ORDER_YM;
1411                 return true;
1412             }
1413             if (monthOrder == 0 && yearOrder == 1)
1414             {
1415                 order = ORDER_MY;
1416                 return true;
1417             }
1418             order = -1;
1419             return false;
1420         }
1421
1422         //
1423         // Decide the month/day order from the pattern.
1424         //
1425         // Return 0 for MD, 1 for DM, otherwise -1.
1426         //
1427         private static Boolean GetMonthDayOrder(String pattern, DateTimeFormatInfo dtfi, out int order)
1428         {
1429             int monthOrder = -1;
1430             int dayOrder = -1;
1431             int orderCount = 0;
1432
1433             bool inQuote = false;
1434             for (int i = 0; i < pattern.Length && orderCount < 2; i++)
1435             {
1436                 char ch = pattern[i];
1437                 if (ch == '\\' || ch == '%')
1438                 {
1439                     i++;
1440                     continue;  // Skip next character that is escaped by this backslash
1441                 }
1442
1443                 if (ch == '\'' || ch == '"')
1444                 {
1445                     inQuote = !inQuote;
1446                 }
1447
1448                 if (!inQuote)
1449                 {
1450                     if (ch == 'd')
1451                     {
1452                         int patternCount = 1;
1453                         //
1454                         // Skip all day pattern charaters.
1455                         //
1456                         for (; i + 1 < pattern.Length && pattern[i + 1] == 'd'; i++)
1457                         {
1458                             patternCount++;
1459                         }
1460
1461                         //
1462                         // Make sure this is not "ddd" or "dddd", which means day of week.
1463                         //
1464                         if (patternCount <= 2)
1465                         {
1466                             dayOrder = orderCount++;
1467                         }
1468                     }
1469                     else if (ch == 'M')
1470                     {
1471                         monthOrder = orderCount++;
1472                         //
1473                         // Skip all month pattern characters.
1474                         //
1475                         for (; i + 1 < pattern.Length && pattern[i + 1] == 'M'; i++)
1476                         {
1477                         }
1478                     }
1479                 }
1480             }
1481
1482             if (monthOrder == 0 && dayOrder == 1)
1483             {
1484                 order = ORDER_MD;
1485                 return true;
1486             }
1487             if (dayOrder == 0 && monthOrder == 1)
1488             {
1489                 order = ORDER_DM;
1490                 return true;
1491             }
1492             order = -1;
1493             return false;
1494         }
1495
1496         //
1497         // Adjust the two-digit year if necessary.
1498         //
1499         private static bool TryAdjustYear(ref DateTimeResult result, int year, out int adjustedYear)
1500         {
1501             if (year < 100)
1502             {
1503                 try
1504                 {
1505                     // the Calendar classes need some real work.  Many of the calendars that throw
1506                     // don't implement a fast/non-allocating (and non-throwing) IsValid{Year|Day|Month} method.
1507                     // we are making a targeted try/catch fix in the in-place release but will revisit this code
1508                     // in the next side-by-side release.
1509                     year = result.calendar.ToFourDigitYear(year);
1510                 }
1511                 catch (ArgumentOutOfRangeException)
1512                 {
1513                     adjustedYear = -1;
1514                     return false;
1515                 }
1516             }
1517             adjustedYear = year;
1518             return true;
1519         }
1520
1521         private static bool SetDateYMD(ref DateTimeResult result, int year, int month, int day)
1522         {
1523             // Note, longer term these checks should be done at the end of the parse. This current
1524             // way of checking creates order dependence with parsing the era name.
1525             if (result.calendar.IsValidDay(year, month, day, result.era))
1526             {
1527                 result.SetDate(year, month, day);                           // YMD
1528                 return (true);
1529             }
1530             return (false);
1531         }
1532
1533         private static bool SetDateMDY(ref DateTimeResult result, int month, int day, int year)
1534         {
1535             return (SetDateYMD(ref result, year, month, day));
1536         }
1537
1538         private static bool SetDateDMY(ref DateTimeResult result, int day, int month, int year)
1539         {
1540             return (SetDateYMD(ref result, year, month, day));
1541         }
1542
1543         private static bool SetDateYDM(ref DateTimeResult result, int year, int day, int month)
1544         {
1545             return (SetDateYMD(ref result, year, month, day));
1546         }
1547
1548         private static void GetDefaultYear(ref DateTimeResult result, ref DateTimeStyles styles)
1549         {
1550             result.Year = result.calendar.GetYear(GetDateTimeNow(ref result, ref styles));
1551             result.flags |= ParseFlags.YearDefault;
1552         }
1553
1554         // Processing teriminal case: DS.DX_NN
1555         private static Boolean GetDayOfNN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1556         {
1557             if ((result.flags & ParseFlags.HaveDate) != 0)
1558             {
1559                 // Multiple dates in the input string
1560                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1561                 return false;
1562             }
1563
1564             int n1 = raw.GetNumber(0);
1565             int n2 = raw.GetNumber(1);
1566
1567             GetDefaultYear(ref result, ref styles);
1568
1569             int order;
1570             if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out order))
1571             {
1572                 result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.MonthDayPattern);
1573                 return false;
1574             }
1575
1576             if (order == ORDER_MD)
1577             {
1578                 if (SetDateYMD(ref result, result.Year, n1, n2))                           // MD
1579                 {
1580                     result.flags |= ParseFlags.HaveDate;
1581                     return true;
1582                 }
1583             }
1584             else
1585             {
1586                 // ORDER_DM
1587                 if (SetDateYMD(ref result, result.Year, n2, n1))                           // DM
1588                 {
1589                     result.flags |= ParseFlags.HaveDate;
1590                     return true;
1591                 }
1592             }
1593             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1594             return false;
1595         }
1596
1597         // Processing teriminal case: DS.DX_NNN
1598         private static Boolean GetDayOfNNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1599         {
1600             if ((result.flags & ParseFlags.HaveDate) != 0)
1601             {
1602                 // Multiple dates in the input string
1603                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1604                 return false;
1605             }
1606
1607             int n1 = raw.GetNumber(0);
1608             int n2 = raw.GetNumber(1); ;
1609             int n3 = raw.GetNumber(2);
1610
1611             int order;
1612             if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order))
1613             {
1614                 result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.ShortDatePattern);
1615                 return false;
1616             }
1617             int year;
1618
1619             if (order == ORDER_YMD)
1620             {
1621                 if (TryAdjustYear(ref result, n1, out year) && SetDateYMD(ref result, year, n2, n3))         // YMD
1622                 {
1623                     result.flags |= ParseFlags.HaveDate;
1624                     return true;
1625                 }
1626             }
1627             else if (order == ORDER_MDY)
1628             {
1629                 if (TryAdjustYear(ref result, n3, out year) && SetDateMDY(ref result, n1, n2, year))         // MDY
1630                 {
1631                     result.flags |= ParseFlags.HaveDate;
1632                     return true;
1633                 }
1634             }
1635             else if (order == ORDER_DMY)
1636             {
1637                 if (TryAdjustYear(ref result, n3, out year) && SetDateDMY(ref result, n1, n2, year))         // DMY
1638                 {
1639                     result.flags |= ParseFlags.HaveDate;
1640                     return true;
1641                 }
1642             }
1643             else if (order == ORDER_YDM)
1644             {
1645                 if (TryAdjustYear(ref result, n1, out year) && SetDateYDM(ref result, year, n2, n3))         // YDM
1646                 {
1647                     result.flags |= ParseFlags.HaveDate;
1648                     return true;
1649                 }
1650             }
1651             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1652             return false;
1653         }
1654
1655         private static Boolean GetDayOfMN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1656         {
1657             if ((result.flags & ParseFlags.HaveDate) != 0)
1658             {
1659                 // Multiple dates in the input string
1660                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1661                 return false;
1662             }
1663
1664             // The interpretation is based on the MonthDayPattern and YearMonthPattern
1665             //
1666             //    MonthDayPattern   YearMonthPattern  Interpretation
1667             //    ---------------   ----------------  ---------------
1668             //    MMMM dd           MMMM yyyy         Day
1669             //    MMMM dd           yyyy MMMM         Day
1670             //    dd MMMM           MMMM yyyy         Year
1671             //    dd MMMM           yyyy MMMM         Day
1672             //
1673             // In the first and last cases, it could be either or neither, but a day is a better default interpretation
1674             // than a 2 digit year.
1675
1676             int monthDayOrder;
1677             if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder))
1678             {
1679                 result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.MonthDayPattern);
1680                 return false;
1681             }
1682             if (monthDayOrder == ORDER_DM)
1683             {
1684                 int yearMonthOrder;
1685                 if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder))
1686                 {
1687                     result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.YearMonthPattern);
1688                     return false;
1689                 }
1690                 if (yearMonthOrder == ORDER_MY)
1691                 {
1692                     int year;
1693                     if (!TryAdjustYear(ref result, raw.GetNumber(0), out year) || !SetDateYMD(ref result, year, raw.month, 1))
1694                     {
1695                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1696                         return false;
1697                     }
1698                     return true;
1699                 }
1700             }
1701
1702             GetDefaultYear(ref result, ref styles);
1703             if (!SetDateYMD(ref result, result.Year, raw.month, raw.GetNumber(0)))
1704             {
1705                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1706                 return false;
1707             }
1708             return true;
1709         }
1710
1711         ////////////////////////////////////////////////////////////////////////
1712         //  Actions:
1713         //  Deal with the terminal state for Hebrew Month/Day pattern
1714         //
1715         ////////////////////////////////////////////////////////////////////////
1716
1717         private static Boolean GetHebrewDayOfNM(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1718         {
1719             int monthDayOrder;
1720             if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder))
1721             {
1722                 result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.MonthDayPattern);
1723                 return false;
1724             }
1725             result.Month = raw.month;
1726             if (monthDayOrder == ORDER_DM || monthDayOrder == ORDER_MD)
1727             {
1728                 if (result.calendar.IsValidDay(result.Year, result.Month, raw.GetNumber(0), result.era))
1729                 {
1730                     result.Day = raw.GetNumber(0);
1731                     return true;
1732                 }
1733             }
1734             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1735             return false;
1736         }
1737
1738         private static Boolean GetDayOfNM(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1739         {
1740             if ((result.flags & ParseFlags.HaveDate) != 0)
1741             {
1742                 // Multiple dates in the input string
1743                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1744                 return false;
1745             }
1746
1747             // The interpretation is based on the MonthDayPattern and YearMonthPattern
1748             //
1749             //    MonthDayPattern   YearMonthPattern  Interpretation
1750             //    ---------------   ----------------  ---------------
1751             //    MMMM dd           MMMM yyyy         Day
1752             //    MMMM dd           yyyy MMMM         Year
1753             //    dd MMMM           MMMM yyyy         Day
1754             //    dd MMMM           yyyy MMMM         Day
1755             //
1756             // In the first and last cases, it could be either or neither, but a day is a better default interpretation
1757             // than a 2 digit year.
1758
1759             int monthDayOrder;
1760             if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder))
1761             {
1762                 result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.MonthDayPattern);
1763                 return false;
1764             }
1765             if (monthDayOrder == ORDER_MD)
1766             {
1767                 int yearMonthOrder;
1768                 if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder))
1769                 {
1770                     result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.YearMonthPattern);
1771                     return false;
1772                 }
1773                 if (yearMonthOrder == ORDER_YM)
1774                 {
1775                     int year;
1776                     if (!TryAdjustYear(ref result, raw.GetNumber(0), out year) || !SetDateYMD(ref result, year, raw.month, 1))
1777                     {
1778                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1779                         return false;
1780                     }
1781                     return true;
1782                 }
1783             }
1784
1785             GetDefaultYear(ref result, ref styles);
1786             if (!SetDateYMD(ref result, result.Year, raw.month, raw.GetNumber(0)))
1787             {
1788                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1789                 return false;
1790             }
1791             return true;
1792         }
1793
1794         private static Boolean GetDayOfMNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1795         {
1796             if ((result.flags & ParseFlags.HaveDate) != 0)
1797             {
1798                 // Multiple dates in the input string
1799                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1800                 return false;
1801             }
1802
1803             int n1 = raw.GetNumber(0);
1804             int n2 = raw.GetNumber(1);
1805
1806             int order;
1807             if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order))
1808             {
1809                 result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.ShortDatePattern);
1810                 return false;
1811             }
1812             int year;
1813
1814             if (order == ORDER_MDY)
1815             {
1816                 if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
1817                 {
1818                     result.SetDate(year, raw.month, n1);      // MDY
1819                     result.flags |= ParseFlags.HaveDate;
1820                     return true;
1821                 }
1822                 else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
1823                 {
1824                     result.SetDate(year, raw.month, n2);      // YMD
1825                     result.flags |= ParseFlags.HaveDate;
1826                     return true;
1827                 }
1828             }
1829             else if (order == ORDER_YMD)
1830             {
1831                 if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
1832                 {
1833                     result.SetDate(year, raw.month, n2);      // YMD
1834                     result.flags |= ParseFlags.HaveDate;
1835                     return true;
1836                 }
1837                 else if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
1838                 {
1839                     result.SetDate(year, raw.month, n1);      // DMY
1840                     result.flags |= ParseFlags.HaveDate;
1841                     return true;
1842                 }
1843             }
1844             else if (order == ORDER_DMY)
1845             {
1846                 if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
1847                 {
1848                     result.SetDate(year, raw.month, n1);      // DMY
1849                     result.flags |= ParseFlags.HaveDate;
1850                     return true;
1851                 }
1852                 else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
1853                 {
1854                     result.SetDate(year, raw.month, n2);      // YMD
1855                     result.flags |= ParseFlags.HaveDate;
1856                     return true;
1857                 }
1858             }
1859
1860             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1861             return false;
1862         }
1863
1864         private static Boolean GetDayOfYNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1865         {
1866             if ((result.flags & ParseFlags.HaveDate) != 0)
1867             {
1868                 // Multiple dates in the input string
1869                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1870                 return false;
1871             }
1872
1873             int n1 = raw.GetNumber(0);
1874             int n2 = raw.GetNumber(1);
1875             String pattern = dtfi.ShortDatePattern;
1876
1877             // For compatibility, don't throw if we can't determine the order, but default to YMD instead
1878             int order;
1879             if (GetYearMonthDayOrder(pattern, dtfi, out order) && order == ORDER_YDM)
1880             {
1881                 if (SetDateYMD(ref result, raw.year, n2, n1))
1882                 {
1883                     result.flags |= ParseFlags.HaveDate;
1884                     return true; // Year + DM
1885                 }
1886             }
1887             else
1888             {
1889                 if (SetDateYMD(ref result, raw.year, n1, n2))
1890                 {
1891                     result.flags |= ParseFlags.HaveDate;
1892                     return true; // Year + MD
1893                 }
1894             }
1895             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1896             return false;
1897         }
1898
1899         private static Boolean GetDayOfNNY(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1900         {
1901             if ((result.flags & ParseFlags.HaveDate) != 0)
1902             {
1903                 // Multiple dates in the input string
1904                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1905                 return false;
1906             }
1907
1908             int n1 = raw.GetNumber(0);
1909             int n2 = raw.GetNumber(1);
1910
1911             int order;
1912             if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order))
1913             {
1914                 result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.ShortDatePattern);
1915                 return false;
1916             }
1917
1918             if (order == ORDER_MDY || order == ORDER_YMD)
1919             {
1920                 if (SetDateYMD(ref result, raw.year, n1, n2))
1921                 {
1922                     result.flags |= ParseFlags.HaveDate;
1923                     return true; // MD + Year
1924                 }
1925             }
1926             else
1927             {
1928                 if (SetDateYMD(ref result, raw.year, n2, n1))
1929                 {
1930                     result.flags |= ParseFlags.HaveDate;
1931                     return true; // DM + Year
1932                 }
1933             }
1934             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1935             return false;
1936         }
1937
1938
1939         private static Boolean GetDayOfYMN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1940         {
1941             if ((result.flags & ParseFlags.HaveDate) != 0)
1942             {
1943                 // Multiple dates in the input string
1944                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1945                 return false;
1946             }
1947
1948             if (SetDateYMD(ref result, raw.year, raw.month, raw.GetNumber(0)))
1949             {
1950                 result.flags |= ParseFlags.HaveDate;
1951                 return true;
1952             }
1953             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1954             return false;
1955         }
1956
1957         private static Boolean GetDayOfYN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1958         {
1959             if ((result.flags & ParseFlags.HaveDate) != 0)
1960             {
1961                 // Multiple dates in the input string
1962                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1963                 return false;
1964             }
1965
1966             if (SetDateYMD(ref result, raw.year, raw.GetNumber(0), 1))
1967             {
1968                 result.flags |= ParseFlags.HaveDate;
1969                 return true;
1970             }
1971             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1972             return false;
1973         }
1974
1975         private static Boolean GetDayOfYM(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1976         {
1977             if ((result.flags & ParseFlags.HaveDate) != 0)
1978             {
1979                 // Multiple dates in the input string
1980                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1981                 return false;
1982             }
1983
1984             if (SetDateYMD(ref result, raw.year, raw.month, 1))
1985             {
1986                 result.flags |= ParseFlags.HaveDate;
1987                 return true;
1988             }
1989             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
1990             return false;
1991         }
1992
1993         private static void AdjustTimeMark(DateTimeFormatInfo dtfi, ref DateTimeRawInfo raw)
1994         {
1995             // Specail case for culture which uses AM as empty string.
1996             // E.g. af-ZA (0x0436)
1997             //    S1159                  \x0000
1998             //    S2359                  nm
1999             // In this case, if we are parsing a string like "2005/09/14 12:23", we will assume this is in AM.
2000
2001             if (raw.timeMark == TM.NotSet)
2002             {
2003                 if (dtfi.AMDesignator != null && dtfi.PMDesignator != null)
2004                 {
2005                     if (dtfi.AMDesignator.Length == 0 && dtfi.PMDesignator.Length != 0)
2006                     {
2007                         raw.timeMark = TM.AM;
2008                     }
2009                     if (dtfi.PMDesignator.Length == 0 && dtfi.AMDesignator.Length != 0)
2010                     {
2011                         raw.timeMark = TM.PM;
2012                     }
2013                 }
2014             }
2015         }
2016
2017         //
2018         // Adjust hour according to the time mark.
2019         //
2020         private static Boolean AdjustHour(ref int hour, TM timeMark)
2021         {
2022             if (timeMark != TM.NotSet)
2023             {
2024                 if (timeMark == TM.AM)
2025                 {
2026                     if (hour < 0 || hour > 12)
2027                     {
2028                         return false;
2029                     }
2030                     hour = (hour == 12) ? 0 : hour;
2031                 }
2032                 else
2033                 {
2034                     if (hour < 0 || hour > 23)
2035                     {
2036                         return false;
2037                     }
2038                     if (hour < 12)
2039                     {
2040                         hour += 12;
2041                     }
2042                 }
2043             }
2044             return true;
2045         }
2046
2047         private static Boolean GetTimeOfN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw)
2048         {
2049             if ((result.flags & ParseFlags.HaveTime) != 0)
2050             {
2051                 // Multiple times in the input string
2052                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2053                 return false;
2054             }
2055             //
2056             // In this case, we need a time mark. Check if so.
2057             //
2058             if (raw.timeMark == TM.NotSet)
2059             {
2060                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2061                 return false;
2062             }
2063             result.Hour = raw.GetNumber(0);
2064             result.flags |= ParseFlags.HaveTime;
2065             return true;
2066         }
2067
2068         private static Boolean GetTimeOfNN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw)
2069         {
2070             Debug.Assert(raw.numCount >= 2, "raw.numCount >= 2");
2071             if ((result.flags & ParseFlags.HaveTime) != 0)
2072             {
2073                 // Multiple times in the input string
2074                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2075                 return false;
2076             }
2077
2078             result.Hour = raw.GetNumber(0);
2079             result.Minute = raw.GetNumber(1);
2080             result.flags |= ParseFlags.HaveTime;
2081             return true;
2082         }
2083
2084         private static Boolean GetTimeOfNNN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw)
2085         {
2086             if ((result.flags & ParseFlags.HaveTime) != 0)
2087             {
2088                 // Multiple times in the input string
2089                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2090                 return false;
2091             }
2092             Debug.Assert(raw.numCount >= 3, "raw.numCount >= 3");
2093             result.Hour = raw.GetNumber(0);
2094             result.Minute = raw.GetNumber(1);
2095             result.Second = raw.GetNumber(2);
2096             result.flags |= ParseFlags.HaveTime;
2097             return true;
2098         }
2099
2100         //
2101         // Processing terminal state: A Date suffix followed by one number.
2102         //
2103         private static Boolean GetDateOfDSN(ref DateTimeResult result, ref DateTimeRawInfo raw)
2104         {
2105             if (raw.numCount != 1 || result.Day != -1)
2106             {
2107                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2108                 return false;
2109             }
2110             result.Day = raw.GetNumber(0);
2111             return true;
2112         }
2113
2114         private static Boolean GetDateOfNDS(ref DateTimeResult result, ref DateTimeRawInfo raw)
2115         {
2116             if (result.Month == -1)
2117             {
2118                 //Should have a month suffix
2119                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2120                 return false;
2121             }
2122             if (result.Year != -1)
2123             {
2124                 // Aleady has a year suffix
2125                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2126                 return false;
2127             }
2128             if (!TryAdjustYear(ref result, raw.GetNumber(0), out result.Year))
2129             {
2130                 // the year value is out of range
2131                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2132                 return false;
2133             }
2134             result.Day = 1;
2135             return true;
2136         }
2137
2138         private static Boolean GetDateOfNNDS(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
2139         {
2140             // For partial CJK Dates, the only valid formats are with a specified year, followed by two numbers, which
2141             // will be the Month and Day, and with a specified Month, when the numbers are either the year and day or
2142             // day and year, depending on the short date pattern.
2143
2144             if ((result.flags & ParseFlags.HaveYear) != 0)
2145             {
2146                 if (((result.flags & ParseFlags.HaveMonth) == 0) && ((result.flags & ParseFlags.HaveDay) == 0))
2147                 {
2148                     if (TryAdjustYear(ref result, raw.year, out result.Year) && SetDateYMD(ref result, result.Year, raw.GetNumber(0), raw.GetNumber(1)))
2149                     {
2150                         return true;
2151                     }
2152                 }
2153             }
2154             else if ((result.flags & ParseFlags.HaveMonth) != 0)
2155             {
2156                 if (((result.flags & ParseFlags.HaveYear) == 0) && ((result.flags & ParseFlags.HaveDay) == 0))
2157                 {
2158                     int order;
2159                     if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order))
2160                     {
2161                         result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadDatePattern), dtfi.ShortDatePattern);
2162                         return false;
2163                     }
2164                     int year;
2165                     if (order == ORDER_YMD)
2166                     {
2167                         if (TryAdjustYear(ref result, raw.GetNumber(0), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(1)))
2168                         {
2169                             return true;
2170                         }
2171                     }
2172                     else
2173                     {
2174                         if (TryAdjustYear(ref result, raw.GetNumber(1), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(0)))
2175                         {
2176                             return true;
2177                         }
2178                     }
2179                 }
2180             }
2181             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2182             return false;
2183         }
2184
2185         //
2186         // A date suffix is found, use this method to put the number into the result.
2187         //
2188         private static bool ProcessDateTimeSuffix(ref DateTimeResult result, ref DateTimeRawInfo raw, ref DateTimeToken dtok)
2189         {
2190             switch (dtok.suffix)
2191             {
2192                 case TokenType.SEP_YearSuff:
2193                     if ((result.flags & ParseFlags.HaveYear) != 0)
2194                     {
2195                         return false;
2196                     }
2197                     result.flags |= ParseFlags.HaveYear;
2198                     result.Year = raw.year = dtok.num;
2199                     break;
2200                 case TokenType.SEP_MonthSuff:
2201                     if ((result.flags & ParseFlags.HaveMonth) != 0)
2202                     {
2203                         return false;
2204                     }
2205                     result.flags |= ParseFlags.HaveMonth;
2206                     result.Month = raw.month = dtok.num;
2207                     break;
2208                 case TokenType.SEP_DaySuff:
2209                     if ((result.flags & ParseFlags.HaveDay) != 0)
2210                     {
2211                         return false;
2212                     }
2213                     result.flags |= ParseFlags.HaveDay;
2214                     result.Day = dtok.num;
2215                     break;
2216                 case TokenType.SEP_HourSuff:
2217                     if ((result.flags & ParseFlags.HaveHour) != 0)
2218                     {
2219                         return false;
2220                     }
2221                     result.flags |= ParseFlags.HaveHour;
2222                     result.Hour = dtok.num;
2223                     break;
2224                 case TokenType.SEP_MinuteSuff:
2225                     if ((result.flags & ParseFlags.HaveMinute) != 0)
2226                     {
2227                         return false;
2228                     }
2229                     result.flags |= ParseFlags.HaveMinute;
2230                     result.Minute = dtok.num;
2231                     break;
2232                 case TokenType.SEP_SecondSuff:
2233                     if ((result.flags & ParseFlags.HaveSecond) != 0)
2234                     {
2235                         return false;
2236                     }
2237                     result.flags |= ParseFlags.HaveSecond;
2238                     result.Second = dtok.num;
2239                     break;
2240             }
2241             return true;
2242         }
2243
2244         ////////////////////////////////////////////////////////////////////////
2245         //
2246         // Actions:
2247         // This is used by DateTime.Parse().
2248         // Process the terminal state for the Hebrew calendar parsing.
2249         //
2250         ////////////////////////////////////////////////////////////////////////
2251
2252         internal static Boolean ProcessHebrewTerminalState(DS dps, ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
2253         {
2254             // The following are accepted terminal state for Hebrew date.
2255             switch (dps)
2256             {
2257                 case DS.DX_MNN:
2258                     // Deal with the default long/short date format when the year number is ambigous (i.e. year < 100).
2259                     raw.year = raw.GetNumber(1);
2260                     if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true))
2261                     {
2262                         result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar), null);
2263                         return false;
2264                     }
2265                     if (!GetDayOfMNN(ref result, ref raw, dtfi))
2266                     {
2267                         return false;
2268                     }
2269                     break;
2270                 case DS.DX_YMN:
2271                     // Deal with the default long/short date format when the year number is NOT ambigous (i.e. year >= 100).
2272                     if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true))
2273                     {
2274                         result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar), null);
2275                         return false;
2276                     }
2277                     if (!GetDayOfYMN(ref result, ref raw, dtfi))
2278                     {
2279                         return false;
2280                     }
2281                     break;
2282                 case DS.DX_NNY:
2283                     // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is now over 5000.
2284                     // E.g. if the year is 5763, we only format as 763. so we do the reverse when parsing.
2285                     if (raw.year < 1000)
2286                     {
2287                         raw.year += 5000;
2288                     }
2289                     if (!GetDayOfNNY(ref result, ref raw, dtfi))
2290                     {
2291                         return false;
2292                     }
2293                     if (!dtfi.YearMonthAdjustment(ref result.Year, ref raw.month, true))
2294                     {
2295                         result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar), null);
2296                         return false;
2297                     }
2298                     break;
2299                 case DS.DX_NM:
2300                 case DS.DX_MN:
2301                     // Deal with Month/Day pattern.
2302                     GetDefaultYear(ref result, ref styles);
2303                     if (!dtfi.YearMonthAdjustment(ref result.Year, ref raw.month, true))
2304                     {
2305                         result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar), null);
2306                         return false;
2307                     }
2308                     if (!GetHebrewDayOfNM(ref result, ref raw, dtfi))
2309                     {
2310                         return false;
2311                     }
2312                     break;
2313                 case DS.DX_YM:
2314                     // Deal with Year/Month pattern.
2315                     if (!dtfi.YearMonthAdjustment(ref raw.year, ref raw.month, true))
2316                     {
2317                         result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar), null);
2318                         return false;
2319                     }
2320                     if (!GetDayOfYM(ref result, ref raw, dtfi))
2321                     {
2322                         return false;
2323                     }
2324                     break;
2325                 case DS.TX_N:
2326                     // Deal hour + AM/PM
2327                     if (!GetTimeOfN(dtfi, ref result, ref raw))
2328                     {
2329                         return false;
2330                     }
2331                     break;
2332                 case DS.TX_NN:
2333                     if (!GetTimeOfNN(dtfi, ref result, ref raw))
2334                     {
2335                         return false;
2336                     }
2337                     break;
2338                 case DS.TX_NNN:
2339                     if (!GetTimeOfNNN(dtfi, ref result, ref raw))
2340                     {
2341                         return false;
2342                     }
2343                     break;
2344                 default:
2345                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2346                     return false;
2347             }
2348             if (dps > DS.ERROR)
2349             {
2350                 //
2351                 // We have reached a terminal state. Reset the raw num count.
2352                 //
2353                 raw.numCount = 0;
2354             }
2355             return true;
2356         }
2357
2358         //
2359         // A terminal state has been reached, call the appropriate function to fill in the parsing result.
2360         // Return true if the state is a terminal state.
2361         //
2362         internal static Boolean ProcessTerminaltState(DS dps, ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
2363         {
2364             bool passed = true;
2365             switch (dps)
2366             {
2367                 case DS.DX_NN:
2368                     passed = GetDayOfNN(ref result, ref styles, ref raw, dtfi);
2369                     break;
2370                 case DS.DX_NNN:
2371                     passed = GetDayOfNNN(ref result, ref raw, dtfi);
2372                     break;
2373                 case DS.DX_MN:
2374                     passed = GetDayOfMN(ref result, ref styles, ref raw, dtfi);
2375                     break;
2376                 case DS.DX_NM:
2377                     passed = GetDayOfNM(ref result, ref styles, ref raw, dtfi);
2378                     break;
2379                 case DS.DX_MNN:
2380                     passed = GetDayOfMNN(ref result, ref raw, dtfi);
2381                     break;
2382                 case DS.DX_DS:
2383                     // The result has got the correct value. No need to process.
2384                     passed = true;
2385                     break;
2386                 case DS.DX_YNN:
2387                     passed = GetDayOfYNN(ref result, ref raw, dtfi);
2388                     break;
2389                 case DS.DX_NNY:
2390                     passed = GetDayOfNNY(ref result, ref raw, dtfi);
2391                     break;
2392                 case DS.DX_YMN:
2393                     passed = GetDayOfYMN(ref result, ref raw, dtfi);
2394                     break;
2395                 case DS.DX_YN:
2396                     passed = GetDayOfYN(ref result, ref raw, dtfi);
2397                     break;
2398                 case DS.DX_YM:
2399                     passed = GetDayOfYM(ref result, ref raw, dtfi);
2400                     break;
2401                 case DS.TX_N:
2402                     passed = GetTimeOfN(dtfi, ref result, ref raw);
2403                     break;
2404                 case DS.TX_NN:
2405                     passed = GetTimeOfNN(dtfi, ref result, ref raw);
2406                     break;
2407                 case DS.TX_NNN:
2408                     passed = GetTimeOfNNN(dtfi, ref result, ref raw);
2409                     break;
2410                 case DS.TX_TS:
2411                     // The result has got the correct value. Nothing to do.
2412                     passed = true;
2413                     break;
2414                 case DS.DX_DSN:
2415                     passed = GetDateOfDSN(ref result, ref raw);
2416                     break;
2417                 case DS.DX_NDS:
2418                     passed = GetDateOfNDS(ref result, ref raw);
2419                     break;
2420                 case DS.DX_NNDS:
2421                     passed = GetDateOfNNDS(ref result, ref raw, dtfi);
2422                     break;
2423             }
2424
2425             PTSTraceExit(dps, passed);
2426             if (!passed)
2427             {
2428                 return false;
2429             }
2430
2431             if (dps > DS.ERROR)
2432             {
2433                 //
2434                 // We have reached a terminal state. Reset the raw num count.
2435                 //
2436                 raw.numCount = 0;
2437             }
2438             return true;
2439         }
2440
2441         internal static DateTime Parse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles)
2442         {
2443             DateTimeResult result = new DateTimeResult();       // The buffer to store the parsing result.
2444             result.Init();
2445             if (TryParse(s, dtfi, styles, ref result))
2446             {
2447                 return result.parsedDate;
2448             }
2449             else
2450             {
2451                 throw GetDateTimeParseException(ref result);
2452             }
2453         }
2454
2455         internal static DateTime Parse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out TimeSpan offset)
2456         {
2457             DateTimeResult result = new DateTimeResult();       // The buffer to store the parsing result.
2458             result.Init();
2459             result.flags |= ParseFlags.CaptureOffset;
2460             if (TryParse(s, dtfi, styles, ref result))
2461             {
2462                 offset = result.timeZoneOffset;
2463                 return result.parsedDate;
2464             }
2465             else
2466             {
2467                 throw GetDateTimeParseException(ref result);
2468             }
2469         }
2470
2471
2472         internal static bool TryParse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result)
2473         {
2474             result = DateTime.MinValue;
2475             DateTimeResult resultData = new DateTimeResult();       // The buffer to store the parsing result.
2476             resultData.Init();
2477             if (TryParse(s, dtfi, styles, ref resultData))
2478             {
2479                 result = resultData.parsedDate;
2480                 return true;
2481             }
2482             return false;
2483         }
2484
2485         internal static bool TryParse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result, out TimeSpan offset)
2486         {
2487             result = DateTime.MinValue;
2488             offset = TimeSpan.Zero;
2489             DateTimeResult parseResult = new DateTimeResult();       // The buffer to store the parsing result.
2490             parseResult.Init();
2491             parseResult.flags |= ParseFlags.CaptureOffset;
2492             if (TryParse(s, dtfi, styles, ref parseResult))
2493             {
2494                 result = parseResult.parsedDate;
2495                 offset = parseResult.timeZoneOffset;
2496                 return true;
2497             }
2498             return false;
2499         }
2500
2501
2502         //
2503         // This is the real method to do the parsing work.
2504         //
2505         internal static bool TryParse(ReadOnlySpan<char> s, DateTimeFormatInfo dtfi, DateTimeStyles styles, ref DateTimeResult result)
2506         {
2507             if (s.Length == 0)
2508             {
2509                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2510                 return false;
2511             }
2512
2513             Debug.Assert(dtfi != null, "dtfi == null");
2514
2515 #if _LOGGING
2516             DTFITrace(dtfi);
2517 #endif
2518
2519             DateTime time;
2520             //
2521             // First try the predefined format.
2522             //
2523
2524             DS dps = DS.BEGIN;     // Date Parsing State.
2525             bool reachTerminalState = false;
2526
2527             DateTimeToken dtok = new DateTimeToken();      // The buffer to store the parsing token.
2528             dtok.suffix = TokenType.SEP_Unk;
2529             DateTimeRawInfo raw = new DateTimeRawInfo();    // The buffer to store temporary parsing information.
2530             unsafe
2531             {
2532                 Int32* numberPointer = stackalloc Int32[3];
2533                 raw.Init(numberPointer);
2534             }
2535             raw.hasSameDateAndTimeSeparators = dtfi.DateSeparator.Equals(dtfi.TimeSeparator, StringComparison.Ordinal);
2536
2537             result.calendar = dtfi.Calendar;
2538             result.era = Calendar.CurrentEra;
2539
2540             //
2541             // The string to be parsed. Use a __DTString wrapper so that we can trace the index which
2542             // indicates the begining of next token.
2543             //
2544             __DTString str = new __DTString(s, dtfi);
2545
2546             str.GetNext();
2547
2548             //
2549             // The following loop will break out when we reach the end of the str.
2550             //
2551             do
2552             {
2553                 //
2554                 // Call the lexer to get the next token.
2555                 //
2556                 // If we find a era in Lex(), the era value will be in raw.era.
2557                 if (!Lex(dps, ref str, ref dtok, ref raw, ref result, ref dtfi, styles))
2558                 {
2559                     TPTraceExit("0000", dps);
2560                     return false;
2561                 }
2562
2563                 //
2564                 // If the token is not unknown, process it.
2565                 // Otherwise, just discard it.
2566                 //
2567                 if (dtok.dtt != DTT.Unk)
2568                 {
2569                     //
2570                     // Check if we got any CJK Date/Time suffix.
2571                     // Since the Date/Time suffix tells us the number belongs to year/month/day/hour/minute/second,
2572                     // store the number in the appropriate field in the result.
2573                     //
2574                     if (dtok.suffix != TokenType.SEP_Unk)
2575                     {
2576                         if (!ProcessDateTimeSuffix(ref result, ref raw, ref dtok))
2577                         {
2578                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2579                             TPTraceExit("0010", dps);
2580                             return false;
2581                         }
2582
2583                         dtok.suffix = TokenType.SEP_Unk;  // Reset suffix to SEP_Unk;
2584                     }
2585
2586                     if (dtok.dtt == DTT.NumLocalTimeMark)
2587                     {
2588                         if (dps == DS.D_YNd || dps == DS.D_YN)
2589                         {
2590                             // Consider this as ISO 8601 format:
2591                             // "yyyy-MM-dd'T'HH:mm:ss"                 1999-10-31T02:00:00
2592                             TPTraceExit("0020", dps);
2593                             return (ParseISO8601(ref raw, ref str, styles, ref result));
2594                         }
2595                         else
2596                         {
2597                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2598                             TPTraceExit("0030", dps);
2599                             return false;
2600                         }
2601                     }
2602
2603                     if (raw.hasSameDateAndTimeSeparators)
2604                     {
2605                         if (dtok.dtt == DTT.YearEnd || dtok.dtt == DTT.YearSpace || dtok.dtt == DTT.YearDateSep)
2606                         {
2607                             // When time and date separators are same and we are hitting a year number while the first parsed part of the string was recognized 
2608                             // 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
2609                             if (dps == DS.T_Nt)
2610                             {
2611                                 dps = DS.D_Nd;
2612                             }
2613                             if (dps == DS.T_NNt)
2614                             {
2615                                 dps = DS.D_NNd;
2616                             }
2617                         }
2618
2619                         bool atEnd = str.AtEnd();
2620                         if (dateParsingStates[(int)dps][(int)dtok.dtt] == DS.ERROR || atEnd)
2621                         {
2622                             switch (dtok.dtt)
2623                             {
2624                                 // we have the case of Serbia have dates in forms 'd.M.yyyy.' so we can expect '.' after the date parts. 
2625                                 // changing the token to end with space instead of Date Separator will avoid failing the parsing.
2626
2627                                 case DTT.YearDateSep: dtok.dtt = atEnd ? DTT.YearEnd : DTT.YearSpace; break;
2628                                 case DTT.NumDatesep: dtok.dtt = atEnd ? DTT.NumEnd : DTT.NumSpace; break;
2629                                 case DTT.NumTimesep: dtok.dtt = atEnd ? DTT.NumEnd : DTT.NumSpace; break;
2630                                 case DTT.MonthDatesep: dtok.dtt = atEnd ? DTT.MonthEnd : DTT.MonthSpace; break;
2631                             }
2632                         }
2633                     }
2634
2635                     //
2636                     // Advance to the next state, and continue
2637                     //
2638                     dps = dateParsingStates[(int)dps][(int)dtok.dtt];
2639
2640                     if (dps == DS.ERROR)
2641                     {
2642                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2643                         TPTraceExit("0040 (invalid state transition)", dps);
2644                         return false;
2645                     }
2646                     else if (dps > DS.ERROR)
2647                     {
2648                         if ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0)
2649                         {
2650                             if (!ProcessHebrewTerminalState(dps, ref result, ref styles, ref raw, dtfi))
2651                             {
2652                                 TPTraceExit("0050 (ProcessHebrewTerminalState)", dps);
2653                                 return false;
2654                             }
2655                         }
2656                         else
2657                         {
2658                             if (!ProcessTerminaltState(dps, ref result, ref styles, ref raw, dtfi))
2659                             {
2660                                 TPTraceExit("0060 (ProcessTerminaltState)", dps);
2661                                 return false;
2662                             }
2663                         }
2664                         reachTerminalState = true;
2665
2666                         //
2667                         // If we have reached a terminal state, start over from DS.BEGIN again.
2668                         // For example, when we parsed "1999-12-23 13:30", we will reach a terminal state at "1999-12-23",
2669                         // and we start over so we can continue to parse "12:30".
2670                         //
2671                         dps = DS.BEGIN;
2672                     }
2673                 }
2674             } while (dtok.dtt != DTT.End && dtok.dtt != DTT.NumEnd && dtok.dtt != DTT.MonthEnd);
2675
2676             if (!reachTerminalState)
2677             {
2678                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2679                 TPTraceExit("0070 (did not reach terminal state)", dps);
2680                 return false;
2681             }
2682
2683             AdjustTimeMark(dtfi, ref raw);
2684             if (!AdjustHour(ref result.Hour, raw.timeMark))
2685             {
2686                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2687                 TPTraceExit("0080 (AdjustHour)", dps);
2688                 return false;
2689             }
2690
2691             // Check if the parased string only contains hour/minute/second values.
2692             bool bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);
2693
2694             //
2695             // Check if any year/month/day is missing in the parsing string.
2696             // If yes, get the default value from today's date.
2697             //
2698             if (!CheckDefaultDateTime(ref result, ref result.calendar, styles))
2699             {
2700                 TPTraceExit("0090 (failed to fill in missing year/month/day defaults)", dps);
2701                 return false;
2702             }
2703
2704             if (!result.calendar.TryToDateTime(result.Year, result.Month, result.Day,
2705                     result.Hour, result.Minute, result.Second, 0, result.era, out time))
2706             {
2707                 result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar), null);
2708                 TPTraceExit("0100 (result.calendar.TryToDateTime)", dps);
2709                 return false;
2710             }
2711             if (raw.fraction > 0)
2712             {
2713                 time = time.AddTicks((long)Math.Round(raw.fraction * Calendar.TicksPerSecond));
2714             }
2715
2716             //
2717             // We have to check day of week before we adjust to the time zone.
2718             // Otherwise, the value of day of week may change after adjustting to the time zone.
2719             //
2720             if (raw.dayOfWeek != -1)
2721             {
2722                 //
2723                 // Check if day of week is correct.
2724                 //
2725                 if (raw.dayOfWeek != (int)result.calendar.GetDayOfWeek(time))
2726                 {
2727                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDayOfWeek), null);
2728                     TPTraceExit("0110 (dayOfWeek check)", dps);
2729                     return false;
2730                 }
2731             }
2732
2733             result.parsedDate = time;
2734
2735             if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly))
2736             {
2737                 TPTraceExit("0120 (DetermineTimeZoneAdjustments)", dps);
2738                 return false;
2739             }
2740             TPTraceExit("0130 (success)", dps);
2741             return true;
2742         }
2743
2744
2745         // Handles time zone adjustments and sets DateTimeKind values as required by the styles
2746         private static Boolean DetermineTimeZoneAdjustments(ref DateTimeResult result, DateTimeStyles styles, Boolean bTimeOnly)
2747         {
2748             if ((result.flags & ParseFlags.CaptureOffset) != 0)
2749             {
2750                 // This is a DateTimeOffset parse, so the offset will actually be captured directly, and 
2751                 // no adjustment is required in most cases
2752                 return DateTimeOffsetTimeZonePostProcessing(ref result, styles);
2753             }
2754             else
2755             {
2756                 Int64 offsetTicks = result.timeZoneOffset.Ticks;
2757
2758                 // the DateTime offset must be within +- 14:00 hours.
2759                 if (offsetTicks < DateTimeOffset.MinOffset || offsetTicks > DateTimeOffset.MaxOffset)
2760                 {
2761                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_OffsetOutOfRange), null);
2762                     return false;
2763                 }
2764             }
2765
2766             // The flags AssumeUniveral and AssumeLocal only apply when the input does not have a time zone
2767             if ((result.flags & ParseFlags.TimeZoneUsed) == 0)
2768             {
2769                 // If AssumeLocal or AssumeLocal is used, there will always be a kind specified. As in the
2770                 // case when a time zone is present, it will default to being local unless AdjustToUniversal
2771                 // is present. These comparisons determine whether setting the kind is sufficient, or if a
2772                 // time zone adjustment is required. For consistentcy with the rest of parsing, it is desirable
2773                 // to fall through to the Adjust methods below, so that there is consist handling of boundary
2774                 // cases like wrapping around on time-only dates and temporarily allowing an adjusted date
2775                 // to exceed DateTime.MaxValue
2776                 if ((styles & DateTimeStyles.AssumeLocal) != 0)
2777                 {
2778                     if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
2779                     {
2780                         result.flags |= ParseFlags.TimeZoneUsed;
2781                         result.timeZoneOffset = TimeZoneInfo.GetLocalUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime);
2782                     }
2783                     else
2784                     {
2785                         result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Local);
2786                         return true;
2787                     }
2788                 }
2789                 else if ((styles & DateTimeStyles.AssumeUniversal) != 0)
2790                 {
2791                     if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
2792                     {
2793                         result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc);
2794                         return true;
2795                     }
2796                     else
2797                     {
2798                         result.flags |= ParseFlags.TimeZoneUsed;
2799                         result.timeZoneOffset = TimeSpan.Zero;
2800                     }
2801                 }
2802                 else
2803                 {
2804                     // No time zone and no Assume flags, so DateTimeKind.Unspecified is fine
2805                     Debug.Assert(result.parsedDate.Kind == DateTimeKind.Unspecified, "result.parsedDate.Kind == DateTimeKind.Unspecified");
2806                     return true;
2807                 }
2808             }
2809
2810             if (((styles & DateTimeStyles.RoundtripKind) != 0) && ((result.flags & ParseFlags.TimeZoneUtc) != 0))
2811             {
2812                 result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc);
2813                 return true;
2814             }
2815
2816             if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
2817             {
2818                 return (AdjustTimeZoneToUniversal(ref result));
2819             }
2820             return (AdjustTimeZoneToLocal(ref result, bTimeOnly));
2821         }
2822
2823         // Apply validation and adjustments specific to DateTimeOffset
2824         private static Boolean DateTimeOffsetTimeZonePostProcessing(ref DateTimeResult result, DateTimeStyles styles)
2825         {
2826             // For DateTimeOffset, default to the Utc or Local offset when an offset was not specified by 
2827             // the input string.
2828             if ((result.flags & ParseFlags.TimeZoneUsed) == 0)
2829             {
2830                 if ((styles & DateTimeStyles.AssumeUniversal) != 0)
2831                 {
2832                     // AssumeUniversal causes the offset to default to zero (0)
2833                     result.timeZoneOffset = TimeSpan.Zero;
2834                 }
2835                 else
2836                 {
2837                     // AssumeLocal causes the offset to default to Local.  This flag is on by default for DateTimeOffset.
2838                     result.timeZoneOffset = TimeZoneInfo.GetLocalUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime);
2839                 }
2840             }
2841
2842             Int64 offsetTicks = result.timeZoneOffset.Ticks;
2843
2844             // there should be no overflow, because the offset can be no more than -+100 hours and the date already
2845             // fits within a DateTime.
2846             Int64 utcTicks = result.parsedDate.Ticks - offsetTicks;
2847
2848             // For DateTimeOffset, both the parsed time and the corresponding UTC value must be within the boundaries
2849             // of a DateTime instance.            
2850             if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks)
2851             {
2852                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_UTCOutOfRange), null);
2853                 return false;
2854             }
2855
2856             // the offset must be within +- 14:00 hours.
2857             if (offsetTicks < DateTimeOffset.MinOffset || offsetTicks > DateTimeOffset.MaxOffset)
2858             {
2859                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_OffsetOutOfRange), null);
2860                 return false;
2861             }
2862
2863             // DateTimeOffset should still honor the AdjustToUniversal flag for consistency with DateTime. It means you
2864             // want to return an adjusted UTC value, so store the utcTicks in the DateTime and set the offset to zero
2865             if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
2866             {
2867                 if (((result.flags & ParseFlags.TimeZoneUsed) == 0) && ((styles & DateTimeStyles.AssumeUniversal) == 0))
2868                 {
2869                     // Handle the special case where the timeZoneOffset was defaulted to Local
2870                     Boolean toUtcResult = AdjustTimeZoneToUniversal(ref result);
2871                     result.timeZoneOffset = TimeSpan.Zero;
2872                     return toUtcResult;
2873                 }
2874
2875                 // The constructor should always succeed because of the range check earlier in the function
2876                 // Althought it is UTC, internally DateTimeOffset does not use this flag
2877                 result.parsedDate = new DateTime(utcTicks, DateTimeKind.Utc);
2878                 result.timeZoneOffset = TimeSpan.Zero;
2879             }
2880
2881             return true;
2882         }
2883
2884
2885         //
2886         // Adjust the specified time to universal time based on the supplied timezone.
2887         // E.g. when parsing "2001/06/08 14:00-07:00",
2888         // the time is 2001/06/08 14:00, and timeZoneOffset = -07:00.
2889         // The result will be "2001/06/08 21:00"
2890         //
2891         private static Boolean AdjustTimeZoneToUniversal(ref DateTimeResult result)
2892         {
2893             long resultTicks = result.parsedDate.Ticks;
2894             resultTicks -= result.timeZoneOffset.Ticks;
2895             if (resultTicks < 0)
2896             {
2897                 resultTicks += Calendar.TicksPerDay;
2898             }
2899
2900             if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks)
2901             {
2902                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_DateOutOfRange), null);
2903                 return false;
2904             }
2905             result.parsedDate = new DateTime(resultTicks, DateTimeKind.Utc);
2906             return true;
2907         }
2908
2909         //
2910         // Adjust the specified time to universal time based on the supplied timezone,
2911         // and then convert to local time.
2912         // E.g. when parsing "2001/06/08 14:00-04:00", and local timezone is GMT-7.
2913         // the time is 2001/06/08 14:00, and timeZoneOffset = -05:00.
2914         // The result will be "2001/06/08 11:00"
2915         //
2916         private static Boolean AdjustTimeZoneToLocal(ref DateTimeResult result, bool bTimeOnly)
2917         {
2918             long resultTicks = result.parsedDate.Ticks;
2919             // Convert to local ticks
2920             TimeZoneInfo tz = TimeZoneInfo.Local;
2921             Boolean isAmbiguousLocalDst = false;
2922             if (resultTicks < Calendar.TicksPerDay)
2923             {
2924                 //
2925                 // This is time of day.
2926                 //
2927
2928                 // Adjust timezone.
2929                 resultTicks -= result.timeZoneOffset.Ticks;
2930                 // If the time is time of day, use the current timezone offset.
2931                 resultTicks += tz.GetUtcOffset(bTimeOnly ? DateTime.Now : result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks;
2932
2933                 if (resultTicks < 0)
2934                 {
2935                     resultTicks += Calendar.TicksPerDay;
2936                 }
2937             }
2938             else
2939             {
2940                 // Adjust timezone to GMT.
2941                 resultTicks -= result.timeZoneOffset.Ticks;
2942                 if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks)
2943                 {
2944                     // If the result ticks is greater than DateTime.MaxValue, we can not create a DateTime from this ticks.
2945                     // In this case, keep using the old code.
2946                     resultTicks += tz.GetUtcOffset(result.parsedDate, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks;
2947                 }
2948                 else
2949                 {
2950                     // Convert the GMT time to local time.
2951                     DateTime utcDt = new DateTime(resultTicks, DateTimeKind.Utc);
2952                     Boolean isDaylightSavings = false;
2953                     resultTicks += TimeZoneInfo.GetUtcOffsetFromUtc(utcDt, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst).Ticks;
2954                 }
2955             }
2956             if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks)
2957             {
2958                 result.parsedDate = DateTime.MinValue;
2959                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_DateOutOfRange), null);
2960                 return false;
2961             }
2962             result.parsedDate = new DateTime(resultTicks, DateTimeKind.Local, isAmbiguousLocalDst);
2963             return true;
2964         }
2965
2966         //
2967         // Parse the ISO8601 format string found during Parse();
2968         //
2969         //
2970         private static bool ParseISO8601(ref DateTimeRawInfo raw, ref __DTString str, DateTimeStyles styles, ref DateTimeResult result)
2971         {
2972             if (raw.year < 0 || raw.GetNumber(0) < 0 || raw.GetNumber(1) < 0)
2973             {
2974             }
2975             str.Index--;
2976             int hour, minute;
2977             int second = 0;
2978             double partSecond = 0;
2979
2980             str.SkipWhiteSpaces();
2981             if (!ParseDigits(ref str, 2, out hour))
2982             {
2983                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2984                 return false;
2985             }
2986             str.SkipWhiteSpaces();
2987             if (!str.Match(':'))
2988             {
2989                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2990                 return false;
2991             }
2992             str.SkipWhiteSpaces();
2993             if (!ParseDigits(ref str, 2, out minute))
2994             {
2995                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
2996                 return false;
2997             }
2998             str.SkipWhiteSpaces();
2999             if (str.Match(':'))
3000             {
3001                 str.SkipWhiteSpaces();
3002                 if (!ParseDigits(ref str, 2, out second))
3003                 {
3004                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3005                     return false;
3006                 }
3007                 if (str.Match('.'))
3008                 {
3009                     if (!ParseFraction(ref str, out partSecond))
3010                     {
3011                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3012                         return false;
3013                     }
3014                     str.Index--;
3015                 }
3016                 str.SkipWhiteSpaces();
3017             }
3018             if (str.GetNext())
3019             {
3020                 char ch = str.GetChar();
3021                 if (ch == '+' || ch == '-')
3022                 {
3023                     result.flags |= ParseFlags.TimeZoneUsed;
3024                     if (!ParseTimeZone(ref str, ref result.timeZoneOffset))
3025                     {
3026                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3027                         return false;
3028                     }
3029                 }
3030                 else if (ch == 'Z' || ch == 'z')
3031                 {
3032                     result.flags |= ParseFlags.TimeZoneUsed;
3033                     result.timeZoneOffset = TimeSpan.Zero;
3034                     result.flags |= ParseFlags.TimeZoneUtc;
3035                 }
3036                 else
3037                 {
3038                     str.Index--;
3039                 }
3040                 str.SkipWhiteSpaces();
3041                 if (str.Match('#'))
3042                 {
3043                     if (!VerifyValidPunctuation(ref str))
3044                     {
3045                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3046                         return false;
3047                     }
3048                     str.SkipWhiteSpaces();
3049                 }
3050                 if (str.Match('\0'))
3051                 {
3052                     if (!VerifyValidPunctuation(ref str))
3053                     {
3054                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3055                         return false;
3056                     }
3057                 }
3058                 if (str.GetNext())
3059                 {
3060                     // If this is true, there were non-white space characters remaining in the DateTime
3061                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3062                     return false;
3063                 }
3064             }
3065
3066             DateTime time;
3067             Calendar calendar = GregorianCalendar.GetDefaultInstance();
3068             if (!calendar.TryToDateTime(raw.year, raw.GetNumber(0), raw.GetNumber(1),
3069                     hour, minute, second, 0, result.era, out time))
3070             {
3071                 result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar), null);
3072                 return false;
3073             }
3074
3075             time = time.AddTicks((long)Math.Round(partSecond * Calendar.TicksPerSecond));
3076             result.parsedDate = time;
3077             if (!DetermineTimeZoneAdjustments(ref result, styles, false))
3078             {
3079                 return false;
3080             }
3081             return true;
3082         }
3083
3084
3085         ////////////////////////////////////////////////////////////////////////
3086         //
3087         // Actions:
3088         //    Parse the current word as a Hebrew number.
3089         //      This is used by DateTime.ParseExact().
3090         //
3091         ////////////////////////////////////////////////////////////////////////
3092
3093         internal static bool MatchHebrewDigits(ref __DTString str, int digitLen, out int number)
3094         {
3095             number = 0;
3096
3097             // Create a context object so that we can parse the Hebrew number text character by character.
3098             HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
3099
3100             // Set this to ContinueParsing so that we will run the following while loop in the first time.
3101             HebrewNumberParsingState state = HebrewNumberParsingState.ContinueParsing;
3102
3103             while (state == HebrewNumberParsingState.ContinueParsing && str.GetNext())
3104             {
3105                 state = HebrewNumber.ParseByChar(str.GetChar(), ref context);
3106             }
3107
3108             if (state == HebrewNumberParsingState.FoundEndOfHebrewNumber)
3109             {
3110                 // If we have reached a terminal state, update the result and returns.
3111                 number = context.result;
3112                 return (true);
3113             }
3114
3115             // If we run out of the character before reaching FoundEndOfHebrewNumber, or
3116             // the state is InvalidHebrewNumber or ContinueParsing, we fail to match a Hebrew number.
3117             // Return an error.
3118             return false;
3119         }
3120
3121         /*=================================ParseDigits==================================
3122         **Action: Parse the number string in __DTString that are formatted using
3123         **        the following patterns:
3124         **        "0", "00", and "000..0"
3125         **Returns: the integer value
3126         **Arguments:    str: a __DTString.  The parsing will start from the
3127         **              next character after str.Index.
3128         **Exceptions: FormatException if error in parsing number.
3129         ==============================================================================*/
3130
3131         internal static bool ParseDigits(ref __DTString str, int digitLen, out int result)
3132         {
3133             if (digitLen == 1)
3134             {
3135                 // 1 really means 1 or 2 for this call
3136                 return ParseDigits(ref str, 1, 2, out result);
3137             }
3138             else
3139             {
3140                 return ParseDigits(ref str, digitLen, digitLen, out result);
3141             }
3142         }
3143
3144         internal static bool ParseDigits(ref __DTString str, int minDigitLen, int maxDigitLen, out int result)
3145         {
3146             Debug.Assert(minDigitLen > 0, "minDigitLen > 0");
3147             Debug.Assert(maxDigitLen < 9, "maxDigitLen < 9");
3148             Debug.Assert(minDigitLen <= maxDigitLen, "minDigitLen <= maxDigitLen");
3149             int localResult = 0;
3150             int startingIndex = str.Index;
3151             int tokenLength = 0;
3152             while (tokenLength < maxDigitLen)
3153             {
3154                 if (!str.GetNextDigit())
3155                 {
3156                     str.Index--;
3157                     break;
3158                 }
3159                 localResult = localResult * 10 + str.GetDigit();
3160                 tokenLength++;
3161             }
3162             result = localResult;
3163             if (tokenLength < minDigitLen)
3164             {
3165                 str.Index = startingIndex;
3166                 return false;
3167             }
3168             return true;
3169         }
3170
3171         /*=================================ParseFractionExact==================================
3172         **Action: Parse the number string in __DTString that are formatted using
3173         **        the following patterns:
3174         **        "0", "00", and "000..0"
3175         **Returns: the fraction value
3176         **Arguments:    str: a __DTString.  The parsing will start from the
3177         **              next character after str.Index.
3178         **Exceptions: FormatException if error in parsing number.
3179         ==============================================================================*/
3180
3181         private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, ref double result)
3182         {
3183             if (!str.GetNextDigit())
3184             {
3185                 str.Index--;
3186                 return false;
3187             }
3188             result = str.GetDigit();
3189
3190             int digitLen = 1;
3191             for (; digitLen < maxDigitLen; digitLen++)
3192             {
3193                 if (!str.GetNextDigit())
3194                 {
3195                     str.Index--;
3196                     break;
3197                 }
3198                 result = result * 10 + str.GetDigit();
3199             }
3200
3201             result /= TimeSpanParse.Pow10(digitLen);
3202             return (digitLen == maxDigitLen);
3203         }
3204
3205         /*=================================ParseSign==================================
3206         **Action: Parse a positive or a negative sign.
3207         **Returns:      true if postive sign.  flase if negative sign.
3208         **Arguments:    str: a __DTString.  The parsing will start from the
3209         **              next character after str.Index.
3210         **Exceptions:   FormatException if end of string is encountered or a sign
3211         **              symbol is not found.
3212         ==============================================================================*/
3213
3214         private static bool ParseSign(ref __DTString str, ref bool result)
3215         {
3216             if (!str.GetNext())
3217             {
3218                 // A sign symbol ('+' or '-') is expected. However, end of string is encountered.
3219                 return false;
3220             }
3221             char ch = str.GetChar();
3222             if (ch == '+')
3223             {
3224                 result = true;
3225                 return (true);
3226             }
3227             else if (ch == '-')
3228             {
3229                 result = false;
3230                 return (true);
3231             }
3232             // A sign symbol ('+' or '-') is expected.
3233             return false;
3234         }
3235
3236         /*=================================ParseTimeZoneOffset==================================
3237         **Action: Parse the string formatted using "z", "zz", "zzz" in DateTime.Format().
3238         **Returns: the TimeSpan for the parsed timezone offset.
3239         **Arguments:    str: a __DTString.  The parsing will start from the
3240         **              next character after str.Index.
3241         **              len: the repeated number of the "z"
3242         **Exceptions: FormatException if errors in parsing.
3243         ==============================================================================*/
3244
3245         private static bool ParseTimeZoneOffset(ref __DTString str, int len, ref TimeSpan result)
3246         {
3247             bool isPositive = true;
3248             int hourOffset;
3249             int minuteOffset = 0;
3250
3251             switch (len)
3252             {
3253                 case 1:
3254                 case 2:
3255                     if (!ParseSign(ref str, ref isPositive))
3256                     {
3257                         return (false);
3258                     }
3259                     if (!ParseDigits(ref str, len, out hourOffset))
3260                     {
3261                         return (false);
3262                     }
3263                     break;
3264                 default:
3265                     if (!ParseSign(ref str, ref isPositive))
3266                     {
3267                         return (false);
3268                     }
3269
3270                     // Parsing 1 digit will actually parse 1 or 2.
3271                     if (!ParseDigits(ref str, 1, out hourOffset))
3272                     {
3273                         return (false);
3274                     }
3275                     // ':' is optional.
3276                     if (str.Match(":"))
3277                     {
3278                         // Found ':'
3279                         if (!ParseDigits(ref str, 2, out minuteOffset))
3280                         {
3281                             return (false);
3282                         }
3283                     }
3284                     else
3285                     {
3286                         // Since we can not match ':', put the char back.
3287                         str.Index--;
3288                         if (!ParseDigits(ref str, 2, out minuteOffset))
3289                         {
3290                             return (false);
3291                         }
3292                     }
3293                     break;
3294             }
3295             if (minuteOffset < 0 || minuteOffset >= 60)
3296             {
3297                 return false;
3298             }
3299
3300             result = (new TimeSpan(hourOffset, minuteOffset, 0));
3301             if (!isPositive)
3302             {
3303                 result = result.Negate();
3304             }
3305             return (true);
3306         }
3307
3308         /*=================================MatchAbbreviatedMonthName==================================
3309         **Action: Parse the abbreviated month name from string starting at str.Index.
3310         **Returns: A value from 1 to 12 for the first month to the twelveth month.
3311         **Arguments:    str: a __DTString.  The parsing will start from the
3312         **              next character after str.Index.
3313         **Exceptions: FormatException if an abbreviated month name can not be found.
3314         ==============================================================================*/
3315
3316         private static bool MatchAbbreviatedMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
3317         {
3318             int maxMatchStrLen = 0;
3319             result = -1;
3320             if (str.GetNext())
3321             {
3322                 //
3323                 // Scan the month names (note that some calendars has 13 months) and find
3324                 // the matching month name which has the max string length.
3325                 // We need to do this because some cultures (e.g. "cs-CZ") which have
3326                 // abbreviated month names with the same prefix.
3327                 //
3328                 int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12 : 13);
3329                 for (int i = 1; i <= monthsInYear; i++)
3330                 {
3331                     String searchStr = dtfi.GetAbbreviatedMonthName(i);
3332                     int matchStrLen = searchStr.Length;
3333                     if (dtfi.HasSpacesInMonthNames
3334                             ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
3335                             : str.MatchSpecifiedWord(searchStr))
3336                     {
3337                         if (matchStrLen > maxMatchStrLen)
3338                         {
3339                             maxMatchStrLen = matchStrLen;
3340                             result = i;
3341                         }
3342                     }
3343                 }
3344
3345                 // Search leap year form.
3346                 if ((dtfi.FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0)
3347                 {
3348                     int tempResult = str.MatchLongestWords(dtfi.internalGetLeapYearMonthNames(), ref maxMatchStrLen);
3349                     // We found a longer match in the leap year month name.  Use this as the result.
3350                     // The result from MatchLongestWords is 0 ~ length of word array.
3351                     // So we increment the result by one to become the month value.
3352                     if (tempResult >= 0)
3353                     {
3354                         result = tempResult + 1;
3355                     }
3356                 }
3357             }
3358             if (result > 0)
3359             {
3360                 str.Index += (maxMatchStrLen - 1);
3361                 return (true);
3362             }
3363             return false;
3364         }
3365
3366         /*=================================MatchMonthName==================================
3367         **Action: Parse the month name from string starting at str.Index.
3368         **Returns: A value from 1 to 12 indicating the first month to the twelveth month.
3369         **Arguments:    str: a __DTString.  The parsing will start from the
3370         **              next character after str.Index.
3371         **Exceptions: FormatException if a month name can not be found.
3372         ==============================================================================*/
3373
3374         private static bool MatchMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
3375         {
3376             int maxMatchStrLen = 0;
3377             result = -1;
3378             if (str.GetNext())
3379             {
3380                 //
3381                 // Scan the month names (note that some calendars has 13 months) and find
3382                 // the matching month name which has the max string length.
3383                 // We need to do this because some cultures (e.g. "vi-VN") which have
3384                 // month names with the same prefix.
3385                 //
3386                 int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12 : 13);
3387                 for (int i = 1; i <= monthsInYear; i++)
3388                 {
3389                     String searchStr = dtfi.GetMonthName(i);
3390                     int matchStrLen = searchStr.Length;
3391                     if (dtfi.HasSpacesInMonthNames
3392                             ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
3393                             : str.MatchSpecifiedWord(searchStr))
3394                     {
3395                         if (matchStrLen > maxMatchStrLen)
3396                         {
3397                             maxMatchStrLen = matchStrLen;
3398                             result = i;
3399                         }
3400                     }
3401                 }
3402
3403                 // Search genitive form.
3404                 if ((dtfi.FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0)
3405                 {
3406                     int tempResult = str.MatchLongestWords(dtfi.MonthGenitiveNames, ref maxMatchStrLen);
3407                     // We found a longer match in the genitive month name.  Use this as the result.
3408                     // The result from MatchLongestWords is 0 ~ length of word array.
3409                     // So we increment the result by one to become the month value.
3410                     if (tempResult >= 0)
3411                     {
3412                         result = tempResult + 1;
3413                     }
3414                 }
3415
3416                 // Search leap year form.
3417                 if ((dtfi.FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0)
3418                 {
3419                     int tempResult = str.MatchLongestWords(dtfi.internalGetLeapYearMonthNames(), ref maxMatchStrLen);
3420                     // We found a longer match in the leap year month name.  Use this as the result.
3421                     // The result from MatchLongestWords is 0 ~ length of word array.
3422                     // So we increment the result by one to become the month value.
3423                     if (tempResult >= 0)
3424                     {
3425                         result = tempResult + 1;
3426                     }
3427                 }
3428             }
3429
3430             if (result > 0)
3431             {
3432                 str.Index += (maxMatchStrLen - 1);
3433                 return (true);
3434             }
3435             return false;
3436         }
3437
3438         /*=================================MatchAbbreviatedDayName==================================
3439         **Action: Parse the abbreviated day of week name from string starting at str.Index.
3440         **Returns: A value from 0 to 6 indicating Sunday to Saturday.
3441         **Arguments:    str: a __DTString.  The parsing will start from the
3442         **              next character after str.Index.
3443         **Exceptions: FormatException if a abbreviated day of week name can not be found.
3444         ==============================================================================*/
3445
3446         private static bool MatchAbbreviatedDayName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
3447         {
3448             int maxMatchStrLen = 0;
3449             result = -1;
3450             if (str.GetNext())
3451             {
3452                 for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++)
3453                 {
3454                     String searchStr = dtfi.GetAbbreviatedDayName(i);
3455                     int matchStrLen = searchStr.Length;
3456                     if (dtfi.HasSpacesInDayNames
3457                             ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
3458                             : str.MatchSpecifiedWord(searchStr))
3459                     {
3460                         if (matchStrLen > maxMatchStrLen)
3461                         {
3462                             maxMatchStrLen = matchStrLen;
3463                             result = (int)i;
3464                         }
3465                     }
3466                 }
3467             }
3468             if (result >= 0)
3469             {
3470                 str.Index += maxMatchStrLen - 1;
3471                 return (true);
3472             }
3473             return false;
3474         }
3475
3476         /*=================================MatchDayName==================================
3477         **Action: Parse the day of week name from string starting at str.Index.
3478         **Returns: A value from 0 to 6 indicating Sunday to Saturday.
3479         **Arguments:    str: a __DTString.  The parsing will start from the
3480         **              next character after str.Index.
3481         **Exceptions: FormatException if a day of week name can not be found.
3482         ==============================================================================*/
3483
3484         private static bool MatchDayName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
3485         {
3486             // Turkish (tr-TR) got day names with the same prefix.
3487             int maxMatchStrLen = 0;
3488             result = -1;
3489             if (str.GetNext())
3490             {
3491                 for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++)
3492                 {
3493                     String searchStr = dtfi.GetDayName(i);
3494                     int matchStrLen = searchStr.Length;
3495                     if (dtfi.HasSpacesInDayNames
3496                             ? str.MatchSpecifiedWords(searchStr, false, ref matchStrLen)
3497                             : str.MatchSpecifiedWord(searchStr))
3498                     {
3499                         if (matchStrLen > maxMatchStrLen)
3500                         {
3501                             maxMatchStrLen = matchStrLen;
3502                             result = (int)i;
3503                         }
3504                     }
3505                 }
3506             }
3507             if (result >= 0)
3508             {
3509                 str.Index += maxMatchStrLen - 1;
3510                 return (true);
3511             }
3512             return false;
3513         }
3514
3515         /*=================================MatchEraName==================================
3516         **Action: Parse era name from string starting at str.Index.
3517         **Returns: An era value.
3518         **Arguments:    str: a __DTString.  The parsing will start from the
3519         **              next character after str.Index.
3520         **Exceptions: FormatException if an era name can not be found.
3521         ==============================================================================*/
3522
3523         private static bool MatchEraName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result)
3524         {
3525             if (str.GetNext())
3526             {
3527                 int[] eras = dtfi.Calendar.Eras;
3528
3529                 if (eras != null)
3530                 {
3531                     for (int i = 0; i < eras.Length; i++)
3532                     {
3533                         String searchStr = dtfi.GetEraName(eras[i]);
3534                         if (str.MatchSpecifiedWord(searchStr))
3535                         {
3536                             str.Index += (searchStr.Length - 1);
3537                             result = eras[i];
3538                             return (true);
3539                         }
3540                         searchStr = dtfi.GetAbbreviatedEraName(eras[i]);
3541                         if (str.MatchSpecifiedWord(searchStr))
3542                         {
3543                             str.Index += (searchStr.Length - 1);
3544                             result = eras[i];
3545                             return (true);
3546                         }
3547                     }
3548                 }
3549             }
3550             return false;
3551         }
3552
3553         /*=================================MatchTimeMark==================================
3554         **Action: Parse the time mark (AM/PM) from string starting at str.Index.
3555         **Returns: TM_AM or TM_PM.
3556         **Arguments:    str: a __DTString.  The parsing will start from the
3557         **              next character after str.Index.
3558         **Exceptions: FormatException if a time mark can not be found.
3559         ==============================================================================*/
3560
3561         private static bool MatchTimeMark(ref __DTString str, DateTimeFormatInfo dtfi, ref TM result)
3562         {
3563             result = TM.NotSet;
3564             // In some cultures have empty strings in AM/PM mark. E.g. af-ZA (0x0436), the AM mark is "", and PM mark is "nm".
3565             if (dtfi.AMDesignator.Length == 0)
3566             {
3567                 result = TM.AM;
3568             }
3569             if (dtfi.PMDesignator.Length == 0)
3570             {
3571                 result = TM.PM;
3572             }
3573
3574             if (str.GetNext())
3575             {
3576                 String searchStr = dtfi.AMDesignator;
3577                 if (searchStr.Length > 0)
3578                 {
3579                     if (str.MatchSpecifiedWord(searchStr))
3580                     {
3581                         // Found an AM timemark with length > 0.
3582                         str.Index += (searchStr.Length - 1);
3583                         result = TM.AM;
3584                         return (true);
3585                     }
3586                 }
3587                 searchStr = dtfi.PMDesignator;
3588                 if (searchStr.Length > 0)
3589                 {
3590                     if (str.MatchSpecifiedWord(searchStr))
3591                     {
3592                         // Found a PM timemark with length > 0.
3593                         str.Index += (searchStr.Length - 1);
3594                         result = TM.PM;
3595                         return (true);
3596                     }
3597                 }
3598                 str.Index--; // Undo the GetNext call.
3599             }
3600             if (result != TM.NotSet)
3601             {
3602                 // If one of the AM/PM marks is empty string, return the result.
3603                 return (true);
3604             }
3605             return false;
3606         }
3607
3608         /*=================================MatchAbbreviatedTimeMark==================================
3609         **Action: Parse the abbreviated time mark (AM/PM) from string starting at str.Index.
3610         **Returns: TM_AM or TM_PM.
3611         **Arguments:    str: a __DTString.  The parsing will start from the
3612         **              next character after str.Index.
3613         **Exceptions: FormatException if a abbreviated time mark can not be found.
3614         ==============================================================================*/
3615
3616         private static bool MatchAbbreviatedTimeMark(ref __DTString str, DateTimeFormatInfo dtfi, ref TM result)
3617         {
3618             // NOTENOTE : the assumption here is that abbreviated time mark is the first
3619             // character of the AM/PM designator.  If this invariant changes, we have to
3620             // change the code below.
3621             if (str.GetNext())
3622             {
3623                 if (str.GetChar() == dtfi.AMDesignator[0])
3624                 {
3625                     result = TM.AM;
3626                     return (true);
3627                 }
3628                 if (str.GetChar() == dtfi.PMDesignator[0])
3629                 {
3630                     result = TM.PM;
3631                     return (true);
3632                 }
3633             }
3634             return false;
3635         }
3636
3637         /*=================================CheckNewValue==================================
3638         **Action: Check if currentValue is initialized.  If not, return the newValue.
3639         **        If yes, check if the current value is equal to newValue.  Return false
3640         **        if they are not equal.  This is used to check the case like "d" and "dd" are both
3641         **        used to format a string.
3642         **Returns: the correct value for currentValue.
3643         **Arguments:
3644         **Exceptions:
3645         ==============================================================================*/
3646
3647         private static bool CheckNewValue(ref int currentValue, int newValue, char patternChar, ref DateTimeResult result)
3648         {
3649             if (currentValue == -1)
3650             {
3651                 currentValue = newValue;
3652                 return (true);
3653             }
3654             else
3655             {
3656                 if (newValue != currentValue)
3657                 {
3658                     result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), patternChar);
3659                     return (false);
3660                 }
3661             }
3662             return (true);
3663         }
3664
3665         private static DateTime GetDateTimeNow(ref DateTimeResult result, ref DateTimeStyles styles)
3666         {
3667             if ((result.flags & ParseFlags.CaptureOffset) != 0)
3668             {
3669                 if ((result.flags & ParseFlags.TimeZoneUsed) != 0)
3670                 {
3671                     // use the supplied offset to calculate 'Now'
3672                     return new DateTime(DateTime.UtcNow.Ticks + result.timeZoneOffset.Ticks, DateTimeKind.Unspecified);
3673                 }
3674                 else if ((styles & DateTimeStyles.AssumeUniversal) != 0)
3675                 {
3676                     // assume the offset is Utc
3677                     return DateTime.UtcNow;
3678                 }
3679             }
3680
3681             // assume the offset is Local            
3682             return DateTime.Now;
3683         }
3684
3685         private static bool CheckDefaultDateTime(ref DateTimeResult result, ref Calendar cal, DateTimeStyles styles)
3686         {
3687             if ((result.flags & ParseFlags.CaptureOffset) != 0)
3688             {
3689                 // DateTimeOffset.Parse should allow dates without a year, but only if there is also no time zone marker;
3690                 // e.g. "May 1 5pm" is OK, but "May 1 5pm -08:30" is not.  This is somewhat pragmatic, since we would
3691                 // have to rearchitect parsing completely to allow this one case to correctly handle things like leap
3692                 // years and leap months.  Is is an extremely corner case, and DateTime is basically incorrect in that
3693                 // case today.
3694                 //
3695                 // values like "11:00Z" or "11:00 -3:00" are also acceptable
3696                 //
3697                 // if ((month or day is set) and (year is not set and time zone is set))
3698                 //
3699                 if (((result.Month != -1) || (result.Day != -1))
3700                     && ((result.Year == -1 || ((result.flags & ParseFlags.YearDefault) != 0)) && (result.flags & ParseFlags.TimeZoneUsed) != 0))
3701                 {
3702                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_MissingIncompleteDate), null);
3703                     return false;
3704                 }
3705             }
3706
3707
3708             if ((result.Year == -1) || (result.Month == -1) || (result.Day == -1))
3709             {
3710                 /*
3711                 The following table describes the behaviors of getting the default value
3712                 when a certain year/month/day values are missing.
3713
3714                 An "X" means that the value exists.  And "--" means that value is missing.
3715
3716                 Year    Month   Day =>  ResultYear  ResultMonth     ResultDay       Note
3717
3718                 X       X       X       Parsed year Parsed month    Parsed day
3719                 X       X       --      Parsed Year Parsed month    First day       If we have year and month, assume the first day of that month.
3720                 X       --      X       Parsed year First month     Parsed day      If the month is missing, assume first month of that year.
3721                 X       --      --      Parsed year First month     First day       If we have only the year, assume the first day of that year.
3722
3723                 --      X       X       CurrentYear Parsed month    Parsed day      If the year is missing, assume the current year.
3724                 --      X       --      CurrentYear Parsed month    First day       If we have only a month value, assume the current year and current day.
3725                 --      --      X       CurrentYear First month     Parsed day      If we have only a day value, assume current year and first month.
3726                 --      --      --      CurrentYear Current month   Current day     So this means that if the date string only contains time, you will get current date.
3727
3728                 */
3729
3730                 DateTime now = GetDateTimeNow(ref result, ref styles);
3731                 if (result.Month == -1 && result.Day == -1)
3732                 {
3733                     if (result.Year == -1)
3734                     {
3735                         if ((styles & DateTimeStyles.NoCurrentDateDefault) != 0)
3736                         {
3737                             // If there is no year/month/day values, and NoCurrentDateDefault flag is used,
3738                             // set the year/month/day value to the beginning year/month/day of DateTime().
3739                             // Note we should be using Gregorian for the year/month/day.
3740                             cal = GregorianCalendar.GetDefaultInstance();
3741                             result.Year = result.Month = result.Day = 1;
3742                         }
3743                         else
3744                         {
3745                             // Year/Month/Day are all missing.
3746                             result.Year = cal.GetYear(now);
3747                             result.Month = cal.GetMonth(now);
3748                             result.Day = cal.GetDayOfMonth(now);
3749                         }
3750                     }
3751                     else
3752                     {
3753                         // Month/Day are both missing.
3754                         result.Month = 1;
3755                         result.Day = 1;
3756                     }
3757                 }
3758                 else
3759                 {
3760                     if (result.Year == -1)
3761                     {
3762                         result.Year = cal.GetYear(now);
3763                     }
3764                     if (result.Month == -1)
3765                     {
3766                         result.Month = 1;
3767                     }
3768                     if (result.Day == -1)
3769                     {
3770                         result.Day = 1;
3771                     }
3772                 }
3773             }
3774             // Set Hour/Minute/Second to zero if these value are not in str.
3775             if (result.Hour == -1) result.Hour = 0;
3776             if (result.Minute == -1) result.Minute = 0;
3777             if (result.Second == -1) result.Second = 0;
3778             if (result.era == -1) result.era = Calendar.CurrentEra;
3779             return true;
3780         }
3781
3782         // Expand a pre-defined format string (like "D" for long date) to the real format that
3783         // we are going to use in the date time parsing.
3784         // This method also set the dtfi according/parseInfo to some special pre-defined
3785         // formats.
3786         //
3787         private static String ExpandPredefinedFormat(String format, ref DateTimeFormatInfo dtfi, ref ParsingInfo parseInfo, ref DateTimeResult result)
3788         {
3789             //
3790             // Check the format to see if we need to override the dtfi to be InvariantInfo,
3791             // and see if we need to set up the userUniversalTime flag.
3792             //
3793             switch (format[0])
3794             {
3795                 case 'o':
3796                 case 'O':       // Round Trip Format
3797                     parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
3798                     dtfi = DateTimeFormatInfo.InvariantInfo;
3799                     break;
3800                 case 'r':
3801                 case 'R':       // RFC 1123 Standard.  (in Universal time)
3802                     parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
3803                     dtfi = DateTimeFormatInfo.InvariantInfo;
3804
3805                     if ((result.flags & ParseFlags.CaptureOffset) != 0)
3806                     {
3807                         result.flags |= ParseFlags.Rfc1123Pattern;
3808                     }
3809                     break;
3810                 case 's':       // Sortable format (in local time)
3811                     dtfi = DateTimeFormatInfo.InvariantInfo;
3812                     parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
3813                     break;
3814                 case 'u':       // Universal time format in sortable format.
3815                     parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
3816                     dtfi = DateTimeFormatInfo.InvariantInfo;
3817
3818                     if ((result.flags & ParseFlags.CaptureOffset) != 0)
3819                     {
3820                         result.flags |= ParseFlags.UtcSortPattern;
3821                     }
3822                     break;
3823                 case 'U':       // Universal time format with culture-dependent format.
3824                     parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
3825                     result.flags |= ParseFlags.TimeZoneUsed;
3826                     result.timeZoneOffset = new TimeSpan(0);
3827                     result.flags |= ParseFlags.TimeZoneUtc;
3828                     if (dtfi.Calendar.GetType() != typeof(GregorianCalendar))
3829                     {
3830                         dtfi = (DateTimeFormatInfo)dtfi.Clone();
3831                         dtfi.Calendar = GregorianCalendar.GetDefaultInstance();
3832                     }
3833                     break;
3834             }
3835
3836             //
3837             // Expand the pre-defined format character to the real format from DateTimeFormatInfo.
3838             //
3839             return (DateTimeFormat.GetRealFormat(format, dtfi));
3840         }
3841
3842
3843
3844
3845
3846         // Given a specified format character, parse and update the parsing result.
3847         //
3848         private static bool ParseByFormat(
3849             ref __DTString str,
3850             ref __DTString format,
3851             ref ParsingInfo parseInfo,
3852             DateTimeFormatInfo dtfi,
3853             ref DateTimeResult result)
3854         {
3855             int tokenLen = 0;
3856             int tempYear = 0, tempMonth = 0, tempDay = 0, tempDayOfWeek = 0, tempHour = 0, tempMinute = 0, tempSecond = 0;
3857             double tempFraction = 0;
3858             TM tempTimeMark = 0;
3859
3860             char ch = format.GetChar();
3861
3862             switch (ch)
3863             {
3864                 case 'y':
3865                     tokenLen = format.GetRepeatCount();
3866                     bool parseResult;
3867                     if (dtfi.HasForceTwoDigitYears)
3868                     {
3869                         parseResult = ParseDigits(ref str, 1, 4, out tempYear);
3870                     }
3871                     else
3872                     {
3873                         if (tokenLen <= 2)
3874                         {
3875                             parseInfo.fUseTwoDigitYear = true;
3876                         }
3877                         parseResult = ParseDigits(ref str, tokenLen, out tempYear);
3878                     }
3879                     if (!parseResult && parseInfo.fCustomNumberParser)
3880                     {
3881                         parseResult = parseInfo.parseNumberDelegate(ref str, tokenLen, out tempYear);
3882                     }
3883                     if (!parseResult)
3884                     {
3885                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3886                         return (false);
3887                     }
3888                     if (!CheckNewValue(ref result.Year, tempYear, ch, ref result))
3889                     {
3890                         return (false);
3891                     }
3892                     break;
3893                 case 'M':
3894                     tokenLen = format.GetRepeatCount();
3895                     if (tokenLen <= 2)
3896                     {
3897                         if (!ParseDigits(ref str, tokenLen, out tempMonth))
3898                         {
3899                             if (!parseInfo.fCustomNumberParser ||
3900                                 !parseInfo.parseNumberDelegate(ref str, tokenLen, out tempMonth))
3901                             {
3902                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3903                                 return (false);
3904                             }
3905                         }
3906                     }
3907                     else
3908                     {
3909                         if (tokenLen == 3)
3910                         {
3911                             if (!MatchAbbreviatedMonthName(ref str, dtfi, ref tempMonth))
3912                             {
3913                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3914                                 return (false);
3915                             }
3916                         }
3917                         else
3918                         {
3919                             if (!MatchMonthName(ref str, dtfi, ref tempMonth))
3920                             {
3921                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3922                                 return (false);
3923                             }
3924                         }
3925                         result.flags |= ParseFlags.ParsedMonthName;
3926                     }
3927                     if (!CheckNewValue(ref result.Month, tempMonth, ch, ref result))
3928                     {
3929                         return (false);
3930                     }
3931                     break;
3932                 case 'd':
3933                     // Day & Day of week
3934                     tokenLen = format.GetRepeatCount();
3935                     if (tokenLen <= 2)
3936                     {
3937                         // "d" & "dd"
3938
3939                         if (!ParseDigits(ref str, tokenLen, out tempDay))
3940                         {
3941                             if (!parseInfo.fCustomNumberParser ||
3942                                 !parseInfo.parseNumberDelegate(ref str, tokenLen, out tempDay))
3943                             {
3944                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3945                                 return (false);
3946                             }
3947                         }
3948                         if (!CheckNewValue(ref result.Day, tempDay, ch, ref result))
3949                         {
3950                             return (false);
3951                         }
3952                     }
3953                     else
3954                     {
3955                         if (tokenLen == 3)
3956                         {
3957                             // "ddd"
3958                             if (!MatchAbbreviatedDayName(ref str, dtfi, ref tempDayOfWeek))
3959                             {
3960                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3961                                 return (false);
3962                             }
3963                         }
3964                         else
3965                         {
3966                             // "dddd*"
3967                             if (!MatchDayName(ref str, dtfi, ref tempDayOfWeek))
3968                             {
3969                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3970                                 return (false);
3971                             }
3972                         }
3973                         if (!CheckNewValue(ref parseInfo.dayOfWeek, tempDayOfWeek, ch, ref result))
3974                         {
3975                             return (false);
3976                         }
3977                     }
3978                     break;
3979                 case 'g':
3980                     tokenLen = format.GetRepeatCount();
3981                     // Put the era value in result.era.
3982                     if (!MatchEraName(ref str, dtfi, ref result.era))
3983                     {
3984                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3985                         return (false);
3986                     }
3987                     break;
3988                 case 'h':
3989                     parseInfo.fUseHour12 = true;
3990                     tokenLen = format.GetRepeatCount();
3991                     if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempHour))
3992                     {
3993                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
3994                         return (false);
3995                     }
3996                     if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result))
3997                     {
3998                         return (false);
3999                     }
4000                     break;
4001                 case 'H':
4002                     tokenLen = format.GetRepeatCount();
4003                     if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempHour))
4004                     {
4005                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4006                         return (false);
4007                     }
4008                     if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result))
4009                     {
4010                         return (false);
4011                     }
4012                     break;
4013                 case 'm':
4014                     tokenLen = format.GetRepeatCount();
4015                     if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempMinute))
4016                     {
4017                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4018                         return (false);
4019                     }
4020                     if (!CheckNewValue(ref result.Minute, tempMinute, ch, ref result))
4021                     {
4022                         return (false);
4023                     }
4024                     break;
4025                 case 's':
4026                     tokenLen = format.GetRepeatCount();
4027                     if (!ParseDigits(ref str, (tokenLen < 2 ? 1 : 2), out tempSecond))
4028                     {
4029                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4030                         return (false);
4031                     }
4032                     if (!CheckNewValue(ref result.Second, tempSecond, ch, ref result))
4033                     {
4034                         return (false);
4035                     }
4036                     break;
4037                 case 'f':
4038                 case 'F':
4039                     tokenLen = format.GetRepeatCount();
4040                     if (tokenLen <= DateTimeFormat.MaxSecondsFractionDigits)
4041                     {
4042                         if (!ParseFractionExact(ref str, tokenLen, ref tempFraction))
4043                         {
4044                             if (ch == 'f')
4045                             {
4046                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4047                                 return (false);
4048                             }
4049                         }
4050                         if (result.fraction < 0)
4051                         {
4052                             result.fraction = tempFraction;
4053                         }
4054                         else
4055                         {
4056                             if (tempFraction != result.fraction)
4057                             {
4058                                 result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), ch);
4059                                 return (false);
4060                             }
4061                         }
4062                     }
4063                     else
4064                     {
4065                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4066                         return (false);
4067                     }
4068                     break;
4069                 case 't':
4070                     // AM/PM designator
4071                     tokenLen = format.GetRepeatCount();
4072                     if (tokenLen == 1)
4073                     {
4074                         if (!MatchAbbreviatedTimeMark(ref str, dtfi, ref tempTimeMark))
4075                         {
4076                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4077                             return (false);
4078                         }
4079                     }
4080                     else
4081                     {
4082                         if (!MatchTimeMark(ref str, dtfi, ref tempTimeMark))
4083                         {
4084                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4085                             return (false);
4086                         }
4087                     }
4088
4089                     if (parseInfo.timeMark == TM.NotSet)
4090                     {
4091                         parseInfo.timeMark = tempTimeMark;
4092                     }
4093                     else
4094                     {
4095                         if (parseInfo.timeMark != tempTimeMark)
4096                         {
4097                             result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), ch);
4098                             return (false);
4099                         }
4100                     }
4101                     break;
4102                 case 'z':
4103                     // timezone offset
4104                     tokenLen = format.GetRepeatCount();
4105                     {
4106                         TimeSpan tempTimeZoneOffset = new TimeSpan(0);
4107                         if (!ParseTimeZoneOffset(ref str, tokenLen, ref tempTimeZoneOffset))
4108                         {
4109                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4110                             return (false);
4111                         }
4112                         if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset)
4113                         {
4114                             result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), 'z');
4115                             return (false);
4116                         }
4117                         result.timeZoneOffset = tempTimeZoneOffset;
4118                         result.flags |= ParseFlags.TimeZoneUsed;
4119                     }
4120                     break;
4121                 case 'Z':
4122                     if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero)
4123                     {
4124                         result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), 'Z');
4125                         return (false);
4126                     }
4127
4128                     result.flags |= ParseFlags.TimeZoneUsed;
4129                     result.timeZoneOffset = new TimeSpan(0);
4130                     result.flags |= ParseFlags.TimeZoneUtc;
4131
4132                     // The updating of the indexes is to reflect that ParseExact MatchXXX methods assume that
4133                     // they need to increment the index and Parse GetXXX do not. Since we are calling a Parse
4134                     // method from inside ParseExact we need to adjust this. Long term, we should try to
4135                     // eliminate this discrepancy.
4136                     str.Index++;
4137                     if (!GetTimeZoneName(ref str))
4138                     {
4139                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4140                         return false;
4141                     }
4142                     str.Index--;
4143                     break;
4144                 case 'K':
4145                     // This should parse either as a blank, the 'Z' character or a local offset like "-07:00"
4146                     if (str.Match('Z'))
4147                     {
4148                         if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero)
4149                         {
4150                             result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), 'K');
4151                             return (false);
4152                         }
4153
4154                         result.flags |= ParseFlags.TimeZoneUsed;
4155                         result.timeZoneOffset = new TimeSpan(0);
4156                         result.flags |= ParseFlags.TimeZoneUtc;
4157                     }
4158                     else if (str.Match('+') || str.Match('-'))
4159                     {
4160                         str.Index--; // Put the character back for the parser
4161                         TimeSpan tempTimeZoneOffset = new TimeSpan(0);
4162                         if (!ParseTimeZoneOffset(ref str, 3, ref tempTimeZoneOffset))
4163                         {
4164                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4165                             return (false);
4166                         }
4167                         if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset)
4168                         {
4169                             result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_RepeatDateTimePattern), 'K');
4170                             return (false);
4171                         }
4172                         result.timeZoneOffset = tempTimeZoneOffset;
4173                         result.flags |= ParseFlags.TimeZoneUsed;
4174                     }
4175                     // Otherwise it is unspecified and we consume no characters
4176                     break;
4177                 case ':':
4178                     // 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
4179                     // We have to exclude the case when the time separator is more than one character and starts with ':' something like "::" for instance.
4180                     if (((dtfi.TimeSeparator.Length > 1 && dtfi.TimeSeparator[0] == ':') || !str.Match(':')) &&
4181                         !str.Match(dtfi.TimeSeparator))
4182                     {
4183                         // A time separator is expected.
4184                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4185                         return false;
4186                     }
4187                     break;
4188                 case '/':
4189                     // 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
4190                     // We have to exclude the case when the date separator is more than one character and starts with '/' something like "//" for instance.
4191                     if (((dtfi.DateSeparator.Length > 1 && dtfi.DateSeparator[0] == '/') || !str.Match('/')) &&
4192                         !str.Match(dtfi.DateSeparator))
4193                     {
4194                         // A date separator is expected.
4195                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4196                         return false;
4197                     }
4198                     break;
4199                 case '\"':
4200                 case '\'':
4201                     StringBuilder enquotedString = StringBuilderCache.Acquire();
4202                     // Use ParseQuoteString so that we can handle escape characters within the quoted string.
4203                     if (!TryParseQuoteString(format.Value, format.Index, enquotedString, out tokenLen))
4204                     {
4205                         result.SetFailure(ParseFailureKind.FormatWithParameter, nameof(SR.Format_BadQuote), ch);
4206                         StringBuilderCache.Release(enquotedString);
4207                         return (false);
4208                     }
4209                     format.Index += tokenLen - 1;
4210
4211                     // Some cultures uses space in the quoted string.  E.g. Spanish has long date format as:
4212                     // "dddd, dd' de 'MMMM' de 'yyyy".  When inner spaces flag is set, we should skip whitespaces if there is space
4213                     // in the quoted string.
4214                     String quotedStr = StringBuilderCache.GetStringAndRelease(enquotedString);
4215
4216                     for (int i = 0; i < quotedStr.Length; i++)
4217                     {
4218                         if (quotedStr[i] == ' ' && parseInfo.fAllowInnerWhite)
4219                         {
4220                             str.SkipWhiteSpaces();
4221                         }
4222                         else if (!str.Match(quotedStr[i]))
4223                         {
4224                             // Can not find the matching quoted string.
4225                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4226                             return false;
4227                         }
4228                     }
4229
4230                     // The "r" and "u" formats incorrectly quoted 'GMT' and 'Z', respectively.  We cannot
4231                     // correct this mistake for DateTime.ParseExact for compatibility reasons, but we can 
4232                     // fix it for DateTimeOffset.ParseExact as DateTimeOffset has not been publically released
4233                     // with this issue.
4234                     if ((result.flags & ParseFlags.CaptureOffset) != 0)
4235                     {
4236                         if ((result.flags & ParseFlags.Rfc1123Pattern) != 0 && quotedStr == GMTName)
4237                         {
4238                             result.flags |= ParseFlags.TimeZoneUsed;
4239                             result.timeZoneOffset = TimeSpan.Zero;
4240                         }
4241                         else if ((result.flags & ParseFlags.UtcSortPattern) != 0 && quotedStr == ZuluName)
4242                         {
4243                             result.flags |= ParseFlags.TimeZoneUsed;
4244                             result.timeZoneOffset = TimeSpan.Zero;
4245                         }
4246                     }
4247
4248                     break;
4249                 case '%':
4250                     // Skip this so we can get to the next pattern character.
4251                     // Used in case like "%d", "%y"
4252
4253                     // Make sure the next character is not a '%' again.
4254                     if (format.Index >= format.Value.Length - 1 || format.Value[format.Index + 1] == '%')
4255                     {
4256                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadFormatSpecifier), null);
4257                         return false;
4258                     }
4259                     break;
4260                 case '\\':
4261                     // Escape character. For example, "\d".
4262                     // Get the next character in format, and see if we can
4263                     // find a match in str.
4264                     if (format.GetNext())
4265                     {
4266                         if (!str.Match(format.GetChar()))
4267                         {
4268                             // Can not find a match for the escaped character.
4269                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4270                             return false;
4271                         }
4272                     }
4273                     else
4274                     {
4275                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadFormatSpecifier), null);
4276                         return false;
4277                     }
4278                     break;
4279                 case '.':
4280                     if (!str.Match(ch))
4281                     {
4282                         if (format.GetNext())
4283                         {
4284                             // If we encounter the pattern ".F", and the dot is not present, it is an optional
4285                             // second fraction and we can skip this format.
4286                             if (format.Match('F'))
4287                             {
4288                                 format.GetRepeatCount();
4289                                 break;
4290                             }
4291                         }
4292                         result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4293                         return false;
4294                     }
4295                     break;
4296                 default:
4297                     if (ch == ' ')
4298                     {
4299                         if (parseInfo.fAllowInnerWhite)
4300                         {
4301                             // Skip whitespaces if AllowInnerWhite.
4302                             // Do nothing here.
4303                         }
4304                         else
4305                         {
4306                             if (!str.Match(ch))
4307                             {
4308                                 // If the space does not match, and trailing space is allowed, we do
4309                                 // one more step to see if the next format character can lead to
4310                                 // successful parsing.
4311                                 // This is used to deal with special case that a empty string can match
4312                                 // a specific pattern.
4313                                 // The example here is af-ZA, which has a time format like "hh:mm:ss tt".  However,
4314                                 // its AM symbol is "" (empty string).  If fAllowTrailingWhite is used, and time is in
4315                                 // the AM, we will trim the whitespaces at the end, which will lead to a failure
4316                                 // when we are trying to match the space before "tt".
4317                                 if (parseInfo.fAllowTrailingWhite)
4318                                 {
4319                                     if (format.GetNext())
4320                                     {
4321                                         if (ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result))
4322                                         {
4323                                             return (true);
4324                                         }
4325                                     }
4326                                 }
4327                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4328                                 return false;
4329                             }
4330                             // Found a macth.
4331                         }
4332                     }
4333                     else
4334                     {
4335                         if (format.MatchSpecifiedWord(GMTName))
4336                         {
4337                             format.Index += (GMTName.Length - 1);
4338                             // Found GMT string in format.  This means the DateTime string
4339                             // is in GMT timezone.
4340                             result.flags |= ParseFlags.TimeZoneUsed;
4341                             result.timeZoneOffset = TimeSpan.Zero;
4342                             if (!str.Match(GMTName))
4343                             {
4344                                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4345                                 return false;
4346                             }
4347                         }
4348                         else if (!str.Match(ch))
4349                         {
4350                             // ch is expected.
4351                             result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4352                             return false;
4353                         }
4354                     }
4355                     break;
4356             } // switch
4357             return (true);
4358         }
4359
4360         //
4361         // The pos should point to a quote character. This method will
4362         // get the string enclosed by the quote character.
4363         //
4364         internal static bool TryParseQuoteString(ReadOnlySpan<char> format, int pos, StringBuilder result, out int returnValue)
4365         {
4366             //
4367             // NOTE : pos will be the index of the quote character in the 'format' string.
4368             //
4369             returnValue = 0;
4370             int formatLen = format.Length;
4371             int beginPos = pos;
4372             char quoteChar = format[pos++]; // Get the character used to quote the following string.
4373
4374             bool foundQuote = false;
4375             while (pos < formatLen)
4376             {
4377                 char ch = format[pos++];
4378                 if (ch == quoteChar)
4379                 {
4380                     foundQuote = true;
4381                     break;
4382                 }
4383                 else if (ch == '\\')
4384                 {
4385                     // The following are used to support escaped character.
4386                     // Escaped character is also supported in the quoted string.
4387                     // Therefore, someone can use a format like "'minute:' mm\"" to display:
4388                     //  minute: 45"
4389                     // because the second double quote is escaped.
4390                     if (pos < formatLen)
4391                     {
4392                         result.Append(format[pos++]);
4393                     }
4394                     else
4395                     {
4396                         //
4397                         // This means that '\' is at the end of the formatting string.
4398                         //
4399                         return false;
4400                     }
4401                 }
4402                 else
4403                 {
4404                     result.Append(ch);
4405                 }
4406             }
4407
4408             if (!foundQuote)
4409             {
4410                 // Here we can't find the matching quote.
4411                 return false;
4412             }
4413
4414             //
4415             // Return the character count including the begin/end quote characters and enclosed string.
4416             //
4417             returnValue = (pos - beginPos);
4418             return true;
4419         }
4420
4421
4422
4423
4424         /*=================================DoStrictParse==================================
4425         **Action: Do DateTime parsing using the format in formatParam.
4426         **Returns: The parsed DateTime.
4427         **Arguments:
4428         **Exceptions:
4429         **
4430         **Notes:
4431         **  When the following general formats are used, InvariantInfo is used in dtfi:
4432         **      'r', 'R', 's'.
4433         **  When the following general formats are used, the time is assumed to be in Universal time.
4434         **
4435         **Limitations:
4436         **  Only GregarianCalendar is supported for now.
4437         **  Only support GMT timezone.
4438         ==============================================================================*/
4439
4440         private static bool DoStrictParse(
4441             ReadOnlySpan<char> s,
4442             String formatParam,
4443             DateTimeStyles styles,
4444             DateTimeFormatInfo dtfi,
4445             ref DateTimeResult result)
4446         {
4447             ParsingInfo parseInfo = new ParsingInfo();
4448             parseInfo.Init();
4449
4450             parseInfo.calendar = dtfi.Calendar;
4451             parseInfo.fAllowInnerWhite = ((styles & DateTimeStyles.AllowInnerWhite) != 0);
4452             parseInfo.fAllowTrailingWhite = ((styles & DateTimeStyles.AllowTrailingWhite) != 0);
4453
4454             // We need the original values of the following two below.
4455             String originalFormat = formatParam;
4456
4457             if (formatParam.Length == 1)
4458             {
4459                 if (((result.flags & ParseFlags.CaptureOffset) != 0) && formatParam[0] == 'U')
4460                 {
4461                     // The 'U' format is not allowed for DateTimeOffset
4462                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadFormatSpecifier), null);
4463                     return false;
4464                 }
4465                 formatParam = ExpandPredefinedFormat(formatParam, ref dtfi, ref parseInfo, ref result);
4466             }
4467
4468             bool bTimeOnly = false;
4469             result.calendar = parseInfo.calendar;
4470
4471             if (parseInfo.calendar.ID == CalendarId.HEBREW)
4472             {
4473                 parseInfo.parseNumberDelegate = m_hebrewNumberParser;
4474                 parseInfo.fCustomNumberParser = true;
4475             }
4476
4477             // Reset these values to negative one so that we could throw exception
4478             // if we have parsed every item twice.
4479             result.Hour = result.Minute = result.Second = -1;
4480
4481             __DTString format = new __DTString(formatParam.AsReadOnlySpan(), dtfi, false);
4482             __DTString str = new __DTString(s, dtfi, false);
4483
4484             if (parseInfo.fAllowTrailingWhite)
4485             {
4486                 // Trim trailing spaces if AllowTrailingWhite.
4487                 format.TrimTail();
4488                 format.RemoveTrailingInQuoteSpaces();
4489                 str.TrimTail();
4490             }
4491
4492             if ((styles & DateTimeStyles.AllowLeadingWhite) != 0)
4493             {
4494                 format.SkipWhiteSpaces();
4495                 format.RemoveLeadingInQuoteSpaces();
4496                 str.SkipWhiteSpaces();
4497             }
4498
4499             //
4500             // Scan every character in format and match the pattern in str.
4501             //
4502             while (format.GetNext())
4503             {
4504                 // We trim inner spaces here, so that we will not eat trailing spaces when
4505                 // AllowTrailingWhite is not used.
4506                 if (parseInfo.fAllowInnerWhite)
4507                 {
4508                     str.SkipWhiteSpaces();
4509                 }
4510                 if (!ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result))
4511                 {
4512                     return (false);
4513                 }
4514             }
4515
4516             if (str.Index < str.Value.Length - 1)
4517             {
4518                 // There are still remaining character in str.
4519                 result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4520                 return false;
4521             }
4522
4523             if (parseInfo.fUseTwoDigitYear && ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) == 0))
4524             {
4525                 // A two digit year value is expected. Check if the parsed year value is valid.
4526                 if (result.Year >= 100)
4527                 {
4528                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4529                     return false;
4530                 }
4531                 try
4532                 {
4533                     result.Year = parseInfo.calendar.ToFourDigitYear(result.Year);
4534                 }
4535                 catch (ArgumentOutOfRangeException e)
4536                 {
4537                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), e);
4538                     return false;
4539                 }
4540             }
4541
4542             if (parseInfo.fUseHour12)
4543             {
4544                 if (parseInfo.timeMark == TM.NotSet)
4545                 {
4546                     // hh is used, but no AM/PM designator is specified.
4547                     // Assume the time is AM.
4548                     // Don't throw exceptions in here becasue it is very confusing for the caller.
4549                     // I always got confused myself when I use "hh:mm:ss" to parse a time string,
4550                     // and ParseExact() throws on me (because I didn't use the 24-hour clock 'HH').
4551                     parseInfo.timeMark = TM.AM;
4552                 }
4553                 if (result.Hour > 12)
4554                 {
4555                     // AM/PM is used, but the value for HH is too big.
4556                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4557                     return false;
4558                 }
4559                 if (parseInfo.timeMark == TM.AM)
4560                 {
4561                     if (result.Hour == 12)
4562                     {
4563                         result.Hour = 0;
4564                     }
4565                 }
4566                 else
4567                 {
4568                     result.Hour = (result.Hour == 12) ? 12 : result.Hour + 12;
4569                 }
4570             }
4571             else
4572             {
4573                 // Military (24-hour time) mode
4574                 //
4575                 // AM cannot be set with a 24-hour time like 17:15.
4576                 // PM cannot be set with a 24-hour time like 03:15.
4577                 if ((parseInfo.timeMark == TM.AM && result.Hour >= 12)
4578                     || (parseInfo.timeMark == TM.PM && result.Hour < 12))
4579                 {
4580                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDateTime), null);
4581                     return false;
4582                 }
4583             }
4584
4585
4586             // Check if the parased string only contains hour/minute/second values.
4587             bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);
4588             if (!CheckDefaultDateTime(ref result, ref parseInfo.calendar, styles))
4589             {
4590                 return false;
4591             }
4592
4593             if (!bTimeOnly && dtfi.HasYearMonthAdjustment)
4594             {
4595                 if (!dtfi.YearMonthAdjustment(ref result.Year, ref result.Month, ((result.flags & ParseFlags.ParsedMonthName) != 0)))
4596                 {
4597                     result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar), null);
4598                     return false;
4599                 }
4600             }
4601             if (!parseInfo.calendar.TryToDateTime(result.Year, result.Month, result.Day,
4602                     result.Hour, result.Minute, result.Second, 0, result.era, out result.parsedDate))
4603             {
4604                 result.SetFailure(ParseFailureKind.FormatBadDateTimeCalendar, nameof(SR.Format_BadDateTimeCalendar), null);
4605                 return false;
4606             }
4607             if (result.fraction > 0)
4608             {
4609                 result.parsedDate = result.parsedDate.AddTicks((long)Math.Round(result.fraction * Calendar.TicksPerSecond));
4610             }
4611
4612             //
4613             // We have to check day of week before we adjust to the time zone.
4614             // It is because the value of day of week may change after adjusting
4615             // to the time zone.
4616             //
4617             if (parseInfo.dayOfWeek != -1)
4618             {
4619                 //
4620                 // Check if day of week is correct.
4621                 //
4622                 if (parseInfo.dayOfWeek != (int)parseInfo.calendar.GetDayOfWeek(result.parsedDate))
4623                 {
4624                     result.SetFailure(ParseFailureKind.Format, nameof(SR.Format_BadDayOfWeek), null);
4625                     return false;
4626                 }
4627             }
4628
4629
4630             if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly))
4631             {
4632                 return false;
4633             }
4634             return true;
4635         }
4636
4637         private static Exception GetDateTimeParseException(ref DateTimeResult result)
4638         {
4639             switch (result.failure)
4640             {
4641                 case ParseFailureKind.ArgumentNull:
4642                     return new ArgumentNullException(result.failureArgumentName, SR.GetResourceString(result.failureMessageID));
4643                 case ParseFailureKind.Format:
4644                     return new FormatException(SR.GetResourceString(result.failureMessageID));
4645                 case ParseFailureKind.FormatWithParameter:
4646                     return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), result.failureMessageFormatArgument));
4647                 case ParseFailureKind.FormatBadDateTimeCalendar:
4648                     return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID), result.calendar));
4649                 default:
4650                     Debug.Fail("Unkown DateTimeParseFailure: " + result);
4651                     return null;
4652             }
4653         }
4654
4655         // Builds with _LOGGING defined (x86dbg, amd64chk, etc) support tracing
4656         // Set the following internal-only/unsupported environment variables to enable DateTime tracing to the console:
4657         //
4658         // COMPlus_LogEnable=1
4659         // COMPlus_LogToConsole=1
4660         // COMPlus_LogLevel=9
4661         // COMPlus_ManagedLogFacility=0x00001000
4662         [Conditional("_LOGGING")]
4663         internal static void LexTraceExit(string message, DS dps)
4664         {
4665 #if _LOGGING
4666             if (!_tracingEnabled)
4667                 return;
4668             BCLDebug.Trace("DATETIME", "[DATETIME] Lex return {0}, DS.{1}", message, dps);
4669 #endif // _LOGGING
4670         }
4671         [Conditional("_LOGGING")]
4672         internal static void PTSTraceExit(DS dps, bool passed)
4673         {
4674 #if _LOGGING
4675             if (!_tracingEnabled)
4676                 return;
4677             BCLDebug.Trace("DATETIME", "[DATETIME] ProcessTerminalState {0} @ DS.{1}", passed ? "passed" : "failed", dps);
4678 #endif // _LOGGING
4679         }
4680         [Conditional("_LOGGING")]
4681         internal static void TPTraceExit(string message, DS dps)
4682         {
4683 #if _LOGGING
4684             if (!_tracingEnabled)
4685                 return;
4686             BCLDebug.Trace("DATETIME", "[DATETIME] TryParse return {0}, DS.{1}", message, dps);
4687 #endif // _LOGGING
4688         }
4689         [Conditional("_LOGGING")]
4690         internal static void DTFITrace(DateTimeFormatInfo dtfi)
4691         {
4692 #if _LOGGING
4693             if (!_tracingEnabled)
4694                 return;
4695
4696             BCLDebug.Trace("DATETIME", "[DATETIME] DateTimeFormatInfo Properties");
4697 #if !FEATURE_COREFX_GLOBALIZATION
4698             BCLDebug.Trace("DATETIME", " NativeCalendarName {0}", Hex(dtfi.NativeCalendarName));
4699 #endif
4700             BCLDebug.Trace("DATETIME", "       AMDesignator {0}", Hex(dtfi.AMDesignator));
4701             BCLDebug.Trace("DATETIME", "       PMDesignator {0}", Hex(dtfi.PMDesignator));
4702             BCLDebug.Trace("DATETIME", "      TimeSeparator {0}", Hex(dtfi.TimeSeparator));
4703             BCLDebug.Trace("DATETIME", "      AbbrvDayNames {0}", Hex(dtfi.AbbreviatedDayNames));
4704             BCLDebug.Trace("DATETIME", "   ShortestDayNames {0}", Hex(dtfi.ShortestDayNames));
4705             BCLDebug.Trace("DATETIME", "           DayNames {0}", Hex(dtfi.DayNames));
4706             BCLDebug.Trace("DATETIME", "    AbbrvMonthNames {0}", Hex(dtfi.AbbreviatedMonthNames));
4707             BCLDebug.Trace("DATETIME", "         MonthNames {0}", Hex(dtfi.MonthNames));
4708             BCLDebug.Trace("DATETIME", " AbbrvMonthGenNames {0}", Hex(dtfi.AbbreviatedMonthGenitiveNames));
4709             BCLDebug.Trace("DATETIME", "      MonthGenNames {0}", Hex(dtfi.MonthGenitiveNames));
4710 #endif // _LOGGING
4711         }
4712 #if _LOGGING
4713         // return a string in the form: "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
4714         internal static string Hex(string[] strs)
4715         {
4716             if (strs == null || strs.Length == 0)
4717                 return String.Empty;
4718             if (strs.Length == 1)
4719                 return Hex(strs[0]);
4720
4721             int curLineLength = 0;
4722             int maxLineLength = 55;
4723             int newLinePadding = 20;
4724
4725
4726             //invariant: strs.Length >= 2
4727             StringBuilder buffer = new StringBuilder();
4728             buffer.Append(Hex(strs[0]));
4729             curLineLength = buffer.Length;
4730             String s;
4731
4732             for (int i = 1; i < strs.Length - 1; i++)
4733             {
4734                 s = Hex(strs[i]);
4735
4736                 if (s.Length > maxLineLength || (curLineLength + s.Length + 2) > maxLineLength)
4737                 {
4738                     buffer.Append(',');
4739                     buffer.Append(Environment.NewLine);
4740                     buffer.Append(' ', newLinePadding);
4741                     curLineLength = 0;
4742                 }
4743                 else
4744                 {
4745                     buffer.Append(", ");
4746                     curLineLength += 2;
4747                 }
4748                 buffer.Append(s);
4749                 curLineLength += s.Length;
4750             }
4751
4752             buffer.Append(',');
4753             s = Hex(strs[strs.Length - 1]);
4754             if (s.Length > maxLineLength || (curLineLength + s.Length + 6) > maxLineLength)
4755             {
4756                 buffer.Append(Environment.NewLine);
4757                 buffer.Append(' ', newLinePadding);
4758             }
4759             else
4760             {
4761                 buffer.Append(' ');
4762             }
4763             buffer.Append(s);
4764             return buffer.ToString();
4765         }
4766         // return a string in the form: "Sun"
4767         internal static string Hex(string str) => Hex(str.AsReadOnlySpan());
4768         internal static string Hex(ReadOnlySpan<char> str)
4769         {
4770             StringBuilder buffer = new StringBuilder();
4771             buffer.Append("\"");
4772             for (int i = 0; i < str.Length; i++)
4773             {
4774                 if (str[i] <= '\x007f')
4775                     buffer.Append(str[i]);
4776                 else
4777                     buffer.Append("\\u" + ((int)str[i]).ToString("x4", CultureInfo.InvariantCulture));
4778             }
4779             buffer.Append("\"");
4780             return buffer.ToString();
4781         }
4782         // return an unicode escaped string form of char c
4783         internal static String Hex(char c)
4784         {
4785             if (c <= '\x007f')
4786                 return c.ToString(CultureInfo.InvariantCulture);
4787             else
4788                 return "\\u" + ((int)c).ToString("x4", CultureInfo.InvariantCulture);
4789         }
4790
4791         internal static bool _tracingEnabled = BCLDebug.CheckEnabled("DATETIME");
4792 #endif // _LOGGING
4793     }
4794
4795
4796     //
4797     // This is a string parsing helper which wraps a String object.
4798     // It has a Index property which tracks
4799     // the current parsing pointer of the string.
4800     //
4801     internal ref struct __DTString
4802     {
4803         //
4804         // Value propery: stores the real string to be parsed.
4805         //
4806         internal ReadOnlySpan<char> Value;
4807
4808         //
4809         // Index property: points to the character that we are currently parsing.
4810         //
4811         internal int Index;
4812
4813         // The length of Value string.
4814         internal int Length => Value.Length;
4815
4816         // The current chracter to be looked at.
4817         internal char m_current;
4818
4819         private CompareInfo m_info;
4820         // Flag to indicate if we encouter an digit, we should check for token or not.
4821         // In some cultures, such as mn-MN, it uses "\x0031\x00a0\x0434\x04af\x0433\x044d\x044d\x0440\x00a0\x0441\x0430\x0440" in month names.
4822         private bool m_checkDigitToken;
4823
4824         internal __DTString(ReadOnlySpan<char> str, DateTimeFormatInfo dtfi, bool checkDigitToken) : this(str, dtfi)
4825         {
4826             m_checkDigitToken = checkDigitToken;
4827         }
4828
4829         internal __DTString(ReadOnlySpan<char> str, DateTimeFormatInfo dtfi)
4830         {
4831             Index = -1;
4832             Value = str;
4833
4834             m_current = '\0';
4835             if (dtfi != null)
4836             {
4837                 m_info = dtfi.CompareInfo;
4838                 m_checkDigitToken = ((dtfi.FormatFlags & DateTimeFormatFlags.UseDigitPrefixInTokens) != 0);
4839             }
4840             else
4841             {
4842                 m_info = CultureInfo.CurrentCulture.CompareInfo;
4843                 m_checkDigitToken = false;
4844             }
4845         }
4846
4847         internal CompareInfo CompareInfo
4848         {
4849             get { return m_info; }
4850         }
4851
4852         //
4853         // Advance the Index.
4854         // Return true if Index is NOT at the end of the string.
4855         //
4856         // Typical usage:
4857         // while (str.GetNext())
4858         // {
4859         //     char ch = str.GetChar()
4860         // }
4861         internal bool GetNext()
4862         {
4863             Index++;
4864             if (Index < Length)
4865             {
4866                 m_current = Value[Index];
4867                 return (true);
4868             }
4869             return (false);
4870         }
4871
4872         internal bool AtEnd()
4873         {
4874             return Index < Length ? false : true;
4875         }
4876
4877         internal bool Advance(int count)
4878         {
4879             Debug.Assert(Index + count <= Length, "__DTString::Advance: Index + count <= len");
4880             Index += count;
4881             if (Index < Length)
4882             {
4883                 m_current = Value[Index];
4884                 return (true);
4885             }
4886             return (false);
4887         }
4888
4889
4890         // Used by DateTime.Parse() to get the next token.
4891         internal void GetRegularToken(out TokenType tokenType, out int tokenValue, DateTimeFormatInfo dtfi)
4892         {
4893             tokenValue = 0;
4894             if (Index >= Length)
4895             {
4896                 tokenType = TokenType.EndOfString;
4897                 return;
4898             }
4899
4900             tokenType = TokenType.UnknownToken;
4901
4902         Start:
4903             if (DateTimeParse.IsDigit(m_current))
4904             {
4905                 // This is a digit.
4906                 tokenValue = m_current - '0';
4907                 int value;
4908                 int start = Index;
4909
4910                 //
4911                 // Collect other digits.
4912                 //
4913                 while (++Index < Length)
4914                 {
4915                     m_current = Value[Index];
4916                     value = m_current - '0';
4917                     if (value >= 0 && value <= 9)
4918                     {
4919                         tokenValue = tokenValue * 10 + value;
4920                     }
4921                     else
4922                     {
4923                         break;
4924                     }
4925                 }
4926                 if (Index - start > DateTimeParse.MaxDateTimeNumberDigits)
4927                 {
4928                     tokenType = TokenType.NumberToken;
4929                     tokenValue = -1;
4930                 }
4931                 else if (Index - start < 3)
4932                 {
4933                     tokenType = TokenType.NumberToken;
4934                 }
4935                 else
4936                 {
4937                     // If there are more than 3 digits, assume that it's a year value.
4938                     tokenType = TokenType.YearNumberToken;
4939                 }
4940                 if (m_checkDigitToken)
4941                 {
4942                     int save = Index;
4943                     char saveCh = m_current;
4944                     // Re-scan using the staring Index to see if this is a token.
4945                     Index = start;  // To include the first digit.
4946                     m_current = Value[Index];
4947                     TokenType tempType;
4948                     int tempValue;
4949                     // This DTFI has tokens starting with digits.
4950                     // E.g. mn-MN has month name like "\x0031\x00a0\x0434\x04af\x0433\x044d\x044d\x0440\x00a0\x0441\x0430\x0440"
4951                     if (dtfi.Tokenize(TokenType.RegularTokenMask, out tempType, out tempValue, ref this))
4952                     {
4953                         tokenType = tempType;
4954                         tokenValue = tempValue;
4955                         // This is a token, so the Index has been advanced propertly in DTFI.Tokenizer().
4956                     }
4957                     else
4958                     {
4959                         // Use the number token value.
4960                         // Restore the index.
4961                         Index = save;
4962                         m_current = saveCh;
4963                     }
4964                 }
4965             }
4966             else if (Char.IsWhiteSpace(m_current))
4967             {
4968                 // Just skip to the next character.
4969                 while (++Index < Length)
4970                 {
4971                     m_current = Value[Index];
4972                     if (!(Char.IsWhiteSpace(m_current)))
4973                     {
4974                         goto Start;
4975                     }
4976                 }
4977                 // We have reached the end of string.
4978                 tokenType = TokenType.EndOfString;
4979             }
4980             else
4981             {
4982                 dtfi.Tokenize(TokenType.RegularTokenMask, out tokenType, out tokenValue, ref this);
4983             }
4984         }
4985
4986         internal TokenType GetSeparatorToken(DateTimeFormatInfo dtfi, out int indexBeforeSeparator, out char charBeforeSeparator)
4987         {
4988             indexBeforeSeparator = Index;
4989             charBeforeSeparator = m_current;
4990             TokenType tokenType;
4991             if (!SkipWhiteSpaceCurrent())
4992             {
4993                 // Reach the end of the string.
4994                 return (TokenType.SEP_End);
4995             }
4996             if (!DateTimeParse.IsDigit(m_current))
4997             {
4998                 // Not a digit.  Tokenize it.
4999                 int tokenValue;
5000                 bool found = dtfi.Tokenize(TokenType.SeparatorTokenMask, out tokenType, out tokenValue, ref this);
5001                 if (!found)
5002                 {
5003                     tokenType = TokenType.SEP_Space;
5004                 }
5005             }
5006             else
5007             {
5008                 // Do nothing here.  If we see a number, it will not be a separator. There is no need wasting time trying to find the
5009                 // separator token.
5010                 tokenType = TokenType.SEP_Space;
5011             }
5012             return (tokenType);
5013         }
5014
5015         [MethodImpl(MethodImplOptions.AggressiveInlining)]
5016         internal bool MatchSpecifiedWord(String target) =>
5017             Index + target.Length <= Length &&
5018             m_info.Compare(Value.Slice(Index, target.Length), target, CompareOptions.IgnoreCase) == 0;
5019
5020         private static readonly Char[] WhiteSpaceChecks = new Char[] { ' ', '\u00A0' };
5021
5022         internal bool MatchSpecifiedWords(String target, bool checkWordBoundary, ref int matchLength)
5023         {
5024             int valueRemaining = Value.Length - Index;
5025             matchLength = target.Length;
5026
5027             if (matchLength > valueRemaining || m_info.Compare(Value.Slice(Index, matchLength), target, CompareOptions.IgnoreCase) != 0)
5028             {
5029                 // Check word by word
5030                 int targetPosition = 0;                 // Where we are in the target string
5031                 int thisPosition = Index;         // Where we are in this string
5032                 int wsIndex = target.IndexOfAny(WhiteSpaceChecks, targetPosition);
5033                 if (wsIndex == -1)
5034                 {
5035                     return false;
5036                 }
5037                 do
5038                 {
5039                     int segmentLength = wsIndex - targetPosition;
5040                     if (thisPosition >= Value.Length - segmentLength)
5041                     { // Subtraction to prevent overflow.
5042                         return false;
5043                     }
5044                     if (segmentLength == 0)
5045                     {
5046                         // If segmentLength == 0, it means that we have leading space in the target string.
5047                         // In that case, skip the leading spaces in the target and this string.
5048                         matchLength--;
5049                     }
5050                     else
5051                     {
5052                         // Make sure we also have whitespace in the input string
5053                         if (!Char.IsWhiteSpace(Value[thisPosition + segmentLength]))
5054                         {
5055                             return false;
5056                         }
5057                         if (m_info.Compare(Value.Slice(thisPosition, segmentLength), target.AsReadOnlySpan().Slice(targetPosition, segmentLength), CompareOptions.IgnoreCase) != 0)
5058                         {
5059                             return false;
5060                         }
5061                         // Advance the input string
5062                         thisPosition = thisPosition + segmentLength + 1;
5063                     }
5064                     // Advance our target string
5065                     targetPosition = wsIndex + 1;
5066
5067
5068                     // Skip past multiple whitespace
5069                     while (thisPosition < Value.Length && Char.IsWhiteSpace(Value[thisPosition]))
5070                     {
5071                         thisPosition++;
5072                         matchLength++;
5073                     }
5074                 } while ((wsIndex = target.IndexOfAny(WhiteSpaceChecks, targetPosition)) >= 0);
5075                 // now check the last segment;
5076                 if (targetPosition < target.Length)
5077                 {
5078                     int segmentLength = target.Length - targetPosition;
5079                     if (thisPosition > Value.Length - segmentLength)
5080                     {
5081                         return false;
5082                     }
5083                     if (m_info.Compare(Value.Slice(thisPosition, segmentLength), target.AsReadOnlySpan().Slice(targetPosition, segmentLength), CompareOptions.IgnoreCase) != 0)
5084                     {
5085                         return false;
5086                     }
5087                 }
5088             }
5089
5090             if (checkWordBoundary)
5091             {
5092                 int nextCharIndex = Index + matchLength;
5093                 if (nextCharIndex < Value.Length)
5094                 {
5095                     if (Char.IsLetter(Value[nextCharIndex]))
5096                     {
5097                         return (false);
5098                     }
5099                 }
5100             }
5101             return (true);
5102         }
5103
5104         //
5105         // Check to see if the string starting from Index is a prefix of
5106         // str.
5107         // If a match is found, true value is returned and Index is updated to the next character to be parsed.
5108         // Otherwise, Index is unchanged.
5109         //
5110         internal bool Match(String str)
5111         {
5112             if (++Index >= Length)
5113             {
5114                 return (false);
5115             }
5116
5117             if (str.Length > (Value.Length - Index))
5118             {
5119                 return false;
5120             }
5121
5122             if (m_info.Compare(Value.Slice(Index, str.Length), str, CompareOptions.Ordinal) == 0)
5123             {
5124                 // Update the Index to the end of the matching string.
5125                 // So the following GetNext()/Match() opeartion will get
5126                 // the next character to be parsed.
5127                 Index += (str.Length - 1);
5128                 return (true);
5129             }
5130             return (false);
5131         }
5132
5133         internal bool Match(char ch)
5134         {
5135             if (++Index >= Length)
5136             {
5137                 return (false);
5138             }
5139             if (Value[Index] == ch)
5140             {
5141                 m_current = ch;
5142                 return (true);
5143             }
5144             Index--;
5145             return (false);
5146         }
5147
5148         //
5149         //  Actions: From the current position, try matching the longest word in the specified string array.
5150         //      E.g. words[] = {"AB", "ABC", "ABCD"}, if the current position points to a substring like "ABC DEF",
5151         //          MatchLongestWords(words, ref MaxMatchStrLen) will return 1 (the index), and maxMatchLen will be 3.
5152         //  Returns:
5153         //      The index that contains the longest word to match
5154         //  Arguments:
5155         //      words   The string array that contains words to search.
5156         //      maxMatchStrLen  [in/out] the initailized maximum length.  This parameter can be used to
5157         //          find the longest match in two string arrays.
5158         //
5159         internal int MatchLongestWords(String[] words, ref int maxMatchStrLen)
5160         {
5161             int result = -1;
5162             for (int i = 0; i < words.Length; i++)
5163             {
5164                 String word = words[i];
5165                 int matchLength = word.Length;
5166                 if (MatchSpecifiedWords(word, false, ref matchLength))
5167                 {
5168                     if (matchLength > maxMatchStrLen)
5169                     {
5170                         maxMatchStrLen = matchLength;
5171                         result = i;
5172                     }
5173                 }
5174             }
5175
5176             return (result);
5177         }
5178
5179         //
5180         // Get the number of repeat character after the current character.
5181         // For a string "hh:mm:ss" at Index of 3. GetRepeatCount() = 2, and Index
5182         // will point to the second ':'.
5183         //
5184         internal int GetRepeatCount()
5185         {
5186             char repeatChar = Value[Index];
5187             int pos = Index + 1;
5188             while ((pos < Length) && (Value[pos] == repeatChar))
5189             {
5190                 pos++;
5191             }
5192             int repeatCount = (pos - Index);
5193             // Update the Index to the end of the repeated characters.
5194             // So the following GetNext() opeartion will get
5195             // the next character to be parsed.
5196             Index = pos - 1;
5197             return (repeatCount);
5198         }
5199
5200         // Return false when end of string is encountered or a non-digit character is found.
5201         [MethodImpl(MethodImplOptions.AggressiveInlining)]
5202         internal bool GetNextDigit() =>
5203             ++Index < Length &&
5204             DateTimeParse.IsDigit(Value[Index]);
5205
5206         //
5207         // Get the current character.
5208         //
5209         internal char GetChar()
5210         {
5211             Debug.Assert(Index >= 0 && Index < Length, "Index >= 0 && Index < len");
5212             return (Value[Index]);
5213         }
5214
5215         //
5216         // Convert the current character to a digit, and return it.
5217         //
5218         internal int GetDigit()
5219         {
5220             Debug.Assert(Index >= 0 && Index < Length, "Index >= 0 && Index < len");
5221             Debug.Assert(DateTimeParse.IsDigit(Value[Index]), "IsDigit(Value[Index])");
5222             return (Value[Index] - '0');
5223         }
5224
5225         //
5226         // Eat White Space ahead of the current position
5227         //
5228         // Return false if end of string is encountered.
5229         //
5230         internal void SkipWhiteSpaces()
5231         {
5232             // Look ahead to see if the next character
5233             // is a whitespace.
5234             while (Index + 1 < Length)
5235             {
5236                 char ch = Value[Index + 1];
5237                 if (!Char.IsWhiteSpace(ch))
5238                 {
5239                     return;
5240                 }
5241                 Index++;
5242             }
5243             return;
5244         }
5245
5246         //
5247         // Skip white spaces from the current position
5248         //
5249         // Return false if end of string is encountered.
5250         //
5251         internal bool SkipWhiteSpaceCurrent()
5252         {
5253             if (Index >= Length)
5254             {
5255                 return (false);
5256             }
5257
5258             if (!Char.IsWhiteSpace(m_current))
5259             {
5260                 return (true);
5261             }
5262
5263             while (++Index < Length)
5264             {
5265                 m_current = Value[Index];
5266                 if (!Char.IsWhiteSpace(m_current))
5267                 {
5268                     return (true);
5269                 }
5270                 // Nothing here.
5271             }
5272             return (false);
5273         }
5274
5275         internal void TrimTail()
5276         {
5277             int i = Length - 1;
5278             while (i >= 0 && Char.IsWhiteSpace(Value[i]))
5279             {
5280                 i--;
5281             }
5282             Value = Value.Slice(0, i + 1);
5283         }
5284
5285         // Trim the trailing spaces within a quoted string.
5286         // Call this after TrimTail() is done.
5287         internal void RemoveTrailingInQuoteSpaces()
5288         {
5289             int i = Length - 1;
5290             if (i <= 1)
5291             {
5292                 return;
5293             }
5294             char ch = Value[i];
5295             // Check if the last character is a quote.
5296             if (ch == '\'' || ch == '\"')
5297             {
5298                 if (Char.IsWhiteSpace(Value[i - 1]))
5299                 {
5300                     i--;
5301                     while (i >= 1 && Char.IsWhiteSpace(Value[i - 1]))
5302                     {
5303                         i--;
5304                     }
5305                     Value = Value.Remove(i, Value.Length - 1 - i);
5306                 }
5307             }
5308         }
5309
5310         // Trim the leading spaces within a quoted string.
5311         // Call this after the leading spaces before quoted string are trimmed.
5312         internal void RemoveLeadingInQuoteSpaces()
5313         {
5314             if (Length <= 2)
5315             {
5316                 return;
5317             }
5318             int i = 0;
5319             char ch = Value[i];
5320             // Check if the last character is a quote.
5321             if (ch == '\'' || ch == '\"')
5322             {
5323                 while ((i + 1) < Length && Char.IsWhiteSpace(Value[i + 1]))
5324                 {
5325                     i++;
5326                 }
5327                 if (i != 0)
5328                 {
5329                     Value = Value.Remove(1, i);
5330                 }
5331             }
5332         }
5333
5334         internal DTSubString GetSubString()
5335         {
5336             DTSubString sub = new DTSubString();
5337             sub.index = Index;
5338             sub.s = Value;
5339             while (Index + sub.length < Length)
5340             {
5341                 DTSubStringType currentType;
5342                 Char ch = Value[Index + sub.length];
5343                 if (ch >= '0' && ch <= '9')
5344                 {
5345                     currentType = DTSubStringType.Number;
5346                 }
5347                 else
5348                 {
5349                     currentType = DTSubStringType.Other;
5350                 }
5351
5352                 if (sub.length == 0)
5353                 {
5354                     sub.type = currentType;
5355                 }
5356                 else
5357                 {
5358                     if (sub.type != currentType)
5359                     {
5360                         break;
5361                     }
5362                 }
5363                 sub.length++;
5364                 if (currentType == DTSubStringType.Number)
5365                 {
5366                     // Incorporate the number into the value
5367                     // Limit the digits to prevent overflow
5368                     if (sub.length > DateTimeParse.MaxDateTimeNumberDigits)
5369                     {
5370                         sub.type = DTSubStringType.Invalid;
5371                         return sub;
5372                     }
5373                     int number = ch - '0';
5374                     Debug.Assert(number >= 0 && number <= 9, "number >= 0 && number <= 9");
5375                     sub.value = sub.value * 10 + number;
5376                 }
5377                 else
5378                 {
5379                     // For non numbers, just return this length 1 token. This should be expanded
5380                     // to more types of thing if this parsing approach is used for things other
5381                     // than numbers and single characters
5382                     break;
5383                 }
5384             }
5385             if (sub.length == 0)
5386             {
5387                 sub.type = DTSubStringType.End;
5388                 return sub;
5389             }
5390
5391             return sub;
5392         }
5393
5394         internal void ConsumeSubString(DTSubString sub)
5395         {
5396             Debug.Assert(sub.index == Index, "sub.index == Index");
5397             Debug.Assert(sub.index + sub.length <= Length, "sub.index + sub.length <= len");
5398             Index = sub.index + sub.length;
5399             if (Index < Length)
5400             {
5401                 m_current = Value[Index];
5402             }
5403         }
5404     }
5405
5406     internal enum DTSubStringType
5407     {
5408         Unknown = 0,
5409         Invalid = 1,
5410         Number = 2,
5411         End = 3,
5412         Other = 4,
5413     }
5414
5415     internal ref struct DTSubString
5416     {
5417         internal ReadOnlySpan<char> s;
5418         internal Int32 index;
5419         internal Int32 length;
5420         internal DTSubStringType type;
5421         internal Int32 value;
5422
5423         internal Char this[Int32 relativeIndex]
5424         {
5425             get
5426             {
5427                 return s[index + relativeIndex];
5428             }
5429         }
5430     }
5431
5432     //
5433     // The buffer to store the parsing token.
5434     //
5435     internal
5436     struct DateTimeToken
5437     {
5438         internal DateTimeParse.DTT dtt;    // Store the token
5439         internal TokenType suffix; // Store the CJK Year/Month/Day suffix (if any)
5440         internal int num;    // Store the number that we are parsing (if any)
5441     }
5442
5443     //
5444     // The buffer to store temporary parsing information.
5445     //
5446     internal
5447     unsafe struct DateTimeRawInfo
5448     {
5449         private int* num;
5450         internal int numCount;
5451         internal int month;
5452         internal int year;
5453         internal int dayOfWeek;
5454         internal int era;
5455         internal DateTimeParse.TM timeMark;
5456         internal double fraction;
5457         internal bool hasSameDateAndTimeSeparators;
5458
5459         internal void Init(int* numberBuffer)
5460         {
5461             month = -1;
5462             year = -1;
5463             dayOfWeek = -1;
5464             era = -1;
5465             timeMark = DateTimeParse.TM.NotSet;
5466             fraction = -1;
5467             num = numberBuffer;
5468         }
5469         internal unsafe void AddNumber(int value)
5470         {
5471             num[numCount++] = value;
5472         }
5473         internal unsafe int GetNumber(int index)
5474         {
5475             return num[index];
5476         }
5477     }
5478
5479     internal enum ParseFailureKind
5480     {
5481         None = 0,
5482         ArgumentNull = 1,
5483         Format = 2,
5484         FormatWithParameter = 3,
5485         FormatBadDateTimeCalendar = 4,  // FormatException when ArgumentOutOfRange is thrown by a Calendar.TryToDateTime().
5486     };
5487
5488     [Flags]
5489     internal enum ParseFlags
5490     {
5491         HaveYear = 0x00000001,
5492         HaveMonth = 0x00000002,
5493         HaveDay = 0x00000004,
5494         HaveHour = 0x00000008,
5495         HaveMinute = 0x00000010,
5496         HaveSecond = 0x00000020,
5497         HaveTime = 0x00000040,
5498         HaveDate = 0x00000080,
5499         TimeZoneUsed = 0x00000100,
5500         TimeZoneUtc = 0x00000200,
5501         ParsedMonthName = 0x00000400,
5502         CaptureOffset = 0x00000800,
5503         YearDefault = 0x00001000,
5504         Rfc1123Pattern = 0x00002000,
5505         UtcSortPattern = 0x00004000,
5506     }
5507
5508     //
5509     // This will store the result of the parsing.  And it will be eventually
5510     // used to construct a DateTime instance.
5511     //
5512     internal
5513     struct DateTimeResult
5514     {
5515         internal int Year;
5516         internal int Month;
5517         internal int Day;
5518         //
5519         // Set time defualt to 00:00:00.
5520         //
5521         internal int Hour;
5522         internal int Minute;
5523         internal int Second;
5524         internal double fraction;
5525
5526         internal int era;
5527
5528         internal ParseFlags flags;
5529
5530         internal TimeSpan timeZoneOffset;
5531
5532         internal Calendar calendar;
5533
5534         internal DateTime parsedDate;
5535
5536         internal ParseFailureKind failure;
5537         internal string failureMessageID;
5538         internal object failureMessageFormatArgument;
5539         internal string failureArgumentName;
5540
5541         internal void Init()
5542         {
5543             Year = -1;
5544             Month = -1;
5545             Day = -1;
5546             fraction = -1;
5547             era = -1;
5548         }
5549
5550         internal void SetDate(int year, int month, int day)
5551         {
5552             Year = year;
5553             Month = month;
5554             Day = day;
5555         }
5556         internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument)
5557         {
5558             this.failure = failure;
5559             this.failureMessageID = failureMessageID;
5560             this.failureMessageFormatArgument = failureMessageFormatArgument;
5561         }
5562
5563         internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument, string failureArgumentName)
5564         {
5565             this.failure = failure;
5566             this.failureMessageID = failureMessageID;
5567             this.failureMessageFormatArgument = failureMessageFormatArgument;
5568             this.failureArgumentName = failureArgumentName;
5569         }
5570     }
5571
5572     // This is the helper data structure used in ParseExact().
5573     internal struct ParsingInfo
5574     {
5575         internal Calendar calendar;
5576         internal int dayOfWeek;
5577         internal DateTimeParse.TM timeMark;
5578
5579         internal bool fUseHour12;
5580         internal bool fUseTwoDigitYear;
5581         internal bool fAllowInnerWhite;
5582         internal bool fAllowTrailingWhite;
5583         internal bool fCustomNumberParser;
5584         internal DateTimeParse.MatchNumberDelegate parseNumberDelegate;
5585
5586         internal void Init()
5587         {
5588             dayOfWeek = -1;
5589             timeMark = DateTimeParse.TM.NotSet;
5590         }
5591     }
5592
5593     //
5594     // The type of token that will be returned by DateTimeFormatInfo.Tokenize().
5595     //
5596     internal enum TokenType
5597     {
5598         // The valid token should start from 1.
5599
5600         // Regular tokens. The range is from 0x00 ~ 0xff.
5601         NumberToken = 1,    // The number.  E.g. "12"
5602         YearNumberToken = 2,    // The number which is considered as year number, which has 3 or more digits.  E.g. "2003"
5603         Am = 3,    // AM timemark. E.g. "AM"
5604         Pm = 4,    // PM timemark. E.g. "PM"
5605         MonthToken = 5,    // A word (or words) that represents a month name.  E.g. "March"
5606         EndOfString = 6,    // End of string
5607         DayOfWeekToken = 7,    // A word (or words) that represents a day of week name.  E.g. "Monday" or "Mon"
5608         TimeZoneToken = 8,    // A word that represents a timezone name. E.g. "GMT"
5609         EraToken = 9,    // A word that represents a era name. E.g. "A.D."
5610         DateWordToken = 10,   // A word that can appear in a DateTime string, but serves no parsing semantics.  E.g. "de" in Spanish culture.
5611         UnknownToken = 11,   // An unknown word, which signals an error in parsing.
5612         HebrewNumber = 12,   // A number that is composed of Hebrew text.  Hebrew calendar uses Hebrew digits for year values, month values, and day values.
5613         JapaneseEraToken = 13,   // Era name for JapaneseCalendar
5614         TEraToken = 14,   // Era name for TaiwanCalendar
5615         IgnorableSymbol = 15,   // A separator like "," that is equivalent to whitespace
5616
5617
5618         // Separator tokens.
5619         SEP_Unk = 0x100,         // Unknown separator.
5620         SEP_End = 0x200,    // The end of the parsing string.
5621         SEP_Space = 0x300,    // Whitespace (including comma).
5622         SEP_Am = 0x400,    // AM timemark. E.g. "AM"
5623         SEP_Pm = 0x500,    // PM timemark. E.g. "PM"
5624         SEP_Date = 0x600,    // date separator. E.g. "/"
5625         SEP_Time = 0x700,    // time separator. E.g. ":"
5626         SEP_YearSuff = 0x800,    // Chinese/Japanese/Korean year suffix.
5627         SEP_MonthSuff = 0x900,    // Chinese/Japanese/Korean month suffix.
5628         SEP_DaySuff = 0xa00,    // Chinese/Japanese/Korean day suffix.
5629         SEP_HourSuff = 0xb00,   // Chinese/Japanese/Korean hour suffix.
5630         SEP_MinuteSuff = 0xc00,   // Chinese/Japanese/Korean minute suffix.
5631         SEP_SecondSuff = 0xd00,   // Chinese/Japanese/Korean second suffix.
5632         SEP_LocalTimeMark = 0xe00,   // 'T', used in ISO 8601 format.
5633         SEP_DateOrOffset = 0xf00,   // '-' which could be a date separator or start of a time zone offset
5634
5635         RegularTokenMask = 0x00ff,
5636         SeparatorTokenMask = 0xff00,
5637     }
5638 }