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