3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 ////////////////////////////////////////////////////////////////////////////
8 // Class: DateTimeParse
10 // Purpose: This class is called by DateTime to parse a date/time string.
12 ////////////////////////////////////////////////////////////////////////////
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;
27 ////////////////////////////////////////////////////////////////////////
29 //This class contains only static members
34 internal const Int32 MaxDateTimeNumberDigits = 8;
36 internal delegate bool MatchNumberDelegate(ref __DTString str, int digitLen, out int result);
38 internal static MatchNumberDelegate m_hebrewNumberParser = new MatchNumberDelegate(DateTimeParse.MatchHebrewDigits);
40 #if !FEATURE_CORECLR && !MONO
41 [SecuritySafeCritical]
42 internal static bool GetAmPmParseFlag()
44 return DateTime.EnableAmPmParseAdjustment();
47 internal static bool enableAmPmParseAdjustment = GetAmPmParseFlag();
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.
53 if (TryParseExact(s, format, dtfi, style, ref result)) {
54 return result.parsedDate;
57 throw GetDateTimeParseException(ref result);
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;
65 result.flags |= ParseFlags.CaptureOffset;
66 if (TryParseExact(s, format, dtfi, style, ref result)) {
67 offset = result.timeZoneOffset;
68 return result.parsedDate;
71 throw GetDateTimeParseException(ref 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.
79 if (TryParseExact(s, format, dtfi, style, ref resultData)) {
80 result = resultData.parsedDate;
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.
91 resultData.flags |= ParseFlags.CaptureOffset;
92 if (TryParseExact(s, format, dtfi, style, ref resultData)) {
93 result = resultData.parsedDate;
94 offset = resultData.timeZoneOffset;
100 internal static bool TryParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result) {
102 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "s");
105 if (format == null) {
106 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "format");
110 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
114 if (format.Length == 0) {
115 result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
119 Contract.Assert(dtfi != null, "dtfi == null");
121 return DoStrictParse(s, format, style, dtfi, ref result);
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.
128 if (TryParseExactMultiple(s, formats, dtfi, style, ref result)) {
129 return result.parsedDate;
132 throw GetDateTimeParseException(ref result);
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;
142 result.flags |= ParseFlags.CaptureOffset;
143 if (TryParseExactMultiple(s, formats, dtfi, style, ref result)) {
144 offset = result.timeZoneOffset;
145 return result.parsedDate;
148 throw GetDateTimeParseException(ref result);
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.
158 resultData.flags |= ParseFlags.CaptureOffset;
159 if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData)) {
160 result = resultData.parsedDate;
161 offset = resultData.timeZoneOffset;
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.
173 if (TryParseExactMultiple(s, formats, dtfi, style, ref resultData)) {
174 result = resultData.parsedDate;
180 internal static bool TryParseExactMultiple(String s, String[] formats,
181 DateTimeFormatInfo dtfi, DateTimeStyles style, ref DateTimeResult result) {
183 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "s");
186 if (formats == null) {
187 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "formats");
192 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
196 if (formats.Length == 0) {
197 result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
201 Contract.Assert(dtfi != null, "dtfi == null");
204 // Do a loop through the provided formats and see if we can parse succesfully in
205 // one of the formats.
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);
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.
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;
223 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
227 ////////////////////////////////////////////////////////////////////////////
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.
235 ////////////////////////////////////////////////////////////////////////////
237 internal enum DTT: int {
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.
260 NumLocalTimeMark = 19, // Num + 'T'
271 ////////////////////////////////////////////////////////////////////////////
273 // DateTime parsing state enumeration (DS.*)
275 ////////////////////////////////////////////////////////////////////////////
279 N = 1, // have one number
280 NN = 2, // have two numbers
282 // The following are known to be part of a date
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
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.
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.
302 // The following are known to be part of a time
304 T_Nt = 18, // have num followed by time separator
305 T_NNt = 19, // have two numbers followed by time separator
310 // The following are terminal states. These all have an action
311 // associated with them; and transition back to BEGIN.
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 .
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.
334 ////////////////////////////////////////////////////////////////////////////
336 // NOTE: The following state machine table is dependent on the order of the
337 // DS and DTT enumerations.
339 // For each non terminal state, the following table defines the next state
340 // for each given date token type.
342 ////////////////////////////////////////////////////////////////////////////
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
407 // End NumEnd NumAmPm NumSpace NumDaySep NumTimesep MonthEnd MonthSpace MonthDSep NumDateSuff NumTimeSuff DayOfWeek YearSpace YearDateSep YearEnd TimeZone Era UTCMark
409 internal const String GMTName = "GMT";
410 internal const String ZuluName = "Z";
413 // Search from the index of str at str.Index to see if the target string exists in the str.
415 private static bool MatchWord(ref __DTString str, String target)
417 int length = target.Length;
418 if (length > (str.Value.Length - str.Index)) {
422 if (str.CompareInfo.Compare(str.Value, str.Index, length,
423 target, 0, length, CompareOptions.IgnoreCase)!=0) {
427 int nextCharIndex = str.Index + target.Length;
429 if (nextCharIndex < str.Value.Length) {
430 char nextCh = str.Value[nextCharIndex];
431 if (Char.IsLetter(nextCh)) {
435 str.Index = nextCharIndex;
436 if (str.Index < str.len) {
437 str.m_current = str.Value[str.Index];
445 // Check the word at the current index to see if it matches GMT name or Zulu name.
447 private static bool GetTimeZoneName(ref __DTString str)
452 if (MatchWord(ref str, GMTName)) {
456 if (MatchWord(ref str, ZuluName)) {
463 internal static bool IsDigit(char ch) {
464 return (ch >= '0' && ch <= '9');
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.
475 ** str the parsing string
477 ============================================================================*/
479 private static bool ParseFraction(ref __DTString str, out double result) {
481 double decimalBase = 0.1;
485 && IsDigit(ch = str.m_current)) {
486 result += (ch - '0') * decimalBase;
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.
499 ** str the parsing string
501 ** FormatException if invalid timezone format is found.
502 ============================================================================*/
504 private static bool ParseTimeZone(ref __DTString str, ref TimeSpan result) {
505 // The hour/minute offset for timezone.
507 int minuteOffset = 0;
510 // Consume the +/- character that has already been read
511 sub = str.GetSubString();
512 if (sub.length != 1) {
515 char offsetChar = sub[0];
516 if (offsetChar != '+' && offsetChar != '-') {
519 str.ConsumeSubString(sub);
521 sub = str.GetSubString();
522 if (sub.type != DTSubStringType.Number) {
525 int value = sub.value;
526 int length = sub.length;
527 if (length == 1 || length == 2) {
528 // Parsing "+8" or "+08"
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) {
540 minuteOffset = sub.value;
541 str.ConsumeSubString(sub);
544 else if (length == 3 || length == 4) {
545 // Parsing "+800" or "+0800"
546 hourOffset = value / 100;
547 minuteOffset = value % 100;
548 str.ConsumeSubString(sub);
551 // Wrong number of digits
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) {
560 result = new TimeSpan(hourOffset, minuteOffset, 0);
561 if (offsetChar == '-') {
562 result = result.Negate();
567 // This is the helper function to handle timezone in string in the format like +/-0800
568 private static bool HandleTimeZone(ref __DTString str, ref DateTimeResult result)
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) {
576 nextCh = str.Value[str.Index + whitespaceCount];
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);
585 result.flags |= ParseFlags.TimeZoneUsed;
586 if (!ParseTimeZone(ref str, ref result.timeZoneOffset)) {
587 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
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.
599 [System.Security.SecuritySafeCritical] // auto-generated
600 private static Boolean Lex(DS dps, ref __DTString str, ref DateTimeToken dtok, ref DateTimeRawInfo raw, ref DateTimeResult result, ref DateTimeFormatInfo dtfi, DateTimeStyles styles)
605 int indexBeforeSeparator;
606 char charBeforeSeparator;
609 dtok.dtt = DTT.Unk; // Assume the token is unkown.
611 str.GetRegularToken(out tokenType, out tokenValue, dtfi);
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);
629 // Look at the regular token.
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);
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).
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];
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);
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))
665 LexTraceExit("0020 (value like \"12:01\" or \"12:\" followed by a non-TZ number", dps);
671 dtok.num = tokenValue;
672 if (tokenType == TokenType.YearNumberToken)
676 raw.year = tokenValue;
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
682 switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) {
683 case TokenType.SEP_End:
684 dtok.dtt = DTT.YearEnd;
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;
692 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
693 LexTraceExit("0030 (TM.AM/TM.PM Happened more than 1x)", dps);
696 case TokenType.SEP_Space:
697 dtok.dtt = DTT.YearSpace;
699 case TokenType.SEP_Date:
700 dtok.dtt = DTT.YearDateSep;
703 case TokenType.SEP_Time:
704 if (!raw.hasSameDateAndTimeSeparators)
706 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
707 LexTraceExit("0040 (Invalid separator after number)", dps);
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;
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;
726 dtok.dtt = DTT.YearDateSep;
729 case TokenType.SEP_YearSuff:
730 case TokenType.SEP_MonthSuff:
731 case TokenType.SEP_DaySuff:
732 dtok.dtt = DTT.NumDatesuff;
735 case TokenType.SEP_HourSuff:
736 case TokenType.SEP_MinuteSuff:
737 case TokenType.SEP_SecondSuff:
738 dtok.dtt = DTT.NumTimesuff;
742 // Invalid separator after number number.
743 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
744 LexTraceExit("0040 (Invalid separator after number)", dps);
748 // Found the token already. Return now.
750 LexTraceExit("0050 (success)", dps);
753 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
754 LexTraceExit("0060", dps);
757 switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
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.
763 case TokenType.SEP_End:
764 dtok.dtt = DTT.NumEnd;
765 raw.AddNumber(dtok.num);
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"
774 #if !FEATURE_CORECLR && !MONO
775 && enableAmPmParseAdjustment
779 if (!ProcessTerminaltState(DS.DX_NN, ref result, ref styles, ref raw, dtfi))
785 raw.AddNumber(dtok.num);
787 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
790 if (dps == DS.T_NNt || dps == DS.T_Nt) {
791 if (false == HandleTimeZone(ref str, ref result))
793 LexTraceExit("0070 (HandleTimeZone returned false)", dps);
798 case TokenType.SEP_Space:
799 dtok.dtt = DTT.NumSpace;
800 raw.AddNumber(dtok.num);
802 case TokenType.SEP_Date:
803 dtok.dtt = DTT.NumDatesep;
804 raw.AddNumber(dtok.num);
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;
816 dtok.dtt = DTT.NumDatesep;
818 raw.AddNumber(dtok.num);
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))
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);
829 dtok.dtt = DTT.NumTimesep;
830 raw.AddNumber(dtok.num);
832 case TokenType.SEP_YearSuff:
834 dtok.num = dtfi.Calendar.ToFourDigitYear(tokenValue);
836 catch (ArgumentOutOfRangeException e) {
837 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", e);
838 LexTraceExit("0075 (Calendar.ToFourDigitYear failed)", dps);
841 dtok.dtt = DTT.NumDatesuff;
844 case TokenType.SEP_MonthSuff:
845 case TokenType.SEP_DaySuff:
846 dtok.dtt = DTT.NumDatesuff;
849 case TokenType.SEP_HourSuff:
850 case TokenType.SEP_MinuteSuff:
851 case TokenType.SEP_SecondSuff:
852 dtok.dtt = DTT.NumTimesuff;
855 case TokenType.SEP_LocalTimeMark:
856 dtok.dtt = DTT.NumLocalTimeMark;
857 raw.AddNumber(dtok.num);
860 // Invalid separator after number number.
861 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
862 LexTraceExit("0080", dps);
866 case TokenType.HebrewNumber:
867 if (tokenValue >= 100) {
868 // This is a year number
869 if (raw.year == -1) {
870 raw.year = tokenValue;
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
876 switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) {
877 case TokenType.SEP_End:
878 dtok.dtt = DTT.YearEnd;
880 case TokenType.SEP_Space:
881 dtok.dtt = DTT.YearSpace;
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;
894 // Invalid separator after number number.
895 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
896 LexTraceExit("0090", dps);
900 // Invalid separator after number number.
901 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
902 LexTraceExit("0100", dps);
906 // This is a day number
907 dtok.num = tokenValue;
908 raw.AddNumber(dtok.num);
910 switch (sep = str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator)) {
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.
915 case TokenType.SEP_End:
916 dtok.dtt = DTT.NumEnd;
918 case TokenType.SEP_Space:
919 case TokenType.SEP_Date:
920 dtok.dtt = DTT.NumDatesep;
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;
932 dtok.dtt = DTT.NumDatesep;
936 // Invalid separator after number number.
937 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
938 LexTraceExit("0110", dps);
943 case TokenType.DayOfWeekToken:
944 if (raw.dayOfWeek == -1)
947 // This is a day of week name.
949 raw.dayOfWeek = tokenValue;
950 dtok.dtt = DTT.DayOfWeek;
952 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
953 LexTraceExit("0120 (DayOfWeek seen more than 1x)", dps);
957 case TokenType.MonthToken:
961 // This is a month name
963 switch(sep=str.GetSeparatorToken(dtfi, out indexBeforeSeparator, out charBeforeSeparator))
965 case TokenType.SEP_End:
966 dtok.dtt = DTT.MonthEnd;
968 case TokenType.SEP_Space:
969 dtok.dtt = DTT.MonthSpace;
971 case TokenType.SEP_Date:
972 dtok.dtt = DTT.MonthDatesep;
974 case TokenType.SEP_Time:
975 if (!raw.hasSameDateAndTimeSeparators)
977 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
978 LexTraceExit("0130 (Invalid separator after month name)", dps);
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;
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;
996 dtok.dtt = DTT.MonthDatesep;
1000 //Invalid separator after month name
1001 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1002 LexTraceExit("0130 (Invalid separator after month name)", dps);
1005 raw.month = tokenValue;
1007 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1008 LexTraceExit("0140 (MonthToken seen more than 1x)", dps);
1012 case TokenType.EraToken:
1013 if (result.era != -1) {
1014 result.era = tokenValue;
1017 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1018 LexTraceExit("0150 (EraToken seen when result.era already set)", dps);
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;
1030 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1031 LexTraceExit("0160 (JapaneseEraToken seen when result.era already set)", dps);
1035 case TokenType.TEraToken:
1037 result.calendar = TaiwanCalendar.GetDefaultInstance();
1038 dtfi = DateTimeFormatInfo.GetTaiwanCalendarDTFI();
1039 if (result.era != -1) {
1040 result.era = tokenValue;
1043 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1044 LexTraceExit("0170 (TEraToken seen when result.era already set)", dps);
1048 case TokenType.TimeZoneToken:
1050 // This is a timezone designator
1052 // NOTENOTE : for now, we only support "GMT" and "Z" (for Zulu time).
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);
1060 dtok.dtt = DTT.TimeZone;
1061 result.flags |= ParseFlags.TimeZoneUsed;
1062 result.timeZoneOffset = new TimeSpan(0);
1063 result.flags |= ParseFlags.TimeZoneUtc;
1065 case TokenType.EndOfString:
1068 case TokenType.DateWordToken:
1069 case TokenType.IgnorableSymbol:
1070 // Date words and ignorable symbols can just be skipped over
1074 if (raw.timeMark == TM.NotSet) {
1075 raw.timeMark = (TM)tokenValue;
1077 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1078 LexTraceExit("0190 (AM/PM timeMark already set)", dps);
1082 case TokenType.UnknownToken:
1083 if (Char.IsLetter(str.m_current)) {
1084 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_UnknowDateTimeWord", str.Index);
1085 LexTraceExit("0200", dps);
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)) {
1094 LexTraceExit("0210 (success)", dps);
1097 #endif // FEATURE_CORECLR
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);
1107 // Time zone parse attempt failed. Fall through to punctuation handling.
1108 str.Index = originalIndex;
1112 // Visual Basic implements string to date conversions on top of DateTime.Parse:
1113 // CDate("#10/10/95#")
1115 if (VerifyValidPunctuation(ref str)) {
1116 LexTraceExit("0230 (success)", dps);
1120 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1121 LexTraceExit("0240", dps);
1125 LexTraceExit("0250 (success)", dps);
1129 private static Boolean VerifyValidPunctuation(ref __DTString str) {
1130 // Compatability Behavior. Allow trailing nulls and surrounding hashes
1131 Char ch = str.Value[str.Index];
1133 bool foundStart = false;
1134 bool foundEnd = false;
1135 for (int i = 0; i < str.len; i++) {
1140 // Having more than two hashes is invalid
1151 else if (ch == '\0') {
1152 // Allow nulls only at the end
1157 else if ((!Char.IsWhiteSpace(ch))) {
1158 // Anthyhing other than whitespace outside hashes is invalid
1159 if (!foundStart || foundEnd) {
1165 // The has was un-paired
1168 // Valid Hash usage: eat the hash and continue.
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
1179 // Move to the end of the string
1180 str.Index = str.len;
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.
1196 // Decide the year/month/day order from the datePattern.
1198 // Return 0 for YMD, 1 for MDY, 2 for DMY, otherwise -1.
1200 private static Boolean GetYearMonthDayOrder(String datePattern, DateTimeFormatInfo dtfi, out int order)
1203 int monthOrder = -1;
1207 bool inQuote = false;
1209 for (int i = 0; i < datePattern.Length && orderCount < 3; i++)
1211 char ch = datePattern[i];
1212 if (ch == '\\' || ch == '%')
1215 continue; // Skip next character that is escaped by this backslash
1218 if (ch == '\'' || ch == '"')
1227 yearOrder = orderCount++;
1230 // Skip all year pattern charaters.
1232 for(; i+1 < datePattern.Length && datePattern[i+1] == 'y'; i++)
1239 monthOrder = orderCount++;
1241 // Skip all month pattern characters.
1243 for(; i+1 < datePattern.Length && datePattern[i+1] == 'M'; i++)
1251 int patternCount = 1;
1253 // Skip all day pattern characters.
1255 for(; i+1 < datePattern.Length && datePattern[i+1] == 'd'; i++)
1260 // Make sure this is not "ddd" or "dddd", which means day of week.
1262 if (patternCount <= 2)
1264 dayOrder = orderCount++;
1270 if (yearOrder == 0 && monthOrder == 1 && dayOrder == 2)
1275 if (monthOrder == 0 && dayOrder == 1 && yearOrder == 2)
1280 if (dayOrder == 0 && monthOrder == 1 && yearOrder == 2)
1285 if (yearOrder == 0 && dayOrder == 1 && monthOrder == 2)
1295 // Decide the year/month order from the pattern.
1297 // Return 0 for YM, 1 for MY, otherwise -1.
1299 private static Boolean GetYearMonthOrder(String pattern, DateTimeFormatInfo dtfi, out int order)
1302 int monthOrder = -1;
1305 bool inQuote = false;
1306 for (int i = 0; i < pattern.Length && orderCount < 2; i++)
1308 char ch = pattern[i];
1309 if (ch == '\\' || ch == '%')
1312 continue; // Skip next character that is escaped by this backslash
1315 if (ch == '\'' || ch == '"')
1324 yearOrder = orderCount++;
1327 // Skip all year pattern charaters.
1329 for(; i+1 < pattern.Length && pattern[i+1] == 'y'; i++)
1335 monthOrder = orderCount++;
1337 // Skip all month pattern characters.
1339 for(; i+1 < pattern.Length && pattern[i+1] == 'M'; i++)
1346 if (yearOrder == 0 && monthOrder == 1)
1351 if (monthOrder == 0 && yearOrder == 1)
1361 // Decide the month/day order from the pattern.
1363 // Return 0 for MD, 1 for DM, otherwise -1.
1365 private static Boolean GetMonthDayOrder(String pattern, DateTimeFormatInfo dtfi, out int order)
1367 int monthOrder = -1;
1371 bool inQuote = false;
1372 for (int i = 0; i < pattern.Length && orderCount < 2; i++)
1374 char ch = pattern[i];
1375 if (ch == '\\' || ch == '%')
1378 continue; // Skip next character that is escaped by this backslash
1381 if (ch == '\'' || ch == '"')
1390 int patternCount = 1;
1392 // Skip all day pattern charaters.
1394 for(; i+1 < pattern.Length && pattern[i+1] == 'd'; i++)
1400 // Make sure this is not "ddd" or "dddd", which means day of week.
1402 if (patternCount <= 2)
1404 dayOrder = orderCount++;
1410 monthOrder = orderCount++;
1412 // Skip all month pattern characters.
1414 for(; i+1 < pattern.Length && pattern[i+1] == 'M'; i++)
1421 if (monthOrder == 0 && dayOrder == 1)
1426 if (dayOrder == 0 && monthOrder == 1)
1436 // Adjust the two-digit year if necessary.
1438 private static bool TryAdjustYear(ref DateTimeResult result, int year, out int adjustedYear)
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);
1449 catch (ArgumentOutOfRangeException) {
1454 adjustedYear = year;
1458 private static bool SetDateYMD(ref DateTimeResult result, int year, int month, int day)
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))
1464 result.SetDate(year, month, day); // YMD
1470 private static bool SetDateMDY(ref DateTimeResult result, int month, int day, int year)
1472 return (SetDateYMD(ref result, year, month, day));
1475 private static bool SetDateDMY(ref DateTimeResult result, int day, int month, int year)
1477 return (SetDateYMD(ref result, year, month, day));
1480 private static bool SetDateYDM(ref DateTimeResult result, int year, int day, int month)
1482 return (SetDateYMD(ref result, year, month, day));
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;
1490 // Processing teriminal case: DS.DX_NN
1491 private static Boolean GetDayOfNN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
1493 if ((result.flags & ParseFlags.HaveDate) != 0) {
1494 // Multiple dates in the input string
1495 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1499 int n1 = raw.GetNumber(0);
1500 int n2 = raw.GetNumber(1);
1502 GetDefaultYear(ref result, ref styles);
1505 if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out order)) {
1506 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern);
1510 if (order == ORDER_MD)
1512 if (SetDateYMD(ref result, result.Year, n1, n2)) // MD
1514 result.flags |= ParseFlags.HaveDate;
1519 if (SetDateYMD(ref result, result.Year, n2, n1)) // DM
1521 result.flags |= ParseFlags.HaveDate;
1525 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1529 // Processing teriminal case: DS.DX_NNN
1530 private static Boolean GetDayOfNNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1532 if ((result.flags & ParseFlags.HaveDate) != 0) {
1533 // Multiple dates in the input string
1534 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1538 int n1 = raw.GetNumber(0);
1539 int n2 = raw.GetNumber(1);;
1540 int n3 = raw.GetNumber(2);
1543 if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) {
1544 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern);
1549 if (order == ORDER_YMD) {
1550 if (TryAdjustYear(ref result, n1, out year) && SetDateYMD(ref result, year, n2, n3)) // YMD
1552 result.flags |= ParseFlags.HaveDate;
1555 } else if (order == ORDER_MDY) {
1556 if (TryAdjustYear(ref result, n3, out year) && SetDateMDY(ref result, n1, n2, year)) // MDY
1558 result.flags |= ParseFlags.HaveDate;
1561 } else if (order == ORDER_DMY) {
1562 if (TryAdjustYear(ref result, n3, out year) && SetDateDMY(ref result, n1, n2, year)) // DMY
1564 result.flags |= ParseFlags.HaveDate;
1567 } else if (order == ORDER_YDM) {
1568 if (TryAdjustYear(ref result, n1, out year) && SetDateYDM(ref result, year, n2, n3)) // YDM
1570 result.flags |= ParseFlags.HaveDate;
1574 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1578 private static Boolean GetDayOfMN(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
1580 if ((result.flags & ParseFlags.HaveDate) != 0) {
1581 // Multiple dates in the input string
1582 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1586 // The interpretation is based on the MonthDayPattern and YearMonthPattern
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
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.
1599 if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder)) {
1600 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern);
1603 if (monthDayOrder == ORDER_DM) {
1605 if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder)) {
1606 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.YearMonthPattern);
1609 if (yearMonthOrder == ORDER_MY) {
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);
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);
1628 ////////////////////////////////////////////////////////////////////////
1630 // Deal with the terminal state for Hebrew Month/Day pattern
1632 ////////////////////////////////////////////////////////////////////////
1634 private static Boolean GetHebrewDayOfNM(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1637 if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder)) {
1638 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern);
1641 result.Month = raw.month;
1642 if (monthDayOrder == ORDER_DM || monthDayOrder == ORDER_MD)
1644 if (result.calendar.IsValidDay(result.Year, result.Month, raw.GetNumber(0), result.era))
1646 result.Day = raw.GetNumber(0);
1650 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1654 private static Boolean GetDayOfNM(ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1656 if ((result.flags & ParseFlags.HaveDate) != 0) {
1657 // Multiple dates in the input string
1658 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1662 // The interpretation is based on the MonthDayPattern and YearMonthPattern
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
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.
1675 if (!GetMonthDayOrder(dtfi.MonthDayPattern, dtfi, out monthDayOrder)) {
1676 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.MonthDayPattern);
1679 if (monthDayOrder == ORDER_MD) {
1681 if (!GetYearMonthOrder(dtfi.YearMonthPattern, dtfi, out yearMonthOrder)) {
1682 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.YearMonthPattern);
1685 if (yearMonthOrder == ORDER_YM) {
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);
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);
1703 private static Boolean GetDayOfMNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1705 if ((result.flags & ParseFlags.HaveDate) != 0) {
1706 // Multiple dates in the input string
1707 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1711 int n1 = raw.GetNumber(0);
1712 int n2 = raw.GetNumber(1);
1715 if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) {
1716 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern);
1721 if (order == ORDER_MDY)
1723 if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
1725 result.SetDate(year, raw.month, n1); // MDY
1726 result.flags |= ParseFlags.HaveDate;
1729 else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
1731 result.SetDate(year, raw.month, n2); // YMD
1732 result.flags |= ParseFlags.HaveDate;
1736 else if (order == ORDER_YMD)
1738 if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
1740 result.SetDate(year, raw.month, n2); // YMD
1741 result.flags |= ParseFlags.HaveDate;
1744 else if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
1746 result.SetDate(year, raw.month, n1); // DMY
1747 result.flags |= ParseFlags.HaveDate;
1751 else if (order == ORDER_DMY)
1753 if (TryAdjustYear(ref result, n2, out year) && result.calendar.IsValidDay(year, raw.month, n1, result.era))
1755 result.SetDate(year, raw.month, n1); // DMY
1756 result.flags |= ParseFlags.HaveDate;
1759 else if (TryAdjustYear(ref result, n1, out year) && result.calendar.IsValidDay(year, raw.month, n2, result.era))
1761 result.SetDate(year, raw.month, n2); // YMD
1762 result.flags |= ParseFlags.HaveDate;
1767 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1771 private static Boolean GetDayOfYNN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
1773 if ((result.flags & ParseFlags.HaveDate) != 0) {
1774 // Multiple dates in the input string
1775 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1779 int n1 = raw.GetNumber(0);
1780 int n2 = raw.GetNumber(1);
1781 String pattern = dtfi.ShortDatePattern;
1783 // For compatability, don't throw if we can't determine the order, but default to YMD instead
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
1792 if (SetDateYMD(ref result, raw.year, n1, n2)) {
1793 result.flags |= ParseFlags.HaveDate;
1794 return true; // Year + MD
1797 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1801 private static Boolean GetDayOfNNY(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
1803 if ((result.flags & ParseFlags.HaveDate) != 0) {
1804 // Multiple dates in the input string
1805 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1809 int n1 = raw.GetNumber(0);
1810 int n2 = raw.GetNumber(1);
1813 if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) {
1814 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern);
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
1824 if (SetDateYMD(ref result, raw.year, n2, n1)) {
1825 result.flags |= ParseFlags.HaveDate;
1826 return true; // DM + Year
1829 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1834 private static Boolean GetDayOfYMN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi) {
1836 if ((result.flags & ParseFlags.HaveDate) != 0) {
1837 // Multiple dates in the input string
1838 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1842 if (SetDateYMD(ref result, raw.year, raw.month, raw.GetNumber(0))) {
1843 result.flags |= ParseFlags.HaveDate;
1846 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1850 private static Boolean GetDayOfYN(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1852 if ((result.flags & ParseFlags.HaveDate) != 0) {
1853 // Multiple dates in the input string
1854 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1858 if (SetDateYMD(ref result, raw.year, raw.GetNumber(0), 1))
1860 result.flags |= ParseFlags.HaveDate;
1863 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1867 private static Boolean GetDayOfYM(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
1869 if ((result.flags & ParseFlags.HaveDate) != 0) {
1870 // Multiple dates in the input string
1871 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1875 if (SetDateYMD(ref result, raw.year, raw.month, 1))
1877 result.flags |= ParseFlags.HaveDate;
1880 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
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)
1889 // In this case, if we are parsing a string like "2005/09/14 12:23", we will assume this is in AM.
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;
1896 if (dtfi.PMDesignator.Length == 0 && dtfi.AMDesignator.Length != 0) {
1897 raw.timeMark = TM.PM;
1904 // Adjust hour according to the time mark.
1906 private static Boolean AdjustHour(ref int hour, TM timeMark) {
1907 if (timeMark != TM.NotSet) {
1909 if (timeMark == TM.AM) {
1910 if (hour < 0 || hour > 12) {
1913 hour = (hour == 12) ? 0 : hour;
1916 if (hour < 0 || hour > 23) {
1927 private static Boolean GetTimeOfN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw)
1929 if ((result.flags & ParseFlags.HaveTime) != 0) {
1930 // Multiple times in the input string
1931 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1935 // In this case, we need a time mark. Check if so.
1937 if (raw.timeMark == TM.NotSet)
1939 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1942 result.Hour = raw.GetNumber(0);
1943 result.flags |= ParseFlags.HaveTime;
1947 private static Boolean GetTimeOfNN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw)
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);
1956 result.Hour = raw.GetNumber(0);
1957 result.Minute = raw.GetNumber(1);
1958 result.flags |= ParseFlags.HaveTime;
1962 private static Boolean GetTimeOfNNN(DateTimeFormatInfo dtfi, ref DateTimeResult result, ref DateTimeRawInfo raw)
1964 if ((result.flags & ParseFlags.HaveTime) != 0) {
1965 // Multiple times in the input string
1966 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
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;
1978 // Processing terminal state: A Date suffix followed by one number.
1980 private static Boolean GetDateOfDSN(ref DateTimeResult result, ref DateTimeRawInfo raw)
1982 if (raw.numCount != 1 || result.Day != -1)
1984 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1987 result.Day = raw.GetNumber(0);
1991 private static Boolean GetDateOfNDS(ref DateTimeResult result, ref DateTimeRawInfo raw)
1993 if (result.Month == -1)
1995 //Should have a month suffix
1996 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
1999 if (result.Year != -1)
2001 // Aleady has a year suffix
2002 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2005 if (!TryAdjustYear(ref result, raw.GetNumber(0), out result.Year))
2007 // the year value is out of range
2008 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2015 private static Boolean GetDateOfNNDS(ref DateTimeResult result, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
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.
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))) {
2029 else if ((result.flags & ParseFlags.HaveMonth) != 0) {
2030 if (((result.flags & ParseFlags.HaveYear) == 0) && ((result.flags & ParseFlags.HaveDay) == 0)) {
2032 if (!GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi, out order)) {
2033 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_BadDatePattern", dtfi.ShortDatePattern);
2037 if (order == ORDER_YMD) {
2038 if (TryAdjustYear(ref result, raw.GetNumber(0), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(1))) {
2043 if (TryAdjustYear(ref result, raw.GetNumber(1), out year) && SetDateYMD(ref result, year, result.Month, raw.GetNumber(0))){
2049 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2054 // A date suffix is found, use this method to put the number into the result.
2056 private static bool ProcessDateTimeSuffix(ref DateTimeResult result, ref DateTimeRawInfo raw, ref DateTimeToken dtok)
2058 switch (dtok.suffix)
2060 case TokenType.SEP_YearSuff:
2061 if ((result.flags & ParseFlags.HaveYear) != 0) {
2064 result.flags |= ParseFlags.HaveYear;
2065 result.Year = raw.year = dtok.num;
2067 case TokenType.SEP_MonthSuff:
2068 if ((result.flags & ParseFlags.HaveMonth) != 0) {
2071 result.flags |= ParseFlags.HaveMonth;
2072 result.Month= raw.month = dtok.num;
2074 case TokenType.SEP_DaySuff:
2075 if ((result.flags & ParseFlags.HaveDay) != 0) {
2078 result.flags |= ParseFlags.HaveDay;
2079 result.Day = dtok.num;
2081 case TokenType.SEP_HourSuff:
2082 if ((result.flags & ParseFlags.HaveHour) != 0) {
2085 result.flags |= ParseFlags.HaveHour;
2086 result.Hour = dtok.num;
2088 case TokenType.SEP_MinuteSuff:
2089 if ((result.flags & ParseFlags.HaveMinute) != 0) {
2092 result.flags |= ParseFlags.HaveMinute;
2093 result.Minute = dtok.num;
2095 case TokenType.SEP_SecondSuff:
2096 if ((result.flags & ParseFlags.HaveSecond) != 0) {
2099 result.flags |= ParseFlags.HaveSecond;
2100 result.Second = dtok.num;
2107 ////////////////////////////////////////////////////////////////////////
2110 // This is used by DateTime.Parse().
2111 // Process the terminal state for the Hebrew calendar parsing.
2113 ////////////////////////////////////////////////////////////////////////
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.
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);
2125 if (!GetDayOfMNN(ref result, ref raw, dtfi)) {
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);
2135 if (!GetDayOfYMN(ref result, ref raw, dtfi)) {
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);
2147 if (!GetHebrewDayOfNM(ref result, ref raw, dtfi)) {
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);
2157 if (!GetDayOfYM(ref result, ref raw, dtfi)) {
2162 // Deal hour + AM/PM
2163 if (!GetTimeOfN(dtfi, ref result, ref raw)) {
2168 if (!GetTimeOfNN(dtfi, ref result, ref raw)) {
2173 if (!GetTimeOfNNN(dtfi, ref result, ref raw)) {
2178 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2185 // We have reached a terminal state. Reset the raw num count.
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.
2196 internal static Boolean ProcessTerminaltState(DS dps, ref DateTimeResult result, ref DateTimeStyles styles, ref DateTimeRawInfo raw, DateTimeFormatInfo dtfi)
2203 passed = GetDayOfNN(ref result, ref styles, ref raw, dtfi);
2206 passed = GetDayOfNNN(ref result, ref raw, dtfi);
2209 passed = GetDayOfMN(ref result, ref styles, ref raw, dtfi);
2212 passed = GetDayOfNM(ref result, ref styles, ref raw, dtfi);
2215 passed = GetDayOfMNN(ref result, ref raw, dtfi);
2218 // The result has got the correct value. No need to process.
2222 passed = GetDayOfYNN(ref result, ref raw, dtfi);
2225 passed = GetDayOfNNY(ref result, ref raw, dtfi);
2228 passed = GetDayOfYMN(ref result, ref raw, dtfi);
2231 passed = GetDayOfYN(ref result, ref raw, dtfi);
2234 passed = GetDayOfYM(ref result, ref raw, dtfi);
2237 passed = GetTimeOfN(dtfi, ref result, ref raw);
2240 passed = GetTimeOfNN(dtfi, ref result, ref raw);
2243 passed = GetTimeOfNNN(dtfi, ref result, ref raw);
2246 // The result has got the correct value. Nothing to do.
2250 passed = GetDateOfDSN(ref result, ref raw);
2253 passed = GetDateOfNDS(ref result, ref raw);
2256 passed = GetDateOfNNDS(ref result, ref raw, dtfi);
2260 PTSTraceExit(dps, passed);
2268 // We have reached a terminal state. Reset the raw num count.
2275 internal static DateTime Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles) {
2276 DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result.
2278 if (TryParse(s, dtfi, styles, ref result)) {
2279 return result.parsedDate;
2282 throw GetDateTimeParseException(ref result);
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.
2289 result.flags |= ParseFlags.CaptureOffset;
2290 if (TryParse(s, dtfi, styles, ref result)) {
2291 offset = result.timeZoneOffset;
2292 return result.parsedDate;
2295 throw GetDateTimeParseException(ref 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.
2304 if (TryParse(s, dtfi, styles, ref resultData)) {
2305 result = resultData.parsedDate;
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.
2316 parseResult.flags |= ParseFlags.CaptureOffset;
2317 if (TryParse(s, dtfi, styles, ref parseResult)) {
2318 result = parseResult.parsedDate;
2319 offset = parseResult.timeZoneOffset;
2327 // This is the real method to do the parsing work.
2329 [System.Security.SecuritySafeCritical] // auto-generated
2330 internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, ref DateTimeResult result) {
2332 result.SetFailure(ParseFailureKind.ArgumentNull, "ArgumentNull_String", null, "s");
2335 if (s.Length == 0) {
2336 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2340 Contract.Assert(dtfi != null, "dtfi == null");
2348 // First try the predefined format.
2356 DS dps = DS.BEGIN; // Date Parsing State.
2357 bool reachTerminalState = false;
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.
2363 Int32 * numberPointer = stackalloc Int32[3];
2364 raw.Init(numberPointer);
2366 raw.hasSameDateAndTimeSeparators = dtfi.DateSeparator.Equals(dtfi.TimeSeparator, StringComparison.Ordinal);
2368 result.calendar = dtfi.Calendar;
2369 result.era = Calendar.CurrentEra;
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.
2375 __DTString str = new __DTString(s, dtfi);
2380 // The following loop will break out when we reach the end of the str.
2384 // Call the lexer to get the next token.
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))
2389 TPTraceExit("0000", dps);
2394 // If the token is not unknown, process it.
2395 // Otherwise, just discard it.
2397 if (dtok.dtt != DTT.Unk)
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.
2404 if (dtok.suffix != TokenType.SEP_Unk)
2406 if (!ProcessDateTimeSuffix(ref result, ref raw, ref dtok)) {
2407 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2408 TPTraceExit("0010", dps);
2412 dtok.suffix = TokenType.SEP_Unk; // Reset suffix to SEP_Unk;
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));
2423 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2424 TPTraceExit("0030", dps);
2429 if (raw.hasSameDateAndTimeSeparators)
2431 if (dtok.dtt == DTT.YearEnd || dtok.dtt == DTT.YearSpace || dtok.dtt == DTT.YearDateSep)
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
2439 if (dps == DS.T_NNt)
2445 bool atEnd = str.AtEnd();
2446 if (dateParsingStates[(int)dps][(int)dtok.dtt] == DS.ERROR || atEnd)
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.
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;
2462 // Advance to the next state, and continue
2464 dps = dateParsingStates[(int)dps][(int)dtok.dtt];
2466 if (dps == DS.ERROR)
2468 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2469 TPTraceExit("0040 (invalid state transition)", dps);
2472 else if (dps > DS.ERROR)
2474 if ((dtfi.FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) {
2475 if (!ProcessHebrewTerminalState(dps, ref result, ref styles, ref raw, dtfi)) {
2476 TPTraceExit("0050 (ProcessHebrewTerminalState)", dps);
2480 if (!ProcessTerminaltState(dps, ref result, ref styles, ref raw, dtfi)) {
2481 TPTraceExit("0060 (ProcessTerminaltState)", dps);
2485 reachTerminalState = true;
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".
2495 } while (dtok.dtt != DTT.End && dtok.dtt != DTT.NumEnd && dtok.dtt != DTT.MonthEnd);
2497 if (!reachTerminalState) {
2498 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2499 TPTraceExit("0070 (did not reach terminal state)", dps);
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);
2510 // Check if the parased string only contains hour/minute/second values.
2511 bool bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);
2514 // Check if any year/month/day is missing in the parsing string.
2515 // If yes, get the default value from today's date.
2517 if (!CheckDefaultDateTime(ref result, ref result.calendar, styles)) {
2518 TPTraceExit("0090 (failed to fill in missing year/month/day defaults)", dps);
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);
2528 if (raw.fraction > 0) {
2529 time = time.AddTicks((long)Math.Round(raw.fraction * Calendar.TicksPerSecond));
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.
2536 if (raw.dayOfWeek != -1) {
2538 // Check if day of week is correct.
2540 if (raw.dayOfWeek != (int)result.calendar.GetDayOfWeek(time)) {
2541 result.SetFailure(ParseFailureKind.Format, "Format_BadDayOfWeek", null);
2542 TPTraceExit("0110 (dayOfWeek check)", dps);
2547 result.parsedDate = time;
2549 if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly)) {
2550 TPTraceExit("0120 (DetermineTimeZoneAdjustments)", dps);
2553 TPTraceExit("0130 (success)", dps);
2558 // Handles time zone adjustments and sets DateTimeKind values as required by the styles
2559 private static Boolean DetermineTimeZoneAdjustments(ref DateTimeResult result, DateTimeStyles styles, Boolean bTimeOnly) {
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);
2566 #if FEATURE_CORECLR // on CoreCLR DateTime is also restricted to +- 14:00, just like DateTimeOffset
2568 Int64 offsetTicks = result.timeZoneOffset.Ticks;
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);
2576 #endif // FEATURE_CORECLR
2578 // The flags AssumeUniveral and AssumeLocal only apply when the input does not have a time zone
2579 if ((result.flags & ParseFlags.TimeZoneUsed) == 0) {
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);
2594 result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Local);
2598 else if ((styles & DateTimeStyles.AssumeUniversal) != 0) {
2599 if ((styles & DateTimeStyles.AdjustToUniversal) != 0) {
2600 result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc);
2604 result.flags |= ParseFlags.TimeZoneUsed;
2605 result.timeZoneOffset = TimeSpan.Zero;
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");
2615 if (((styles & DateTimeStyles.RoundtripKind) != 0) && ((result.flags & ParseFlags.TimeZoneUtc) != 0)) {
2616 result.parsedDate = DateTime.SpecifyKind(result.parsedDate, DateTimeKind.Utc);
2620 if ((styles & DateTimeStyles.AdjustToUniversal) != 0) {
2621 return (AdjustTimeZoneToUniversal(ref result));
2623 return (AdjustTimeZoneToLocal(ref result, bTimeOnly));
2626 // Apply validation and adjustments specific to DateTimeOffset
2627 private static Boolean DateTimeOffsetTimeZonePostProcessing(ref DateTimeResult result, DateTimeStyles styles) {
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;
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);
2642 Int64 offsetTicks = result.timeZoneOffset.Ticks;
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;
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);
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);
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;
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;
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"
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;
2694 if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks) {
2695 result.SetFailure(ParseFailureKind.Format, "Format_DateOutOfRange", null);
2698 result.parsedDate = new DateTime(resultTicks, DateTimeKind.Utc);
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"
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) {
2716 // This is time of day.
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;
2724 if (resultTicks < 0) {
2725 resultTicks += Calendar.TicksPerDay;
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;
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;
2741 if (resultTicks < DateTime.MinTicks || resultTicks > DateTime.MaxTicks) {
2742 result.parsedDate = DateTime.MinValue;
2743 result.SetFailure(ParseFailureKind.Format, "Format_DateOutOfRange", null);
2746 result.parsedDate = new DateTime(resultTicks, DateTimeKind.Local, isAmbiguousLocalDst);
2751 // Parse the ISO8601 format string found during Parse();
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) {
2760 double partSecond = 0;
2762 str.SkipWhiteSpaces();
2763 if (!ParseDigits(ref str, 2, out hour)) {
2764 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2767 str.SkipWhiteSpaces();
2768 if (!str.Match(':')) {
2769 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2772 str.SkipWhiteSpaces();
2773 if (!ParseDigits(ref str, 2, out minute)) {
2774 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
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);
2784 if (str.Match('.')) {
2785 if (!ParseFraction(ref str, out partSecond)) {
2786 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2791 str.SkipWhiteSpaces();
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);
2801 } else if (ch == 'Z' || ch == 'z') {
2802 result.flags |= ParseFlags.TimeZoneUsed;
2803 result.timeZoneOffset = TimeSpan.Zero;
2804 result.flags |= ParseFlags.TimeZoneUtc;
2808 str.SkipWhiteSpaces();
2809 if (str.Match('#')) {
2810 if (!VerifyValidPunctuation(ref str)) {
2811 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
2814 str.SkipWhiteSpaces();
2816 if (str.Match('\0')) {
2817 if (!VerifyValidPunctuation(ref str)) {
2818 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
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);
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);
2837 time = time.AddTicks((long)Math.Round(partSecond * Calendar.TicksPerSecond));
2838 result.parsedDate = time;
2839 if (!DetermineTimeZoneAdjustments(ref result, styles, false)) {
2846 ////////////////////////////////////////////////////////////////////////
2849 // Parse the current word as a Hebrew number.
2850 // This is used by DateTime.ParseExact().
2852 ////////////////////////////////////////////////////////////////////////
2854 internal static bool MatchHebrewDigits(ref __DTString str, int digitLen, out int number) {
2857 // Create a context object so that we can parse the Hebrew number text character by character.
2858 HebrewNumberParsingContext context = new HebrewNumberParsingContext(0);
2860 // Set this to ContinueParsing so that we will run the following while loop in the first time.
2861 HebrewNumberParsingState state = HebrewNumberParsingState.ContinueParsing;
2863 while (state == HebrewNumberParsingState.ContinueParsing && str.GetNext()) {
2864 state = HebrewNumber.ParseByChar(str.GetChar(), ref context);
2867 if (state == HebrewNumberParsingState.FoundEndOfHebrewNumber) {
2868 // If we have reached a terminal state, update the result and returns.
2869 number = context.result;
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.
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 ==============================================================================*/
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);
2895 return ParseDigits(ref str, digitLen, digitLen, out 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");
2904 int startingIndex = str.Index;
2905 int tokenLength = 0;
2906 while (tokenLength < maxDigitLen) {
2907 if (!str.GetNextDigit()) {
2911 result = result * 10 + str.GetDigit();
2914 if (tokenLength < minDigitLen) {
2915 str.Index = startingIndex;
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 ==============================================================================*/
2931 private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, ref double result) {
2932 if (!str.GetNextDigit()) {
2936 result = str.GetDigit();
2939 for (; digitLen < maxDigitLen; digitLen++) {
2940 if (!str.GetNextDigit()) {
2944 result = result * 10 + str.GetDigit();
2947 result = ((double)result / Math.Pow(10, digitLen));
2948 return (digitLen == maxDigitLen);
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 ==============================================================================*/
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.
2965 char ch = str.GetChar();
2969 } else if (ch == '-') {
2973 // A sign symbol ('+' or '-') is expected.
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 ==============================================================================*/
2986 private static bool ParseTimeZoneOffset(ref __DTString str, int len, ref TimeSpan result) {
2987 bool isPositive = true;
2989 int minuteOffset = 0;
2994 if (!ParseSign(ref str, ref isPositive)) {
2997 if (!ParseDigits(ref str, len, out hourOffset)) {
3002 if (!ParseSign(ref str, ref isPositive)) {
3006 // Parsing 1 digit will actually parse 1 or 2.
3007 if (!ParseDigits(ref str, 1, out hourOffset)) {
3011 if (str.Match(":")) {
3013 if (!ParseDigits(ref str, 2, out minuteOffset)) {
3017 // Since we can not match ':', put the char back.
3019 if (!ParseDigits(ref str, 2, out minuteOffset)) {
3025 if (minuteOffset < 0 || minuteOffset >= 60) {
3029 result = (new TimeSpan(hourOffset, minuteOffset, 0));
3031 result = result.Negate();
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 ==============================================================================*/
3044 private static bool MatchAbbreviatedMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) {
3045 int maxMatchStrLen = 0;
3047 if (str.GetNext()) {
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.
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;
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;
3082 str.Index += (maxMatchStrLen - 1);
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 ==============================================================================*/
3096 private static bool MatchMonthName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) {
3097 int maxMatchStrLen = 0;
3099 if (str.GetNext()) {
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.
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;
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;
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;
3146 str.Index += (maxMatchStrLen - 1);
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 ==============================================================================*/
3160 private static bool MatchAbbreviatedDayName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) {
3161 int maxMatchStrLen = 0;
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;
3178 str.Index += maxMatchStrLen - 1;
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 ==============================================================================*/
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;
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;
3211 str.Index += maxMatchStrLen - 1;
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 ==============================================================================*/
3225 private static bool MatchEraName(ref __DTString str, DateTimeFormatInfo dtfi, ref int result) {
3226 if (str.GetNext()) {
3227 int[] eras = dtfi.Calendar.Eras;
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);
3237 searchStr = dtfi.GetAbbreviatedEraName(eras[i]);
3238 if (str.MatchSpecifiedWord(searchStr)) {
3239 str.Index += (searchStr.Length - 1);
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 ==============================================================================*/
3257 private static bool MatchTimeMark(ref __DTString str, DateTimeFormatInfo dtfi, ref TM result) {
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) {
3263 if (dtfi.PMDesignator.Length == 0) {
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);
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);
3286 str.Index--; // Undo the GetNext call.
3288 if (result != TM.NotSet) {
3289 // If one of the AM/PM marks is empty string, return the result.
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 ==============================================================================*/
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.
3309 if (str.GetChar() == dtfi.AMDesignator[0]) {
3313 if (str.GetChar() == dtfi.PMDesignator[0]) {
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.
3329 ==============================================================================*/
3331 private static bool CheckNewValue(ref int currentValue, int newValue, char patternChar, ref DateTimeResult result) {
3332 if (currentValue == -1) {
3333 currentValue = newValue;
3336 if (newValue != currentValue) {
3337 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", patternChar);
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);
3350 else if ((styles & DateTimeStyles.AssumeUniversal) != 0) {
3351 // assume the offset is Utc
3352 return DateTime.UtcNow;
3356 // assume the offset is Local
3357 return DateTime.Now;
3360 private static bool CheckDefaultDateTime(ref DateTimeResult result, ref Calendar cal, DateTimeStyles styles) {
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
3369 // values like "11:00Z" or "11:00 -3:00" are also acceptable
3371 // if ((month or day is set) and (year is not set and time zone is set))
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);
3381 if ((result.Year == -1) || (result.Month == -1) || (result.Day == -1)) {
3383 The following table describes the behaviors of getting the default value
3384 when a certain year/month/day values are missing.
3386 An "X" means that the value exists. And "--" means that value is missing.
3388 Year Month Day => ResultYear ResultMonth ResultDay Note
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.
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.
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;
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);
3418 // Month/Day are both missing.
3423 if (result.Year == -1) {
3424 result.Year = cal.GetYear(now);
3426 if (result.Month == -1) {
3429 if (result.Day == -1) {
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;
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
3447 private static String ExpandPredefinedFormat(String format, ref DateTimeFormatInfo dtfi, ref ParsingInfo parseInfo, ref DateTimeResult result) {
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.
3452 switch (format[0]) {
3454 case 'O': // Round Trip Format
3455 parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
3456 dtfi = DateTimeFormatInfo.InvariantInfo;
3459 case 'R': // RFC 1123 Standard. (in Universal time)
3460 parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
3461 dtfi = DateTimeFormatInfo.InvariantInfo;
3463 if ((result.flags & ParseFlags.CaptureOffset) != 0) {
3464 result.flags |= ParseFlags.Rfc1123Pattern;
3467 case 's': // Sortable format (in local time)
3468 dtfi = DateTimeFormatInfo.InvariantInfo;
3469 parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
3471 case 'u': // Universal time format in sortable format.
3472 parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
3473 dtfi = DateTimeFormatInfo.InvariantInfo;
3475 if ((result.flags & ParseFlags.CaptureOffset) != 0) {
3476 result.flags |= ParseFlags.UtcSortPattern;
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();
3492 // Expand the pre-defined format character to the real format from DateTimeFormatInfo.
3494 return (DateTimeFormat.GetRealFormat(format, dtfi));
3501 // Given a specified format character, parse and update the parsing result.
3503 private static bool ParseByFormat(
3505 ref __DTString format,
3506 ref ParsingInfo parseInfo,
3507 DateTimeFormatInfo dtfi,
3508 ref DateTimeResult result) {
3511 int tempYear = 0, tempMonth = 0, tempDay = 0, tempDayOfWeek = 0, tempHour = 0, tempMinute = 0, tempSecond = 0;
3512 double tempFraction = 0;
3513 TM tempTimeMark = 0;
3515 char ch = format.GetChar();
3519 tokenLen = format.GetRepeatCount();
3521 if (dtfi.HasForceTwoDigitYears) {
3522 parseResult = ParseDigits(ref str, 1, 4, out tempYear);
3525 if (tokenLen <= 2) {
3526 parseInfo.fUseTwoDigitYear = true;
3528 parseResult = ParseDigits(ref str, tokenLen, out tempYear);
3530 if (!parseResult && parseInfo.fCustomNumberParser) {
3531 parseResult = parseInfo.parseNumberDelegate(ref str, tokenLen, out tempYear);
3534 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3537 if (!CheckNewValue(ref result.Year, tempYear, ch, ref result)) {
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);
3552 if (tokenLen == 3) {
3553 if (!MatchAbbreviatedMonthName(ref str, dtfi, ref tempMonth)) {
3554 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3558 if (!MatchMonthName(ref str, dtfi, ref tempMonth)) {
3559 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3563 result.flags |= ParseFlags.ParsedMonthName;
3565 if (!CheckNewValue(ref result.Month, tempMonth, ch, ref result)) {
3570 // Day & Day of week
3571 tokenLen = format.GetRepeatCount();
3572 if (tokenLen <= 2) {
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);
3582 if (!CheckNewValue(ref result.Day, tempDay, ch, ref result)) {
3586 if (tokenLen == 3) {
3588 if (!MatchAbbreviatedDayName(ref str, dtfi, ref tempDayOfWeek)) {
3589 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3594 if (!MatchDayName(ref str, dtfi, ref tempDayOfWeek)) {
3595 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3599 if (!CheckNewValue(ref parseInfo.dayOfWeek, tempDayOfWeek, ch, ref result)) {
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);
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);
3619 if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result)) {
3624 tokenLen = format.GetRepeatCount();
3625 if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempHour)) {
3626 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3629 if (!CheckNewValue(ref result.Hour, tempHour, ch, ref result)) {
3634 tokenLen = format.GetRepeatCount();
3635 if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempMinute)) {
3636 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3639 if (!CheckNewValue(ref result.Minute, tempMinute, ch, ref result)) {
3644 tokenLen = format.GetRepeatCount();
3645 if (!ParseDigits(ref str, (tokenLen < 2? 1 : 2), out tempSecond)) {
3646 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3649 if (!CheckNewValue(ref result.Second, tempSecond, ch, ref result)) {
3655 tokenLen = format.GetRepeatCount();
3656 if (tokenLen <= DateTimeFormat.MaxSecondsFractionDigits) {
3657 if (!ParseFractionExact(ref str, tokenLen, ref tempFraction)) {
3659 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3663 if (result.fraction < 0) {
3664 result.fraction = tempFraction;
3666 if (tempFraction != result.fraction) {
3667 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", ch);
3672 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3678 tokenLen = format.GetRepeatCount();
3679 if (tokenLen == 1) {
3680 if (!MatchAbbreviatedTimeMark(ref str, dtfi, ref tempTimeMark)) {
3681 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3685 if (!MatchTimeMark(ref str, dtfi, ref tempTimeMark)) {
3686 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3691 if (parseInfo.timeMark == TM.NotSet) {
3692 parseInfo.timeMark = tempTimeMark;
3695 if (parseInfo.timeMark != tempTimeMark) {
3696 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", ch);
3703 tokenLen = format.GetRepeatCount();
3705 TimeSpan tempTimeZoneOffset = new TimeSpan(0);
3706 if (!ParseTimeZoneOffset(ref str, tokenLen, ref tempTimeZoneOffset)) {
3707 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3710 if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset) {
3711 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'z');
3714 result.timeZoneOffset = tempTimeZoneOffset;
3715 result.flags |= ParseFlags.TimeZoneUsed;
3719 if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && result.timeZoneOffset != TimeSpan.Zero) {
3720 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'Z');
3724 result.flags |= ParseFlags.TimeZoneUsed;
3725 result.timeZoneOffset = new TimeSpan(0);
3726 result.flags |= ParseFlags.TimeZoneUtc;
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.
3733 if (!GetTimeZoneName(ref str)) {
3734 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
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');
3747 result.flags |= ParseFlags.TimeZoneUsed;
3748 result.timeZoneOffset = new TimeSpan(0);
3749 result.flags |= ParseFlags.TimeZoneUtc;
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);
3758 if ((result.flags & ParseFlags.TimeZoneUsed) != 0 && tempTimeZoneOffset != result.timeZoneOffset) {
3759 result.SetFailure(ParseFailureKind.FormatWithParameter, "Format_RepeatDateTimePattern", 'K');
3762 result.timeZoneOffset = tempTimeZoneOffset;
3763 result.flags |= ParseFlags.TimeZoneUsed;
3765 // Otherwise it is unspecified and we consume no characters
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);
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))
3783 // A date separator is expected.
3784 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
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);
3796 format.Index += tokenLen - 1;
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();
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);
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
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;
3822 else if ((result.flags & ParseFlags.UtcSortPattern) != 0 && quotedStr == ZuluName) {
3823 result.flags |= ParseFlags.TimeZoneUsed;
3824 result.timeZoneOffset = TimeSpan.Zero;
3830 // Skip this so we can get to the next pattern character.
3831 // Used in case like "%d", "%y"
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);
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);
3850 result.SetFailure(ParseFailureKind.Format, "Format_BadFormatSpecifier", null);
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();
3864 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3870 if (parseInfo.fAllowInnerWhite) {
3871 // Skip whitespaces if AllowInnerWhite.
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)) {
3891 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
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);
3907 } else if (!str.Match(ch)) {
3909 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
3919 // The pos should point to a quote character. This method will
3920 // get the string enclosed by the quote character.
3922 internal static bool TryParseQuoteString(String format, int pos, StringBuilder result, out int returnValue) {
3924 // NOTE : pos will be the index of the quote character in the 'format' string.
3927 int formatLen = format.Length;
3929 char quoteChar = format[pos++]; // Get the character used to quote the following string.
3931 bool foundQuote = false;
3932 while (pos < formatLen) {
3933 char ch = format[pos++];
3934 if (ch == quoteChar) {
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:
3943 // because the second double quote is escaped.
3944 if (pos < formatLen) {
3945 result.Append(format[pos++]);
3948 // This means that '\' is at the end of the formatting string.
3958 // Here we can't find the matching quote.
3963 // Return the character count including the begin/end quote characters and enclosed string.
3965 returnValue = (pos - beginPos);
3972 /*=================================DoStrictParse==================================
3973 **Action: Do DateTime parsing using the format in formatParam.
3974 **Returns: The parsed DateTime.
3979 ** When the following general formats are used, InvariantInfo is used in dtfi:
3981 ** When the following general formats are used, the time is assumed to be in Universal time.
3984 ** Only GregarianCalendar is supported for now.
3985 ** Only support GMT timezone.
3986 ==============================================================================*/
3988 private static bool DoStrictParse(
3991 DateTimeStyles styles,
3992 DateTimeFormatInfo dtfi,
3993 ref DateTimeResult result) {
3997 ParsingInfo parseInfo = new ParsingInfo();
4000 parseInfo.calendar = dtfi.Calendar;
4001 parseInfo.fAllowInnerWhite = ((styles & DateTimeStyles.AllowInnerWhite) != 0);
4002 parseInfo.fAllowTrailingWhite = ((styles & DateTimeStyles.AllowTrailingWhite) != 0);
4005 // We need the original values of the following two below.
4006 String originalFormat = formatParam;
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);
4015 formatParam = ExpandPredefinedFormat(formatParam, ref dtfi, ref parseInfo, ref result);
4018 bool bTimeOnly = false;
4019 result.calendar = parseInfo.calendar;
4021 if (parseInfo.calendar.ID == Calendar.CAL_HEBREW) {
4022 parseInfo.parseNumberDelegate = m_hebrewNumberParser;
4023 parseInfo.fCustomNumberParser = true;
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;
4030 __DTString format = new __DTString(formatParam, dtfi, false);
4031 __DTString str = new __DTString(s, dtfi, false);
4033 if (parseInfo.fAllowTrailingWhite) {
4034 // Trim trailing spaces if AllowTrailingWhite.
4036 format.RemoveTrailingInQuoteSpaces();
4040 if ((styles & DateTimeStyles.AllowLeadingWhite) != 0) {
4041 format.SkipWhiteSpaces();
4042 format.RemoveLeadingInQuoteSpaces();
4043 str.SkipWhiteSpaces();
4047 // Scan every character in format and match the pattern in str.
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();
4055 if (!ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result)) {
4060 if (str.Index < str.Value.Length - 1) {
4061 // There are still remaining character in str.
4062 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
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);
4073 result.Year = parseInfo.calendar.ToFourDigitYear(result.Year);
4075 catch (ArgumentOutOfRangeException e) {
4076 result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", e);
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;
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);
4095 if (parseInfo.timeMark == TM.AM) {
4096 if (result.Hour == 12) {
4100 result.Hour = (result.Hour == 12) ? 12 : result.Hour + 12;
4105 // Military (24-hour time) mode
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);
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)) {
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);
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);
4134 if (result.fraction > 0) {
4135 result.parsedDate = result.parsedDate.AddTicks((long)Math.Round(result.fraction * Calendar.TicksPerSecond));
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.
4143 if (parseInfo.dayOfWeek != -1) {
4145 // Check if day of week is correct.
4147 if (parseInfo.dayOfWeek != (int)parseInfo.calendar.GetDayOfWeek(result.parsedDate)) {
4148 result.SetFailure(ParseFailureKind.Format, "Format_BadDayOfWeek", null);
4154 if (!DetermineTimeZoneAdjustments(ref result, styles, bTimeOnly)) {
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));
4171 Contract.Assert(false, "Unkown DateTimeParseFailure: " + result);
4187 [Conditional("_LOGGING")]
4188 [ResourceExposure(ResourceScope.None)]
4189 internal static void LexTraceExit(string message, DS dps) {
4191 if (!_tracingEnabled)
4193 BCLDebug.Trace("DATETIME", "[DATETIME] Lex return {0}, DS.{1}", message, dps);
4197 [Conditional("_LOGGING")]
4198 [ResourceExposure(ResourceScope.None)]
4199 internal static void PTSTraceExit(DS dps, bool passed) {
4201 if (!_tracingEnabled)
4203 BCLDebug.Trace("DATETIME", "[DATETIME] ProcessTerminalState {0} @ DS.{1}", passed ? "passed" : "failed", dps);
4207 [Conditional("_LOGGING")]
4208 [ResourceExposure(ResourceScope.None)]
4209 internal static void TPTraceExit(string message, DS dps) {
4211 if (!_tracingEnabled)
4213 BCLDebug.Trace("DATETIME", "[DATETIME] TryParse return {0}, DS.{1}", message, dps);
4217 [Conditional("_LOGGING")]
4218 [ResourceExposure(ResourceScope.None)]
4219 internal static void DTFITrace(DateTimeFormatInfo dtfi) {
4221 if (!_tracingEnabled)
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));
4240 [ResourceExposure(ResourceScope.None)]
4241 // return a string in the form: "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
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]);
4248 int curLineLength = 0;
4249 int maxLineLength = 55;
4250 int newLinePadding = 20;
4253 //invariant: strs.Length >= 2
4254 StringBuilder buffer = new StringBuilder();
4255 buffer.Append(Hex(strs[0]));
4256 curLineLength = buffer.Length;
4259 for (int i = 1; i < strs.Length-1; i++) {
4262 if (s.Length > maxLineLength || (curLineLength + s.Length + 2) > maxLineLength) {
4264 buffer.Append(Environment.NewLine);
4265 buffer.Append(' ', newLinePadding);
4269 buffer.Append(", ");
4273 curLineLength += s.Length;
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);
4286 return buffer.ToString();
4289 [ResourceExposure(ResourceScope.None)]
4290 // return a string in the form: "Sun"
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]);
4298 buffer.Append("\\u" + ((int)str[i]).ToString("x4", CultureInfo.InvariantCulture));
4300 buffer.Append("\"");
4301 return buffer.ToString();
4304 [ResourceExposure(ResourceScope.None)]
4305 // return an unicode escaped string form of char c
4306 internal static String Hex(char c) {
4308 return c.ToString(CultureInfo.InvariantCulture);
4310 return "\\u" + ((int)c).ToString("x4", CultureInfo.InvariantCulture);
4313 internal static bool _tracingEnabled = BCLDebug.CheckEnabled("DATETIME");
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.
4328 // Value propery: stores the real string to be parsed.
4330 internal String Value;
4333 // Index property: points to the character that we are currently parsing.
4337 // The length of Value string.
4340 // The current chracter to be looked at.
4341 internal char m_current;
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;
4348 internal __DTString(String str, DateTimeFormatInfo dtfi, bool checkDigitToken) : this (str, dtfi)
4350 m_checkDigitToken = checkDigitToken;
4353 internal __DTString(String str, DateTimeFormatInfo dtfi)
4362 m_info = dtfi.CompareInfo;
4363 m_checkDigitToken = ((dtfi.FormatFlags & DateTimeFormatFlags.UseDigitPrefixInTokens) != 0);
4366 m_info = Thread.CurrentThread.CurrentCulture.CompareInfo;
4367 m_checkDigitToken = false;
4371 internal CompareInfo CompareInfo
4373 get { return m_info; }
4377 // Advance the Index.
4378 // Return true if Index is NOT at the end of the string.
4381 // while (str.GetNext())
4383 // char ch = str.GetChar()
4385 internal bool GetNext() {
4388 m_current = Value[Index];
4394 internal bool AtEnd()
4396 return Index < len ? false : true;
4399 internal bool Advance(int count) {
4400 Contract.Assert(Index + count <= len, "__DTString::Advance: Index + count <= len");
4403 m_current = Value[Index];
4410 // Used by DateTime.Parse() to get the next token.
4411 [System.Security.SecurityCritical] // auto-generated
4412 internal void GetRegularToken(out TokenType tokenType, out int tokenValue, DateTimeFormatInfo dtfi) {
4415 tokenType = TokenType.EndOfString;
4419 tokenType = TokenType.UnknownToken;
4422 if (DateTimeParse.IsDigit(m_current)) {
4424 tokenValue = m_current - '0';
4429 // Collect other digits.
4431 while (++Index < len)
4433 m_current = Value[Index];
4434 value = m_current - '0';
4435 if (value >= 0 && value <= 9) {
4436 tokenValue = tokenValue * 10 + value;
4441 if (Index - start > DateTimeParse.MaxDateTimeNumberDigits) {
4442 tokenType = TokenType.NumberToken;
4444 } else if (Index - start < 3) {
4445 tokenType = TokenType.NumberToken;
4447 // If there are more than 3 digits, assume that it's a year value.
4448 tokenType = TokenType.YearNumberToken;
4450 if (m_checkDigitToken)
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];
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))
4463 tokenType = tempType;
4464 tokenValue = tempValue;
4465 // This is a token, so the Index has been advanced propertly in DTFI.Tokenizer().
4468 // Use the number token value.
4469 // Restore the index.
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))) {
4484 // We have reached the end of string.
4485 tokenType = TokenType.EndOfString;
4487 dtfi.Tokenize(TokenType.RegularTokenMask, out tokenType, out tokenValue, ref this);
4491 [System.Security.SecurityCritical] // auto-generated
4492 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);
4500 if (!DateTimeParse.IsDigit(m_current)) {
4501 // Not a digit. Tokenize it.
4503 bool found = dtfi.Tokenize(TokenType.SeparatorTokenMask, out tokenType, out tokenValue, ref this);
4505 tokenType = TokenType.SEP_Space;
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
4510 tokenType = TokenType.SEP_Space;
4515 internal bool MatchSpecifiedWord(String target) {
4516 return MatchSpecifiedWord(target, target.Length + Index);
4519 internal bool MatchSpecifiedWord(String target, int endIndex) {
4520 int count = endIndex - Index;
4522 if (count != target.Length) {
4526 if (Index + count > len) {
4530 return (m_info.Compare(Value, Index, count, target, 0, count, CompareOptions.IgnoreCase)==0);
4533 private static Char[] WhiteSpaceChecks = new Char[] { ' ', '\u00A0' };
4535 internal bool MatchSpecifiedWords(String target, bool checkWordBoundary, ref int matchLength) {
4536 int valueRemaining = Value.Length - Index;
4537 matchLength = target.Length;
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) {
4548 int segmentLength = wsIndex - targetPosition;
4549 if (thisPosition >= Value.Length - segmentLength) { // Subtraction to prevent overflow.
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.
4557 // Make sure we also have whitespace in the input string
4558 if (!Char.IsWhiteSpace(Value[thisPosition + segmentLength])) {
4561 if (m_info.Compare(Value, thisPosition, segmentLength, target, targetPosition, segmentLength, CompareOptions.IgnoreCase) !=0) {
4564 // Advance the input string
4565 thisPosition = thisPosition + segmentLength + 1;
4567 // Advance our target string
4568 targetPosition = wsIndex + 1;
4571 // Skip past multiple whitespace
4572 while (thisPosition < Value.Length && Char.IsWhiteSpace(Value[thisPosition])) {
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) {
4583 if (m_info.Compare(Value, thisPosition, segmentLength, target, targetPosition, segmentLength, CompareOptions.IgnoreCase) !=0) {
4589 if (checkWordBoundary) {
4590 int nextCharIndex = Index + matchLength;
4591 if (nextCharIndex < Value.Length) {
4592 if (Char.IsLetter(Value[nextCharIndex])) {
4601 // Check to see if the string starting from Index is a prefix of
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.
4606 internal bool Match(String str) {
4607 if (++Index >= len) {
4611 if (str.Length > (Value.Length - Index)) {
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);
4625 internal bool Match(char ch) {
4626 if (++Index >= len) {
4629 if (Value[Index] == ch) {
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.
4642 // The index that contains the longest word to match
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.
4648 internal int MatchLongestWords(String[] words, ref int maxMatchStrLen) {
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;
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 ':'.
4669 internal int GetRepeatCount() {
4670 char repeatChar = Value[Index];
4671 int pos = Index + 1;
4672 while ((pos < len) && (Value[pos] == repeatChar)) {
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.
4680 return (repeatCount);
4683 // Return false when end of string is encountered or a non-digit character is found.
4684 internal bool GetNextDigit() {
4685 if (++Index >= len) {
4688 return (DateTimeParse.IsDigit(Value[Index]));
4692 // Get the current character.
4694 internal char GetChar() {
4695 Contract.Assert(Index >= 0 && Index < len, "Index >= 0 && Index < len");
4696 return (Value[Index]);
4700 // Convert the current character to a digit, and return it.
4702 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');
4709 // Eat White Space ahead of the current position
4711 // Return false if end of string is encountered.
4713 internal void SkipWhiteSpaces()
4715 // Look ahead to see if the next character
4717 while (Index+1 < len)
4719 char ch = Value[Index+1];
4720 if (!Char.IsWhiteSpace(ch)) {
4729 // Skip white spaces from the current position
4731 // Return false if end of string is encountered.
4733 internal bool SkipWhiteSpaceCurrent()
4739 if (!Char.IsWhiteSpace(m_current))
4744 while (++Index < len)
4746 m_current = Value[Index];
4747 if (!Char.IsWhiteSpace(m_current))
4756 internal void TrimTail() {
4758 while (i >= 0 && Char.IsWhiteSpace(Value[i])) {
4761 Value = Value.Substring(0, i + 1);
4765 // Trim the trailing spaces within a quoted string.
4766 // Call this after TrimTail() is done.
4767 internal void RemoveTrailingInQuoteSpaces() {
4773 // Check if the last character is a quote.
4774 if (ch == '\'' || ch == '\"') {
4775 if (Char.IsWhiteSpace(Value[i-1])) {
4777 while (i >= 1 && Char.IsWhiteSpace(Value[i-1])) {
4780 Value = Value.Remove(i, Value.Length - 1 - i);
4786 // Trim the leading spaces within a quoted string.
4787 // Call this after the leading spaces before quoted string are trimmed.
4788 internal void RemoveLeadingInQuoteSpaces() {
4794 // Check if the last character is a quote.
4795 if (ch == '\'' || ch == '\"') {
4796 while ((i + 1) < len && Char.IsWhiteSpace(Value[i+1])) {
4800 Value = Value.Remove(1, i);
4806 internal DTSubString GetSubString() {
4807 DTSubString sub = new DTSubString();
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;
4817 currentType = DTSubStringType.Other;
4820 if (sub.length == 0) {
4821 sub.type = currentType;
4824 if (sub.type != currentType) {
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;
4836 int number = ch - '0';
4837 Contract.Assert(number >= 0 && number <= 9, "number >= 0 && number <= 9");
4838 sub.value = sub.value * 10 + number;
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
4847 if (sub.length == 0) {
4848 sub.type = DTSubStringType.End;
4855 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;
4860 m_current = Value[Index];
4865 internal enum DTSubStringType {
4873 internal struct DTSubString {
4875 internal Int32 index;
4876 internal Int32 length;
4877 internal DTSubStringType type;
4878 internal Int32 value;
4880 internal Char this[Int32 relativeIndex] {
4882 return s[index + relativeIndex];
4888 // The buffer to store the parsing token.
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)
4898 // The buffer to store temporary parsing information.
4901 unsafe struct DateTimeRawInfo {
4904 internal int numCount;
4907 internal int dayOfWeek;
4909 internal DateTimeParse.TM timeMark;
4910 internal double fraction;
4911 internal bool hasSameDateAndTimeSeparators;
4916 internal bool timeZone;
4918 [System.Security.SecurityCritical] // auto-generated
4919 internal void Init(int * numberBuffer) {
4924 timeMark = DateTimeParse.TM.NotSet;
4928 [System.Security.SecuritySafeCritical] // auto-generated
4929 internal unsafe void AddNumber(int value) {
4930 num[numCount++] = value;
4932 [System.Security.SecuritySafeCritical] // auto-generated
4933 internal unsafe int GetNumber(int index) {
4938 internal enum ParseFailureKind {
4942 FormatWithParameter = 3,
4943 FormatBadDateTimeCalendar = 4, // FormatException when ArgumentOutOfRange is thrown by a Calendar.TryToDateTime().
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,
4966 // This will store the result of the parsing. And it will be eventually
4967 // used to construct a DateTime instance.
4970 struct DateTimeResult
4976 // Set time defualt to 00:00:00.
4979 internal int Minute;
4980 internal int Second;
4981 internal double fraction;
4985 internal ParseFlags flags;
4987 internal TimeSpan timeZoneOffset;
4989 internal Calendar calendar;
4991 internal DateTime parsedDate;
4993 internal ParseFailureKind failure;
4994 internal string failureMessageID;
4995 internal object failureMessageFormatArgument;
4996 internal string failureArgumentName;
4998 internal void Init() {
5006 internal void SetDate(int year, int month, int day)
5012 internal void SetFailure(ParseFailureKind failure, string failureMessageID, object failureMessageFormatArgument) {
5013 this.failure = failure;
5014 this.failureMessageID = failureMessageID;
5015 this.failureMessageFormatArgument = failureMessageFormatArgument;
5018 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;
5030 // This is the helper data structure used in ParseExact().
5031 internal struct ParsingInfo {
5033 internal Calendar calendar;
5034 internal int dayOfWeek;
5035 internal DateTimeParse.TM timeMark;
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;
5044 internal void Init() {
5046 timeMark = DateTimeParse.TM.NotSet;
5052 // The type of token that will be returned by DateTimeFormatInfo.Tokenize().
5054 internal enum TokenType {
5055 // The valid token should start from 1.
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. "[....]"
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
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
5092 RegularTokenMask = 0x00ff,
5093 SeparatorTokenMask = 0xff00,