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