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