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