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