// ==++== // // // Copyright (c) 2002 Microsoft Corporation. All rights reserved. // // The use and distribution terms for this software are contained in the file // named license.txt, which can be found in the root of this distribution. // By using this software in any fashion, you are agreeing to be bound by the // terms of this license. // // You must not remove this notice, or any other, from this software. // // // ==--== // // Class: DateTimeParse // // // Purpose: This class is called by DateTime to parse a date/time string. // // Date: July 8, 1999 // namespace System { using System; using System.Text; using System.Globalization; using System.Threading; using System.Collections; /* NOTENOTE : There are some nasty cases in the parsing of date/time: 0x438 fo (country:Faeroe Islands, languge:Faeroese) LOCALE_STIME=[.] LOCALE_STIMEFOR=[HH.mm.ss] LOCALE_SDATE=[-] LOCALE_SSHORTDATE=[dd-MM-yyyy] LOCALE_SLONGDATE=[d. MMMM yyyy] The time separator is ".", However, it also has a "." in the long date format. 0x437: (country:Georgia, languge:Georgian) Short date: dd.MM.yy Long date: yyyy ???? dd MM, dddd The order in long date is YDM, which is different from the common ones: YMD/MDY/DMY. 0x0404: (country:Taiwan, languge:Chinese) LOCALE_STIMEFORMAT=[tt hh:mm:ss] When general date is used, the pattern is "yyyy/M/d tt hh:mm:ss". Note that the "tt" is after "yyyy/M/d". And this is different from most cultures. 0x0437: (country:Georgia, languge:Georgian) Short date: dd.MM.yy Long date: yyyy ???? dd MM, dddd 0x0456: */ /* */ //This class contains only static members and does not require the serializable attribute. internal class DateTimeParse { private static String alternativeDateSeparator = "-"; private static DateTimeFormatInfo invariantInfo = DateTimeFormatInfo.InvariantInfo; // // This is used to cache the lower-cased month names of the invariant culture. // private static String[] invariantMonthNames = null; private static String[] invariantAbbrevMonthNames = null; // // This is used to cache the lower-cased day names of the invariant culture. // private static String[] invariantDayNames = null; private static String[] invariantAbbrevDayNames = null; private static String invariantAMDesignator = invariantInfo.AMDesignator; private static String invariantPMDesignator = invariantInfo.PMDesignator; private static DateTimeFormatInfo m_jajpDTFI = null; private static DateTimeFormatInfo m_zhtwDTFI = null; internal static DateTime ParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style) { if (s == null || format == null) { throw new ArgumentNullException((s == null ? "s" : "format"), Environment.GetResourceString("ArgumentNull_String")); } if (s.Length == 0) { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } if (format.Length == 0) { throw new FormatException(Environment.GetResourceString("Format_BadFormatSpecifier")); } if (dtfi == null) { dtfi = DateTimeFormatInfo.CurrentInfo; } DateTime result; if (DoStrictParse(s, format, style, dtfi, true, out result)) { return (result); } // // This is just used to keep compiler happy. // This is because DoStrictParse() alwyas does either: // 1. Return true or // 2. Throw exceptions if there is error in parsing. // So we will never get here. // return (new DateTime()); } internal static bool ParseExactMultiple(String s, String[] formats, DateTimeFormatInfo dtfi, DateTimeStyles style, out DateTime result) { if (s == null || formats == null) { throw new ArgumentNullException((s == null ? "s" : "formats"), Environment.GetResourceString("ArgumentNull_String")); } if (s.Length == 0) { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } if (formats.Length == 0) { throw new FormatException(Environment.GetResourceString("Format_BadFormatSpecifier")); } if (dtfi == null) { dtfi = DateTimeFormatInfo.CurrentInfo; } // // Do a loop through the provided formats and see if we can parse succesfully in // one of the formats. // for (int i = 0; i < formats.Length; i++) { if (formats[i] == null || formats[i].Length == 0) { throw new FormatException(Environment.GetResourceString("Format_BadFormatSpecifier")); } if (DoStrictParse(s, formats[i], style, dtfi, false, out result)) { return (true); } } result = new DateTime(0); return (false); } // // separator types // private const int SEP_Unk = 0; // Unknown separator. private const int SEP_End = 1; // The end of the parsing string. private const int SEP_Space = 2; // Whitespace (including comma). private const int SEP_Am = 3; // AM timemark. private const int SEP_Pm = 4; // PM timemark. private const int SEP_Date = 5; // date separator. private const int SEP_Time = 6; // time separator. private const int SEP_YearSuff = 7; // Chinese/Japanese/Korean year suffix. private const int SEP_MonthSuff = 8; // Chinese/Japanese/Korean month suffix. private const int SEP_DaySuff = 9; // Chinese/Japanese/Korean day suffix. private const int SEP_HourSuff = 10; // Chinese/Japanese/Korean hour suffix. private const int SEP_MinuteSuff = 11; // Chinese/Japanese/Korean minute suffix. private const int SEP_SecondSuff = 12; // Chinese/Japanese/Korean second suffix. private const int SEP_LocalTimeMark = 13; // 'T' private const int SEP_Max = 14; // Date Token Types (DTT_*) // // Following is the set of tokens that can be generated from a date // string. Notice that the legal set of trailing separators have been // folded in with the date number, and month name tokens. This set // of tokens is chosen to reduce the number of date parse states. // private const int DTT_End = 0; // '/0' private const int DTT_NumEnd = 1; // Num[ ]*[/0] private const int DTT_NumAmpm = 2; // Num[ ]+AmPm private const int DTT_NumSpace = 3; // Num[ ]+^[Dsep|Tsep|'0/'] private const int DTT_NumDatesep = 4; // Num[ ]*Dsep private const int DTT_NumTimesep = 5; // Num[ ]*Tsep private const int DTT_MonthEnd = 6; // Month[ ]*'/0' private const int DTT_MonthSpace = 7; // Month[ ]+^[Dsep|Tsep|'/0'] private const int DTT_MonthDatesep=8; // Month[ ]*Dsep private const int DTT_NumDatesuff= 9; // Month[ ]*DSuff private const int DTT_NumTimesuff= 10; // Month[ ]*TSuff private const int DTT_DayOfWeek = 11; // Day of week name private const int DTT_YearSpace = 12; // Year+^[Dsep|Tsep|'0/'] private const int DTT_YearDateSep= 13; // Year+Dsep private const int DTT_YearEnd = 14; // Year+['/0'] private const int DTT_TimeZone = 15; // timezone name private const int DTT_Era = 16; // era name private const int DTT_NumUTCTimeMark = 17; // Num + 'Z' // When you add a new token which will be in the // state table, add it after DTT_NumLocalTimeMark. private const int DTT_Unk = 18; // unknown private const int DTT_NumLocalTimeMark = 19; // Num + 'T' private const int DTT_Max = 20; // marker private const int TM_AM = 0; private const int TM_PM = 1; // // Year/Month/Day suffixes // private const String CJKYearSuff = "/u5e74"; private const String CJKMonthSuff = "/u6708"; private const String CJKDaySuff = "/u65e5"; private const String KoreanYearSuff = "/ub144"; private const String KoreanMonthSuff = "/uc6d4"; private const String KoreanDaySuff = "/uc77c"; private const String CJKHourSuff = "/u6642"; private const String ChineseHourSuff = "/u65f6"; private const String CJKMinuteSuff = "/u5206"; private const String CJKSecondSuff = "/u79d2"; private const String LocalTimeMark = "T"; // // DateTime parsing state enumeration (DS_*) // private const int DS_BEGIN = 0; private const int DS_N = 1; // have one number private const int DS_NN = 2; // have two numbers // The following are known to be part of a date private const int DS_D_Nd = 3; // date string: have number followed by date separator private const int DS_D_NN = 4; // date string: have two numbers private const int DS_D_NNd = 5; // date string: have two numbers followed by date separator private const int DS_D_M = 6; // date string: have a month private const int DS_D_MN = 7; // date string: have a month and a number private const int DS_D_NM = 8; // date string: have a number and a month private const int DS_D_MNd = 9; // date string: have a month and number followed by date separator private const int DS_D_NDS = 10; // date string: have one number followed a date suffix. private const int DS_D_Y = 11; // date string: have a year. private const int DS_D_YN = 12; // date string: have a year and a number private const int DS_D_YM = 13; // date string: have a year and a month private const int DS_D_S = 14; // have numbers followed by a date suffix. private const int DS_T_S = 15; // have numbers followed by a time suffix. // The following are known to be part of a time private const int DS_T_Nt = 16; // have num followed by time separator private const int DS_T_NNt = 17; // have two numbers followed by time separator private const int DS_ERROR = 18; // The following are terminal states. These all have an action // associated with them; and transition back to DS_BEGIN. private const int DS_DX_NN = 19; // day from two numbers private const int DS_DX_NNN = 20; // day from three numbers private const int DS_DX_MN = 21; // day from month and one number private const int DS_DX_NM = 22; // day from month and one number private const int DS_DX_MNN = 23; // day from month and two numbers private const int DS_DX_DS = 24; // a set of date suffixed numbers. private const int DS_DX_DSN = 25; // day from date suffixes and one number. private const int DS_DX_NDS = 26; // day from one number and date suffixes . private const int DS_DX_NNDS = 27; // day from one number and date suffixes . private const int DS_DX_YNN = 28; // date string: have a year and two number private const int DS_DX_YMN = 29; // date string: have a year, a month, and a number. private const int DS_DX_YN = 30; // date string: have a year and one number private const int DS_DX_YM = 31; // date string: have a year, a month. private const int DS_TX_N = 32; // time from one number (must have ampm) private const int DS_TX_NN = 33; // time from two numbers private const int DS_TX_NNN = 34; // time from three numbers private const int DS_TX_TS = 35; // a set of time suffixed numbers. private const int DS_DX_NNY = 36; private const int DS_MAX = 37; // marker: end of enum // // NOTE: The following state machine table is dependent on the order of the // DS_ and DTT_ enumerations. // // For each non terminal state, the following table defines the next state // for each given date token type. // // DTT_End DTT_NumEnd DTT_NumAmPm DTT_NumSpace // DTT_NumDaySep // DTT_NumTimesep // DTT_MonthEnd DTT_MonthSpace // DTT_MonthDSep // DTT_NumDateSuff // DTT_NumTimeSuff DTT_DayOfWeek // DTT_YearSpace // DTT_YearDateSep // DTT_YearEnd, // DTT_TimeZone // DTT_Era DTT_UTCTimeMark private static int[][] dateParsingStates = { // DS_BEGIN // DS_BEGIN new int[] { 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}, // DS_N // DS_N new int[] { 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_YN, DS_DX_YN, DS_N, DS_N, DS_ERROR}, // DS_NN // DS_NN new int[] { 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}, // DS_D_Nd // DS_D_Nd //new int[] { DS_ERROR, DS_DX_NN, DS_ERROR, DS_D_NN, DS_D_NNd, DS_ERROR, DS_DX_MN, DS_D_MN, DS_D_MNd, DS_ERROR, DS_ERROR, DS_D_Nd, DS_D_YN, DS_D_YN, DS_DX_YN, DS_ERROR, DS_D_Nd, DS_ERROR}, new int[] { 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_YN, DS_DX_YN, DS_ERROR, DS_D_Nd, DS_ERROR}, // DS_D_NN // DS_D_NN new int[] { 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}, // DS_D_NNd // DS_D_NNd new int[] { DS_ERROR, DS_DX_NNN, DS_DX_NNN, DS_DX_NNN, DS_DX_NNN, 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}, // DS_D_M // DS_D_M new int[] { 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_YM, DS_DX_YM, DS_ERROR, DS_D_M, DS_ERROR}, // DS_D_MN // DS_D_MN new int[] { DS_DX_MN, DS_DX_MNN, DS_DX_MNN, DS_DX_MNN, DS_DX_MNN, DS_T_Nt, DS_ERROR, DS_ERROR, DS_ERROR, DS_DX_DS, DS_T_S, DS_D_MN, DS_DX_YMN, DS_DX_YMN, DS_DX_YMN, DS_ERROR, DS_D_MN, DS_ERROR}, // DS_D_NM // DS_D_NM new int[] { 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}, // DS_D_MNd // DS_D_MNd //new int[] { 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}, new int[] { DS_DX_MN, 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}, // DS_D_NDS, // DS_D_NDS, new int[] { 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}, // DS_D_Y // DS_D_Y new int[] { DS_ERROR, DS_ERROR, DS_ERROR, DS_D_YN, DS_D_YN, DS_ERROR, DS_DX_YM, DS_D_YM, DS_D_YM, DS_D_YM, DS_ERROR, DS_D_Y, DS_ERROR, DS_ERROR, DS_ERROR, DS_ERROR, DS_D_Y, DS_ERROR}, // DS_D_YN // DS_D_YN new int[] { DS_ERROR, DS_DX_YNN, DS_DX_YNN, DS_DX_YNN, DS_DX_YNN, 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}, // DS_D_YM // DS_D_YM new int[] { DS_DX_YM, DS_DX_YMN, DS_DX_YMN, DS_DX_YMN, DS_DX_YMN, 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}, // DS_D_S // DS_D_S new int[] { 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}, // DS_T_S // DS_T_S new int[] { 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}, // DS_T_Nt // DS_T_Nt //new int[] { DS_ERROR, DS_TX_NN, DS_TX_NN, DS_TX_NN, DS_ERROR, DS_T_NNt, DS_ERROR, 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}, new int[] { 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}, // DS_T_NNt // DS_T_NNt new int[] { 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}, }; // End NumEnd NumAmPm NumSpace NumDaySep NumTimesep MonthEnd MonthSpace MonthDSep NumDateSuff NumTimeSuff DayOfWeek YearSpace YearDateSep YearEnd private const String GMTName = "GMT"; private const String ZuluName = "Z"; // // Search from the index of str at str.Index to see if the target string exists in the str. // // allowPartialMatch: If true, the method will not check if the matching string // is in a word boundary, and will skip to the word boundary. // For example, it will return true for matching "January" in "Januaryfoo". // If false, the matching word must be in a word boundary. So it will return // false for matching "January" in "Januaryfoo". // private static bool MatchWord(__DTString str, String target, bool allowPartialMatch) { int length = target.Length; if (length > (str.Value.Length - str.Index)) { return false; } if (CultureInfo.CurrentCulture.CompareInfo.Compare(str.Value, str.Index, length, target, 0, length, CompareOptions.IgnoreCase)!=0) { return (false); } int nextCharIndex = str.Index + target.Length; if (allowPartialMatch) { // // Skip the remaining part of the word. // // This is to ignore special cases like: // a few cultures has suffix after MMMM, like "fi" and "eu". while (nextCharIndex < str.Value.Length) { if (!Char.IsLetter(str.Value[nextCharIndex])) { break; } nextCharIndex++; } } else { if (nextCharIndex < str.Value.Length) { char nextCh = str.Value[nextCharIndex]; if (Char.IsLetter(nextCh) || nextCh == '/x00a1') { // The '/x00a1' is a very unfortunate hack, since // gl-ES (0x0456) used '/x00a1' in their day of week names. // And '/x00a1' is a puctuation. This breaks our // assumption that there is no non-letter characters // following a day/month name. return (false); } } } str.Index = nextCharIndex; return (true); } // // Starting at str.Index, check the type of the separator. // private static int GetSeparator(__DTString str, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { int separator = SEP_Space; // Assume the separator is a space. And try to find a better one. // // Check if we found any white spaces. // if (!str.SkipWhiteSpaceComma()) { // // SkipWhiteSpaceComma() will return true when end of string is reached. // // // Return the separator as SEP_End. // return (SEP_End); } if (Char.IsLetter(str.GetChar())) { // // This is a beginning of a word. // if (raw.timeMark == -1) { // // Check if this is an AM time mark. // int timeMark; if ((timeMark = GetTimeMark(str, dtfi)) != -1) { raw.timeMark = timeMark;; return (timeMark == TM_AM ? SEP_Am: SEP_Pm); } } if (MatchWord(str, LocalTimeMark, false)) { separator = SEP_LocalTimeMark; } else if (MatchWord(str, CJKYearSuff, false) || MatchWord(str, KoreanYearSuff, false)) { separator = SEP_YearSuff; } else if (MatchWord(str, CJKMonthSuff, false) || MatchWord(str, KoreanMonthSuff, false)) { separator = SEP_MonthSuff; } else if (MatchWord(str, CJKDaySuff, false) || MatchWord(str, KoreanDaySuff, false)) { separator = SEP_DaySuff; } else if (MatchWord(str, CJKHourSuff, false) || MatchWord(str, ChineseHourSuff, false)) { separator = SEP_HourSuff; } else if (MatchWord(str, CJKMinuteSuff, false)) { separator = SEP_MinuteSuff; } else if (MatchWord(str, CJKSecondSuff, false)) { separator = SEP_SecondSuff; } } else { // // Not a letter. Check if this is a date separator. // if ((MatchWord(str, dtfi.DateSeparator, false)) || (MatchWord(str, invariantInfo.DateSeparator, false)) || (MatchWord(str, alternativeDateSeparator, false))) { // // NOTENOTE : alternativeDateSeparator is a special case because some cultures // (e.g. the invariant culture) use "/". However, in RFC format, we use "-" as the // date separator. Therefore, we should check for it. // separator = SEP_Date; } // // Check if this is a time separator. // else if ((MatchWord(str, dtfi.TimeSeparator, false)) || (MatchWord(str, invariantInfo.TimeSeparator, false))) { separator = SEP_Time; } else if (dtfi.CultureID == 0x041c) { // Special case for sq-AL (0x041c) // Its time pattern is "h:mm:ss.tt" if (str.GetChar() == '.') { if (raw.timeMark == -1) { // // Check if this is an AM time mark. // int timeMark; str.Index++; if ((timeMark = GetTimeMark(str, dtfi)) != -1) { raw.timeMark = timeMark;; return (timeMark == TM_AM ? SEP_Am: SEP_Pm); } str.Index--; } } } } return (separator); } // // Check the word at the current index to see if it matches a month name. // Return -1 if a match is not found. Otherwise, a value from 1 to 12 is returned. // private static int GetMonthNumber(__DTString str, DateTimeFormatInfo dtfi) { // // Check the month name specified in dtfi. // int i; int monthInYear = (dtfi.GetMonthName(13).Length == 0 ? 12 : 13); int maxLen = 0; int result = -1; int index; String word = str.PeekCurrentWord(); // // We have to match the month name with the longest length, // since there are cultures which have more than one month names // with the same prefix. // for (i = 1; i <= monthInYear; i++) { String monthName = dtfi.GetMonthName(i); if ((index = str.CompareInfo.IndexOf( word, monthName, CompareOptions.IgnoreCase)) >= 0) { // This condition allows us to parse the month name for: // 1. the general cases like "yyyy MMMM dd". // 2. prefix in the month name like: "dd '/x05d1'MMMM yyyy" (Hebrew - Israel) result = i; maxLen = index + monthName.Length; } else if (str.StartsWith(monthName, true)) { // The condition allows us to get the month name for cultures // which has spaces in their month names. // E.g. if (monthName.Length > maxLen) { result = i; maxLen = monthName.Length; } } } if (result > -1) { str.Index += maxLen; return (result); } for (i = 1; i <= monthInYear; i++) { if (MatchWord(str, dtfi.GetAbbreviatedMonthName(i), false)) { return (i); } } // // Check the month name in the invariant culture. // for (i = 1; i <= 12; i++) { if (MatchWord(str, invariantInfo.GetMonthName(i), false)) { return (i); } } for (i = 1; i <= 12; i++) { if (MatchWord(str, invariantInfo.GetAbbreviatedMonthName(i), false)) { return (i); } } return (-1); } // // Check the word at the current index to see if it matches a day of week name. // Return -1 if a match is not found. Otherwise, a value from 0 to 6 is returned. // private static int GetDayOfWeekNumber(__DTString str, DateTimeFormatInfo dtfi) { // // Check the month name specified in dtfi. // DayOfWeek i; int maxLen = 0; int result = -1; // // We have to match the day name with the longest length, // since there are cultures which have more than one day of week names // with the same prefix. // int endIndex = str.FindEndOfCurrentWord(); String dayName=null; for (i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) { dayName = dtfi.GetDayName(i); if (str.MatchSpecifiedWord(dayName, endIndex)) { if (dayName.Length > maxLen) { result = (int)i; maxLen = dayName.Length; } } } if (result > -1) { str.Index = endIndex; return (result); } for (i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) { if (MatchWord(str, dtfi.GetAbbreviatedDayName(i), false)) { return ((int)i); } } // // Check the month name in the invariant culture. // for (i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) { if (MatchWord(str, invariantInfo.GetDayName(i), false)) { return ((int)i); } } for (i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) { if (MatchWord(str, invariantInfo.GetAbbreviatedDayName(i), false)) { return ((int)i); } } return (-1); } // // Check the word at the current index to see if it matches GMT name or Zulu name. // private static bool GetTimeZoneName(__DTString str) { // // if (MatchWord(str, GMTName, false)) { return (true); } if (MatchWord(str, ZuluName, false)) { return (true); } return (false); } // // Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse // date string with Japanese era name correctly even when the supplied DTFI // does not use Japanese calendar. // The created instance is stored in global m_jajpDTFI. // private static void GetJapaneseCalendarDTFI() { // Check Calendar.ID specifically to avoid a lock here. if (m_jajpDTFI == null || m_jajpDTFI.Calendar.ID != Calendar.CAL_JAPAN) { m_jajpDTFI = new CultureInfo("ja-JP", false).DateTimeFormat; m_jajpDTFI.Calendar = JapaneseCalendar.GetDefaultInstance(); } } // // Create a Taiwan DTFI which uses TaiwanCalendar. This is used to parse // date string with era name correctly even when the supplied DTFI // does not use Taiwan calendar. // The created instance is stored in global m_zhtwDTFI. // private static void GetTaiwanCalendarDTFI() { // Check Calendar.ID specifically to avoid a lock here. if (m_zhtwDTFI == null || m_zhtwDTFI.Calendar.ID != Calendar.CAL_TAIWAN) { m_zhtwDTFI = new CultureInfo("zh-TW", false).DateTimeFormat; m_zhtwDTFI.Calendar = TaiwanCalendar.GetDefaultInstance(); } } private static int GetEra(__DTString str, DateTimeResult result, ref DateTimeFormatInfo dtfi) { int[] eras = dtfi.Calendar.Eras; if (eras != null) { String word = str.PeekCurrentWord(); int era; if ((era = dtfi.GetEra(word)) > 0) { str.Index += word.Length; return (era); } switch (dtfi.CultureID) { case 0x0411: // 0x0411 is the culture ID for Japanese. if (dtfi.Calendar.ID != Calendar.CAL_JAPAN) { // If the calendar for dtfi is Japanese, we have already // done the check above. No need to re-check again. GetJapaneseCalendarDTFI(); if ((era = m_jajpDTFI.GetEra(word)) > 0) { str.Index += word.Length; result.calendar = JapaneseCalendar.GetDefaultInstance(); dtfi = m_jajpDTFI; return (era); } } break; case 0x0404: // 0x0404 is the culture ID for Taiwan. if (dtfi.Calendar.ID != Calendar.CAL_TAIWAN) { GetTaiwanCalendarDTFI(); if ((era = m_zhtwDTFI.GetEra(word)) > 0) { str.Index += word.Length; result.calendar = TaiwanCalendar.GetDefaultInstance(); dtfi = m_zhtwDTFI; return (era); } } break; } } return (-1); } private static int GetTimeMark(__DTString str, DateTimeFormatInfo dtfi) { if (((dtfi.AMDesignator.Length > 0) && MatchWord(str, dtfi.AMDesignator, false)) || MatchWord(str, invariantAMDesignator, false)) { return (TM_AM); } else if (((dtfi.PMDesignator.Length > 0) && MatchWord(str, dtfi.PMDesignator, false)) || MatchWord(str, invariantPMDesignator, false)) { // // Check if this is an PM time mark. // return (TM_PM); } return (-1); } internal static bool IsDigit(char ch) { // // How do we map full-width char return (ch >= '0' && ch <= '9'); } /*=================================ParseFraction========================== **Action: Starting at the str.Index, if the current character is a digit, parse the remaining ** numbers as fraction. For example, if the sub-string starting at str.Index is "123", then ** the method will return 0.123 **Returns: The fraction number. **Arguments: ** str the parsing string **Exceptions: ============================================================================*/ private static double ParseFraction(__DTString str) { double result = 0; double decimalBase = 0.1; char ch; while (str.Index <= str.len-1 && IsDigit(ch = str.Value[str.Index])) { result += (ch - '0') * decimalBase; decimalBase *= 0.1; str.Index++; } return (result); } /*=================================ParseTimeZone========================== **Action: Parse the timezone offset in the following format: ** "+8", "+08", "+0800", "+0800" ** This method is used by DateTime.Parse(). **Returns: The TimeZone offset. **Arguments: ** str the parsing string **Exceptions: ** FormatException if invalid timezone format is found. ============================================================================*/ private static TimeSpan ParseTimeZone(__DTString str, char offsetChar) { // The hour/minute offset for timezone. int hourOffset = 0; int minuteOffset = 0; if (str.GetNextDigit()) { // Get the first digit, Try if we can parse timezone in the form of "+8". hourOffset = str.GetDigit(); if (str.GetNextDigit()) { // Parsing "+18" hourOffset *= 10; hourOffset += str.GetDigit(); if (str.GetNext()) { char ch; if (Char.IsDigit(ch = str.GetChar())) { // Parsing "+1800" // Put the char back, since we already get the char in the previous GetNext() call. str.Index--; if (ParseDigits(str, 2, true, out minuteOffset)) { // ParseDigits() does not advance the char for us, so do it here. str.Index++; } else { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } } else if (ch == ':') { // Parsing "+18:00" if (ParseDigits(str, 2, true, out minuteOffset)) { str.Index++; } else { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } } else { // Not a digit, not a colon, put this char back. str.Index--; } } } // The next char is not a digit, so we get the timezone in the form of "+8". } else { // Invalid timezone: No numbers after +/-. throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } TimeSpan timezoneOffset = new TimeSpan(hourOffset, minuteOffset, 0); if (offsetChar == '-') { timezoneOffset = timezoneOffset.Negate(); } return (timezoneOffset); } // // This is the lexer. Check the character at the current index, and put the found token in dtok and // some raw date/time information in raw. // private static void Lex( int dps, __DTString str, DateTimeToken dtok, DateTimeRawInfo raw, DateTimeResult result, ref DateTimeFormatInfo dtfi) { int sep; dtok.dtt = DTT_Unk; // Assume the token is unkown. // // Skip any white spaces. // if (!str.SkipWhiteSpaceComma()) { // // SkipWhiteSpaceComma() will return true when end of string is reached. // dtok.dtt = DTT_End; return; } char ch = str.GetChar(); if (Char.IsLetter(ch)) { // // This is a letter. // int month, dayOfWeek, era, timeMark; // // Check if this is a beginning of a month name. // And check if this is a day of week name. // if (raw.month == -1 && (month = GetMonthNumber(str, dtfi)) >= 1) { // // This is a month name // switch(sep=GetSeparator(str, raw, dtfi)) { case SEP_End: dtok.dtt = DTT_MonthEnd; break; case SEP_Space: dtok.dtt = DTT_MonthSpace; break; case SEP_Date: dtok.dtt = DTT_MonthDatesep; break; default: //Invalid separator after month name throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } raw.month = month; } else if (raw.dayOfWeek == -1 && (dayOfWeek = GetDayOfWeekNumber(str, dtfi)) >= 0) { // // This is a day of week name. // raw.dayOfWeek = dayOfWeek; dtok.dtt = DTT_DayOfWeek; // // Discard the separator. // GetSeparator(str, raw, dtfi); } else if (GetTimeZoneName(str)) { // // This is a timezone designator // // NOTENOTE : for now, we only support "GMT" and "Z" (for Zulu time). // dtok.dtt = DTT_TimeZone; result.timeZoneUsed = true; result.timeZoneOffset = new TimeSpan(0); } else if ((raw.era == -1) && ((era = GetEra(str, result, ref dtfi)) != -1)) { raw.era = era; dtok.dtt = DTT_Era; } else if (raw.timeMark == -1 && (timeMark = GetTimeMark(str, dtfi)) != -1) { raw.timeMark = timeMark; GetSeparator(str, raw, dtfi); } else { // // Not a month name, not a day of week name. Check if this is one of the // known date words. This is used to deal case like Spanish cultures, which // uses 'de' in their Date string. // // if (!str.MatchWords(dtfi.DateWords)) { throw new FormatException( String.Format(Environment.GetResourceString("Format_UnknowDateTimeWord"), str.Index)); } GetSeparator(str, raw, dtfi); } } else if (Char.IsDigit(ch)) { if (raw.numCount == 3) { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } // // This is a digit. // int number = ch - '0'; int digitCount = 1; // // Collect other digits. // while (str.GetNextDigit()) { number = number * 10 + str.GetDigit(); digitCount++; } // If the previous parsing state is DS_T_NNt (like 12:01), and we got another number, // so we will have a terminal state DS_TX_NNN (like 12:01:02). // If the previous parsing state is DS_T_Nt (like 12:), and we got another number, // so we will have a terminal state DS_TX_NN (like 12:01:02). // // Look ahead to see if the following character is a decimal point or timezone offset. // This enables us to parse time in the forms of: // "11:22:33.1234" or "11:22:33-08". if (dps == DS_T_NNt || dps == DS_T_Nt) { char nextCh; if ((str.Index < str.len - 1)) { nextCh = str.Value[str.Index]; switch (nextCh) { case '.': if (dps == DS_T_NNt) { // Yes, advance to the next character. str.Index++; // Collect the second fraction. raw.fraction = ParseFraction(str); } break; case '+': case '-': if (result.timeZoneUsed) { // Should not have two timezone offsets. throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } result.timeZoneUsed = true; result.timeZoneOffset = ParseTimeZone(str, nextCh); break; } } } if (number >= 0) { dtok.num = number; if (digitCount >= 3) { if (raw.year == -1) { raw.year = number; // // If we have number which has 3 or more digits (like "001" or "0001"), // we assume this number is a year. Save the currnet raw.numCount in // raw.year. // switch (sep = GetSeparator(str, raw, dtfi)) { case SEP_End: dtok.dtt = DTT_YearEnd; break; case SEP_Am: case SEP_Pm: case SEP_Space: dtok.dtt = DTT_YearSpace; break; case SEP_Date: dtok.dtt = DTT_YearDateSep; break; case SEP_YearSuff: case SEP_MonthSuff: case SEP_DaySuff: dtok.dtt = DTT_NumDatesuff; dtok.suffix = sep; break; case SEP_HourSuff: case SEP_MinuteSuff: case SEP_SecondSuff: dtok.dtt = DTT_NumTimesuff; dtok.suffix = sep; break; default: // Invalid separator after number number. throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } // // Found the token already. Let's bail. // return; } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } } else { // // number is overflowed. // throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } switch (sep = GetSeparator(str, raw, dtfi)) { // // Note here we check if the numCount is less than three. // When we have more than three numbers, it will be caught as error in the state machine. // case SEP_End: dtok.dtt = DTT_NumEnd; raw.num[raw.numCount++] = dtok.num; break; case SEP_Am: case SEP_Pm: dtok.dtt = DTT_NumAmpm; raw.num[raw.numCount++] = dtok.num; break; case SEP_Space: dtok.dtt = DTT_NumSpace; raw.num[raw.numCount++] = dtok.num; break; case SEP_Date: dtok.dtt = DTT_NumDatesep; raw.num[raw.numCount++] = dtok.num; break; case SEP_Time: if (!result.timeZoneUsed) { dtok.dtt = DTT_NumTimesep; raw.num[raw.numCount++] = dtok.num; } else { // If we already got timezone, there should be no // time separator again. throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } break; case SEP_YearSuff: dtok.num = dtfi.Calendar.ToFourDigitYear(number); dtok.dtt = DTT_NumDatesuff; dtok.suffix = sep; break; case SEP_MonthSuff: case SEP_DaySuff: dtok.dtt = DTT_NumDatesuff; dtok.suffix = sep; break; case SEP_HourSuff: case SEP_MinuteSuff: case SEP_SecondSuff: dtok.dtt = DTT_NumTimesuff; dtok.suffix = sep; break; case SEP_LocalTimeMark: dtok.dtt = DTT_NumLocalTimeMark; raw.num[raw.numCount++] = dtok.num; break; default: // Invalid separator after number number. throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } } else { // // Not a letter, not a digit. Just ignore it. // str.Index++; } return; } private const int ORDER_YMD = 0; // The order of date is Year/Month/Day. private const int ORDER_MDY = 1; // The order of date is Month/Day/Year. private const int ORDER_DMY = 2; // The order of date is Day/Month/Year. private const int ORDER_YDM = 3; // The order of date is Year/Day/Month private const int ORDER_YM = 4; // Year/Month order. private const int ORDER_MY = 5; // Month/Year order. private const int ORDER_MD = 6; // Month/Day order. private const int ORDER_DM = 7; // Day/Month order. // // Decide the year/month/day order from the datePattern. // // Return 0 for YMD, 1 for MDY, 2 for DMY, otherwise -1. // private static int GetYearMonthDayOrder(String datePattern, DateTimeFormatInfo dtfi) { int yearOrder = -1; int monthOrder = -1; int dayOrder = -1; int orderCount = 0; bool inQuote = false; for (int i = 0; i < datePattern.Length && orderCount < 3; i++) { char ch = datePattern[i]; if (ch == '/'' || ch == '"') { inQuote = !inQuote; } if (!inQuote) { if (ch == 'y') { yearOrder = orderCount++; // // Skip all year pattern charaters. // for(; i+1 < datePattern.Length && datePattern[i+1] == 'y'; i++) { // Do nothing here. } } else if (ch == 'M') { monthOrder = orderCount++; // // Skip all month pattern characters. // for(; i+1 < datePattern.Length && datePattern[i+1] == 'M'; i++) { // Do nothing here. } } else if (ch == 'd') { int patternCount = 1; // // Skip all day pattern characters. // for(; i+1 < datePattern.Length && datePattern[i+1] == 'd'; i++) { patternCount++; } // // Make sure this is not "ddd" or "dddd", which means day of week. // if (patternCount <= 2) { dayOrder = orderCount++; } } } } if (yearOrder == 0 && monthOrder == 1 && dayOrder == 2) { return (ORDER_YMD); } if (monthOrder == 0 && dayOrder == 1 && yearOrder == 2) { return (ORDER_MDY); } if (dayOrder == 0 && monthOrder == 1 && yearOrder == 2) { return (ORDER_DMY); } if (yearOrder == 0 && dayOrder == 1 && monthOrder == 2) { return (ORDER_YDM); } throw new FormatException(String.Format(Environment.GetResourceString("Format_BadDatePattern"), datePattern)); } // // Decide the year/month order from the pattern. // // Return 0 for YM, 1 for MY, otherwise -1. // private static int GetYearMonthOrder(String pattern, DateTimeFormatInfo dtfi) { int yearOrder = -1; int monthOrder = -1; int orderCount = 0; bool inQuote = false; for (int i = 0; i < pattern.Length && orderCount < 2; i++) { char ch = pattern[i]; if (ch == '/'' || ch == '"') { inQuote = !inQuote; } if (!inQuote) { if (ch == 'y') { yearOrder = orderCount++; // // Skip all year pattern charaters. // for(; i+1 < pattern.Length && pattern[i+1] == 'y'; i++) { } } else if (ch == 'M') { monthOrder = orderCount++; // // Skip all month pattern characters. // for(; i+1 < pattern.Length && pattern[i+1] == 'M'; i++) { } } } } if (yearOrder == 0 && monthOrder == 1) { return (ORDER_YM); } if (monthOrder == 0 && yearOrder == 1) { return (ORDER_MY); } throw new FormatException(String.Format(Environment.GetResourceString("Format_BadDatePattern"), pattern)); } // // Decide the month/day order from the pattern. // // Return 0 for MD, 1 for DM, otherwise -1. // private static int GetMonthDayOrder(String pattern, DateTimeFormatInfo dtfi) { int monthOrder = -1; int dayOrder = -1; int orderCount = 0; bool inQuote = false; for (int i = 0; i < pattern.Length && orderCount < 2; i++) { char ch = pattern[i]; if (ch == '/'' || ch == '"') { inQuote = !inQuote; } if (!inQuote) { if (ch == 'd') { int patternCount = 1; // // Skip all day pattern charaters. // for(; i+1 < pattern.Length && pattern[i+1] == 'd'; i++) { patternCount++; } // // Make sure this is not "ddd" or "dddd", which means day of week. // if (patternCount <= 2) { dayOrder = orderCount++; } } else if (ch == 'M') { monthOrder = orderCount++; // // Skip all month pattern characters. // for(; i+1 < pattern.Length && pattern[i+1] == 'M'; i++) { } } } } if (monthOrder == 0 && dayOrder == 1) { return (ORDER_MD); } if (dayOrder == 0 && monthOrder == 1) { return (ORDER_DM); } throw new FormatException(String.Format(Environment.GetResourceString("Format_BadDatePattern"), pattern)); } private static bool IsValidMonth(DateTimeResult result, int year, int month) { return (month >= 1 && month <= result.calendar.GetMonthsInYear(year)); } // // NOTENOTE: This funciton assumes that year/month is correct. So call IsValidMonth before calling this. // private static bool IsValidDay(DateTimeResult result, int year, int month, int day) { return (day >= 1 && day <= result.calendar.GetDaysInMonth(year, month)); } // // Adjust the two-digit year if necessary. // private static int AdjustYear(DateTimeResult result, int year) { if (year < 100) { year = result.calendar.ToFourDigitYear(year); } return (year); } private static bool SetDateYMD(DateTimeResult result, int year, int month, int day) { if (IsValidMonth(result, year, month) && IsValidDay(result, year, month, day)) { result.SetDate(year, month, day); // YMD return (true); } return (false); } private static bool SetDateMDY(DateTimeResult result, int month, int day, int year) { return (SetDateYMD(result, year, month, day)); } private static bool SetDateDMY(DateTimeResult result, int day, int month, int year) { return (SetDateYMD(result, year, month, day)); } private static bool SetDateYDM(DateTimeResult result, int year, int day, int month) { return (SetDateYMD(result, year, month, day)); } // Processing teriminal case: DS_DX_NN private static void GetDayOfNN(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { int n1 = raw.num[0]; int n2 = raw.num[1]; int year = result.calendar.GetYear(DateTime.Now); int order = GetMonthDayOrder(dtfi.MonthDayPattern, dtfi); if (order == ORDER_MD) { if (SetDateYMD(result, year, n1, n2)) // MD { return; } } else { // ORDER_DM if (SetDateYMD(result, year, n2, n1)) // DM { return; } } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } // Processing teriminal case: DS_DX_NNN private static void GetDayOfNNN(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { int n1 = raw.num[0]; int n2 = raw.num[1];; int n3 = raw.num[2]; int order = GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi); if (order == ORDER_YMD) { if (SetDateYMD(result, AdjustYear(result, n1), n2, n3)) // YMD { return; } } else if (order == ORDER_MDY) { if (SetDateMDY(result, n1, n2, AdjustYear(result, n3))) // MDY { return; } } else if (order == ORDER_DMY) { if (SetDateDMY(result, n1, n2, AdjustYear(result, n3))) // DMY { return; } } else if (order == ORDER_YDM) { if (SetDateYDM(result, AdjustYear(result, n1), n2, n3)) // YDM { return; } } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } private static void GetDayOfMN(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { int currentYear = result.calendar.GetYear(DateTime.Now); result.Month = raw.month; // // NOTENOTE : in the case of invariant culture, // we will have an ambiguous situation when we have a string "June 11". // It could be 11-06-01 or CurrentYear-06-11. // In here, we favor CurrentYear-06-11 by checking the month/day first. // int monthDayOrder = GetMonthDayOrder(dtfi.MonthDayPattern, dtfi); if (monthDayOrder == ORDER_MD) { if (SetDateYMD(result, currentYear, raw.month, raw.num[0])) { return; } } else if (monthDayOrder == ORDER_DM) { if (SetDateYMD(result, currentYear, raw.month, raw.num[0])) { return; } } int yearMonthOrder = GetYearMonthOrder(dtfi.YearMonthPattern, dtfi); if (yearMonthOrder == ORDER_MY) { if (IsValidMonth(result, raw.num[0], raw.month)) { result.Year = raw.num[0]; result.Day = 1; return; } } if (IsValidDay(result, currentYear, result.Month, raw.num[0])) { result.Year = currentYear; result.Day = raw.num[0]; return; } if (IsValidDay(result, raw.num[0], result.Month, 1)) { result.Year = raw.num[0]; result.Day = 1; } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } private static void GetDayOfNM(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { int currentYear = result.calendar.GetYear(DateTime.Now); result.Month = raw.month; // Check month/day first before checking year/month. // The logic here is that people often uses 4 digit for years, which will be captured by GetDayOfYM(). // Therefore, we assume a number followed by a month is generally a month/day. int monthDayOrder = GetMonthDayOrder(dtfi.MonthDayPattern, dtfi); if (monthDayOrder == ORDER_DM) { result.Year = currentYear; if (IsValidDay(result, result.Year, raw.month, raw.num[0])) { result.Day = raw.num[0]; return; } } int yearMonthOrder = GetYearMonthOrder(dtfi.YearMonthPattern, dtfi); if (yearMonthOrder == ORDER_YM) { if (IsValidMonth(result, raw.num[0], raw.month)) { result.Year = raw.num[0]; result.Day = 1; return; } } // // NOTENOTE : in the case of invariant culture, // we will have an ambiguous situation when we have a string "June 11". // It is ambiguous because the month day pattern is "MMMM dd", // and year month pattern is "MMMM, yyyy". // Therefore, It could be 11-06-01 or CurrentYear-06-11. // In here, we favor CurrentYear-06-11 by checking the month/day first. // if (IsValidDay(result, currentYear, result.Month, raw.num[0])) { result.Year = currentYear; result.Day = raw.num[0]; return; } if (IsValidDay(result, raw.num[0], result.Month, 1)) { result.Year = raw.num[0]; result.Day = 1; } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } private static void GetDayOfMNN(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { int n1 = raw.num[0]; int n2 = raw.num[1]; int order = GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi); int year; if (order == ORDER_MDY) { if (IsValidDay(result, year = AdjustYear(result, n2), raw.month, n1)) { result.SetDate(year, raw.month, n1); // MDY return; } else if (IsValidDay(result, year = AdjustYear(result, n1), raw.month, n2)) { result.SetDate(year, raw.month, n2); // YMD return; } } else if (order == ORDER_YMD) { if (IsValidDay(result, year = AdjustYear(result, n1), raw.month, n2)) { result.SetDate(year, raw.month, n2); // YMD return; } else if (IsValidDay(result, year = AdjustYear(result, n2), raw.month, n1)) { result.SetDate(year, raw.month, n1); // DMY return; } } else if (order == ORDER_DMY) { if (IsValidDay(result, year = AdjustYear(result, n2), raw.month, n1)) { result.SetDate(year, raw.month, n1); // DMY return; } else if (IsValidDay(result, year = AdjustYear(result, n1), raw.month, n2)) { result.SetDate(year, raw.month, n2); // YMD return; } } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } private static void GetDayOfYNN(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { int n1 = raw.num[0]; int n2 = raw.num[1]; if (dtfi.CultureID == 0x0437) { // 0x0437 = Georgian - Georgia (ka-GE) // Very special case for ka-GE: // Its short date patten is "dd.MM.yyyy" (ORDER_DMY). // However, its long date pattern is "yyyy '/x10ec/x10da/x10d8/x10e1' dd MM, dddd" (ORDER_YDM) int order = GetYearMonthDayOrder(dtfi.LongDatePattern, dtfi); if (order == ORDER_YDM) { if (SetDateYMD(result, raw.year, n2, n1)) { return; // Year + DM } } else { if (SetDateYMD(result, raw.year, n1, n2)) { return; // Year + MD } } } else { // Otherwise, assume it is year/month/day. if (SetDateYMD(result, raw.year, n1, n2)) { return; // Year + MD } } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } private static void GetDayOfNNY(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { int n1 = raw.num[0]; int n2 = raw.num[1]; int order = GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi); if (order == ORDER_MDY || order == ORDER_YMD) { if (SetDateYMD(result, raw.year, n1, n2)) { return; // MD + Year } } else { if (SetDateYMD(result, raw.year, n2, n1)) { return; // DM + Year } } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } private static void GetDayOfYMN(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { if (SetDateYMD(result, raw.year, raw.month, raw.num[0])) { return; } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } private static void GetDayOfYN(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { if (SetDateYMD(result, raw.year, raw.num[0], 1)) { return; } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } private static void GetDayOfYM(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { if (SetDateYMD(result, raw.year, raw.month, 1)) { return; } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } private static void AdjustTimeMark(DateTimeFormatInfo dtfi, DateTimeRawInfo raw) { // Specail case for culture which uses AM as empty string. // E.g. af-ZA (0x0436) // S1159 /x0000 // S2359 nm // In this case, if we are parsing a string like "2005/09/14 12:23", we will assume this is in AM. if (raw.timeMark == -1) { if (dtfi.AMDesignator != null && dtfi.PMDesignator != null) { if (dtfi.AMDesignator.Length == 0 && dtfi.PMDesignator.Length != 0) { raw.timeMark = TM_AM; } if (dtfi.PMDesignator.Length == 0 && dtfi.AMDesignator.Length != 0) { raw.timeMark = TM_PM; } } } } // // Adjust hour according to the time mark. // private static int AdjustHour(DateTimeRawInfo raw) { int hour = raw.num[0]; if (hour < 0 || hour > 12) { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } if (raw.timeMark == TM_AM) { hour = (hour == 12) ? 0 : hour; } else { hour = (hour == 12) ? 12 : hour + 12; } return (hour); } private static void GetTimeOfN(DateTimeFormatInfo dtfi, DateTimeResult result, DateTimeRawInfo raw) { // // In this case, we need a time mark. Check if so. // if (raw.timeMark == -1) { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } AdjustTimeMark(dtfi, raw); result.Hour = AdjustHour(raw); } private static void GetTimeOfNN(DateTimeFormatInfo dtfi, DateTimeResult result, DateTimeRawInfo raw) { if (raw.numCount < 2) { throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_InternalState)); } AdjustTimeMark(dtfi, raw); result.Hour = (raw.timeMark == - 1) ? raw.num[0] : AdjustHour(raw); result.Minute = raw.num[1]; } private static void GetTimeOfNNN(DateTimeFormatInfo dtfi, DateTimeResult result, DateTimeRawInfo raw) { if (raw.numCount < 3) { throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_InternalState)); } AdjustTimeMark(dtfi, raw); result.Hour = (raw.timeMark == - 1) ? raw.num[0] : AdjustHour(raw); result.Minute = raw.num[1]; result.Second = raw.num[2]; } // // Processing terminal state: A Date suffix followed by one number. // private static void GetDateOfDSN(DateTimeResult result, DateTimeRawInfo raw) { if (raw.numCount != 1 || result.Day != -1) { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } result.Day = raw.num[0]; } private static void GetDateOfNDS(DateTimeResult result, DateTimeRawInfo raw) { if (result.Month == -1) { //Should have a month suffix throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } if (result.Year != -1) { // Aleady has a year suffix throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } result.Year = raw.num[0]; result.Day = 1; } private static void GetDateOfNNDS(DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { int order = GetYearMonthDayOrder(dtfi.ShortDatePattern, dtfi); switch (order) { case ORDER_YMD: break; case ORDER_MDY: break; case ORDER_DMY: if (result.Day == -1 && result.Year == -1) { if (IsValidDay(result, raw.num[1], result.Month, raw.num[0])) { result.Year = raw.num[1]; result.Day = raw.num[0]; } } break; } } // // A date suffix is found, use this method to put the number into the result. // private static void ProcessDateTimeSuffix(DateTimeResult result, DateTimeRawInfo raw, DateTimeToken dtok) { switch (dtok.suffix) { case SEP_YearSuff: result.Year = raw.year = dtok.num; break; case SEP_MonthSuff: result.Month= raw.month = dtok.num; break; case SEP_DaySuff: result.Day = dtok.num; break; case SEP_HourSuff: result.Hour = dtok.num; break; case SEP_MinuteSuff: result.Minute = dtok.num; break; case SEP_SecondSuff: result.Second = dtok.num; break; } } // // A terminal state has been reached, call the appropriate function to fill in the parsing result. // Return true if the state is a terminal state. // private static void ProcessTerminaltState(int dps, DateTimeResult result, DateTimeRawInfo raw, DateTimeFormatInfo dtfi) { switch (dps) { case DS_DX_NN: GetDayOfNN(result, raw, dtfi); break; case DS_DX_NNN: GetDayOfNNN(result, raw, dtfi); break; case DS_DX_MN: GetDayOfMN(result, raw, dtfi); break; case DS_DX_NM: GetDayOfNM(result, raw, dtfi); break; case DS_DX_MNN: GetDayOfMNN(result, raw, dtfi); break; case DS_DX_DS: // The result has got the correct value. No need to process. break; case DS_DX_YNN: GetDayOfYNN(result, raw, dtfi); break; case DS_DX_NNY: GetDayOfNNY(result, raw, dtfi); break; case DS_DX_YMN: GetDayOfYMN(result, raw, dtfi); break; case DS_DX_YN: GetDayOfYN(result, raw, dtfi); break; case DS_DX_YM: GetDayOfYM(result, raw, dtfi); break; case DS_TX_N: GetTimeOfN(dtfi, result, raw); break; case DS_TX_NN: GetTimeOfNN(dtfi, result, raw); break; case DS_TX_NNN: GetTimeOfNNN(dtfi, result, raw); break; case DS_TX_TS: // The result has got the correct value. No need to process. break; case DS_DX_DSN: GetDateOfDSN(result, raw); break; case DS_DX_NDS: GetDateOfNDS(result, raw); break; case DS_DX_NNDS: GetDateOfNNDS(result, raw, dtfi); break; } if (dps > DS_ERROR) { // // We have reached a terminal state. Reset the raw num count. // raw.numCount = 0; } return; } // // This is the real method to do the parsing work. // internal static DateTime Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles) { if (s == null) { throw new ArgumentNullException("s", Environment.GetResourceString("ArgumentNull_String")); } if (s.Length == 0) { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } if (dtfi == null) { dtfi = DateTimeFormatInfo.CurrentInfo; } DateTime time; // // First try the predefined format. // int dps = DS_BEGIN; // Date Parsing State. bool reachTerminalState = false; DateTimeResult result = new DateTimeResult(); // The buffer to store the parsing result. DateTimeToken dtok = new DateTimeToken(); // The buffer to store the parsing token. DateTimeRawInfo raw = new DateTimeRawInfo(); // The buffer to store temporary parsing information. result.calendar = dtfi.Calendar; // // The string to be parsed. Use a __DTString wrapper so that we can trace the index which // indicates the begining of next token. // __DTString str = new __DTString(s); str.GetNext(); // // The following loop will break out when we reach the end of the str. // do { // // Call the lexer to get the next token. // // If we find a era in Lex(), the era value will be in raw.era. Lex(dps, str, dtok, raw, result, ref dtfi); // // If the token is not unknown, process it. // Otherwise, just discard it. // if (dtok.dtt != DTT_Unk) { // // Check if we got any CJK Date/Time suffix. // Since the Date/Time suffix tells us the number belongs to year/month/day/hour/minute/second, // store the number in the appropriate field in the result. // if (dtok.suffix != SEP_Unk) { ProcessDateTimeSuffix(result, raw, dtok); dtok.suffix = SEP_Unk; // Reset suffix to SEP_Unk; } if (dps == DS_D_YN && dtok.dtt == DTT_NumLocalTimeMark) { // Consider this as ISO 8601 format: // "yyyy-MM-dd'T'HH:mm:ss" 1999-10-31T02:00:00 return (ParseISO8601(raw, str, styles)); } // // Advance to the next state, and continue // dps = dateParsingStates[dps][dtok.dtt]; if (dps == DS_ERROR) { BCLDebug.Trace("NLS", "DateTimeParse.DoParse(): dps is DS_ERROR"); throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } else if (dps > DS_ERROR) { ProcessTerminaltState(dps, result, raw, dtfi); reachTerminalState = true; // // If we have reached a terminal state, start over from DS_BEGIN again. // For example, when we parsed "1999-12-23 13:30", we will reach a terminal state at "1999-12-23", // and we start over so we can continue to parse "12:30". // dps = DS_BEGIN; } } } while (dtok.dtt != DTT_End && dtok.dtt != DTT_NumEnd && dtok.dtt != DTT_MonthEnd); if (!reachTerminalState) { BCLDebug.Trace("NLS", "DateTimeParse.DoParse(): terminal state is not reached"); throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } // Check if the parased string only contains hour/minute/second values. bool bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1); // // Check if any year/month/day is missing in the parsing string. // If yes, get the default value from today's date. // CheckDefaultDateTime(result, ref result.calendar, styles); try { if (raw.era == -1) { raw.era = Calendar.CurrentEra; } time = result.calendar.ToDateTime(result.Year, result.Month, result.Day, result.Hour, result.Minute, result.Second, 0, raw.era); if (raw.fraction > 0) { time = time.AddTicks((long)Math.Round(raw.fraction * Calendar.TicksPerSecond)); } } catch (Exception) { BCLDebug.Trace("NLS", "DateTimeParse.DoParse(): time is bad"); throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } // // NOTENOTE : // We have to check day of week before we adjust to the time zone. // Otherwise, the value of day of week may change after adjustting to the time zone. // if (raw.dayOfWeek != -1) { // // Check if day of week is correct. // if (raw.dayOfWeek != (int)result.calendar.GetDayOfWeek(time)) { BCLDebug.Trace("NLS", "DateTimeParse.DoParse(): day of week is not correct"); throw new FormatException(Environment.GetResourceString("Format_BadDayOfWeek")); } } if (result.timeZoneUsed) { time = AdjustTimeZone(time, result.timeZoneOffset, styles, bTimeOnly); } return (time); } private static DateTime AdjustTimeZone(DateTime time, TimeSpan timeZoneOffset, DateTimeStyles sytles, bool bTimeOnly) { if ((sytles & DateTimeStyles.AdjustToUniversal) != 0) { return (AdjustTimeZoneToUniversal(time, timeZoneOffset)); } return (AdjustTimeZoneToLocal(time, timeZoneOffset, bTimeOnly)); } // // Adjust the specified time to universal time based on the supplied timezone. // E.g. when parsing "2001/06/08 14:00-07:00", // the time is 2001/06/08 14:00, and timeZoneOffset = -07:00. // The result will be "2001/06/08 21:00" // private static DateTime AdjustTimeZoneToUniversal(DateTime time, TimeSpan timeZoneOffset) { long resultTicks = time.Ticks; resultTicks -= timeZoneOffset.Ticks; if (resultTicks < 0) { resultTicks += Calendar.TicksPerDay; } if (resultTicks < 0) { throw new FormatException(Environment.GetResourceString("Format_DateOutOfRange")); } return (new DateTime(resultTicks)); } // // Adjust the specified time to universal time based on the supplied timezone, // and then convert to local time. // E.g. when parsing "2001/06/08 14:00-04:00", and local timezone is GMT-7. // the time is 2001/06/08 14:00, and timeZoneOffset = -05:00. // The result will be "2001/06/08 11:00" // private static DateTime AdjustTimeZoneToLocal(DateTime time, TimeSpan timeZoneOffset, bool bTimeOnly) { long resultTicks = time.Ticks; // Convert to local ticks if (resultTicks < Calendar.TicksPerDay) { // // This is time of day. // // Adjust timezone. resultTicks -= timeZoneOffset.Ticks; // If the time is time of day, use the current timezone offset. resultTicks += TimeZone.CurrentTimeZone.GetUtcOffset(bTimeOnly ? DateTime.Now: time).Ticks; if (resultTicks < 0) { resultTicks += Calendar.TicksPerDay; } } else { // Adjust timezone to GMT. resultTicks -= timeZoneOffset.Ticks; if (resultTicks > DateTime.MaxValue.Ticks) { // If the result ticks is greater than DateTime.MaxValue, we can not create a DateTime from this ticks. // In this case, keep using the old code. // This code path is used to get around bug 78411. resultTicks += TimeZone.CurrentTimeZone.GetUtcOffset(time).Ticks; } else { // Convert the GMT time to local time. return (new DateTime(resultTicks).ToLocalTime()); } } if (resultTicks < 0) { throw new FormatException(Environment.GetResourceString("Format_DateOutOfRange")); } return (new DateTime(resultTicks)); } // // Parse the ISO8601 format string found during Parse(); // // private static DateTime ParseISO8601(DateTimeRawInfo raw, __DTString str, DateTimeStyles styles) { if (raw.year < 0 || raw.num[0] < 0 || raw.num[1] < 0) { } str.Index--; int hour, minute, second; bool timeZoneUsed = false; TimeSpan timeZoneOffset = new TimeSpan(); DateTime time = new DateTime(0); double partSecond = 0; str.SkipWhiteSpaces(); ParseDigits(str, 2, true, out hour); str.SkipWhiteSpaces(); if (str.Match(':')) { str.SkipWhiteSpaces(); ParseDigits(str, 2, true, out minute); str.SkipWhiteSpaces(); if (str.Match(':')) { str.SkipWhiteSpaces(); ParseDigits(str, 2, true, out second); str.SkipWhiteSpaces(); if (str.GetNext()) { char ch = str.GetChar(); if (ch == '+' || ch == '-') { timeZoneUsed = true; timeZoneOffset = ParseTimeZone(str, str.GetChar()); } else if (ch == '.') { str.Index++; //ParseFraction requires us to advance to the next character. partSecond = ParseFraction(str); } else if (ch == 'Z' || ch == 'z') { timeZoneUsed = true; } else { throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } } time =new DateTime(raw.year, raw.num[0], raw.num[1], hour, minute, second); time = time.AddTicks((long)Math.Round(partSecond * Calendar.TicksPerSecond)); if (timeZoneUsed) { time = AdjustTimeZone(time, timeZoneOffset, styles, false); } return time; } } throw new FormatException(Environment.GetResourceString("Format_BadDateTime")); } /*=================================ParseDigits================================== **Action: Parse the number string in __DTString that are formatted using ** the following patterns: ** "0", "00", and "000..0" **Returns: the integer value **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if error in parsing number. ==============================================================================*/ private static bool ParseDigits(__DTString str, int digitLen, bool isThrowExp, out int result) { result = 0; if (!str.GetNextDigit()) { return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } result = str.GetDigit(); if (digitLen == 1) { // When digitLen == 1, we should able to parse number like "9" and "19". However, // we won't go beyond two digits. // // So let's look ahead one character to see if it is a digit. If yes, add it to result. if (str.GetNextDigit()) { result = result * 10 + str.GetDigit(); } else { // Not a digit, let's roll back the Index. str.Index--; } } else if (digitLen == 2) { if (!str.GetNextDigit()) { return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } result = result * 10 + str.GetDigit(); } else { for (int i = 1; i < digitLen; i++) { if (!str.GetNextDigit()) { return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } result = result * 10 + str.GetDigit(); } } return (true); } /*=================================ParseFractionExact================================== **Action: Parse the number string in __DTString that are formatted using ** the following patterns: ** "0", "00", and "000..0" **Returns: the fraction value **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if error in parsing number. ==============================================================================*/ private static bool ParseFractionExact(__DTString str, int digitLen, bool isThrowExp, ref double result) { if (!str.GetNextDigit()) { return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } result = str.GetDigit(); for (int i = 1; i < digitLen; i++) { if (!str.GetNextDigit()) { return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } result = result * 10 + str.GetDigit(); } result = ((double)result / Math.Pow(10, digitLen)); return (true); } /*=================================ParseSign================================== **Action: Parse a positive or a negative sign. **Returns: true if postive sign. flase if negative sign. **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if end of string is encountered or a sign ** symbol is not found. ==============================================================================*/ private static bool ParseSign(__DTString str, bool isThrowExp, ref bool result) { if (!str.GetNext()) { // A sign symbol ('+' or '-') is expected. However, end of string is encountered. return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } char ch = str.GetChar(); if (ch == '+') { result = true; return (true); } else if (ch == '-') { result = false; return (true); } // A sign symbol ('+' or '-') is expected. return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } /*=================================ParseTimeZoneOffset================================== **Action: Parse the string formatted using "z", "zz", "zzz" in DateTime.Format(). **Returns: the TimeSpan for the parsed timezone offset. **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. ** len: the repeated number of the "z" **Exceptions: FormatException if errors in parsing. ==============================================================================*/ private static bool ParseTimeZoneOffset(__DTString str, int len, bool isThrowExp, ref TimeSpan result) { bool isPositive = true; int hourOffset; int minuteOffset = 0; switch (len) { case 1: case 2: if (!ParseSign(str, isThrowExp, ref isPositive)) { return (false); } if (!ParseDigits(str, len, isThrowExp, out hourOffset)) { return (false); } break; default: if (!ParseSign(str, isThrowExp, ref isPositive)) { return (false); } if (!ParseDigits(str, 2, isThrowExp, out hourOffset)) { return (false); } // ':' is optional. if (str.Match(":")) { // Found ':' if (!ParseDigits(str, 2, isThrowExp, out minuteOffset)) { return (false); } } else { // Since we can not match ':', put the char back. str.Index--; if (!ParseDigits(str, 2, isThrowExp, out minuteOffset)) { return (false); } } break; } result = (new TimeSpan(hourOffset, minuteOffset, 0)); if (!isPositive) { result = result.Negate(); } return (true); } /*=================================MatchAbbreviatedMonthName================================== **Action: Parse the abbreviated month name from string starting at str.Index. **Returns: A value from 1 to 12 for the first month to the twelveth month. **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if an abbreviated month name can not be found. ==============================================================================*/ private static bool MatchAbbreviatedMonthName(__DTString str, DateTimeFormatInfo dtfi, bool isThrowExp, ref int result) { int maxMatchStrLen = 0; result = -1; if (str.GetNext()) { // // Scan the month names (note that some calendars has 13 months) and find // the matching month name which has the max string length. // We need to do this because some cultures (e.g. "cs-CZ") which have // abbreviated month names with the same prefix. // int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12: 13); for (int i = 1; i <= monthsInYear; i++) { String searchStr = dtfi.GetAbbreviatedMonthName(i); if (str.MatchSpecifiedWord(searchStr)) { int matchStrLen = searchStr.Length; if (matchStrLen > maxMatchStrLen) { maxMatchStrLen = matchStrLen; result = i; } } } } if (result > 0) { str.Index += (maxMatchStrLen - 1); return (true); } return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } /*=================================MatchMonthName================================== **Action: Parse the month name from string starting at str.Index. **Returns: A value from 1 to 12 indicating the first month to the twelveth month. **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if a month name can not be found. ==============================================================================*/ private static bool MatchMonthName(__DTString str, DateTimeFormatInfo dtfi, bool isThrowExp, ref int result) { int maxMatchStrLen = 0; result = -1; if (str.GetNext()) { // // Scan the month names (note that some calendars has 13 months) and find // the matching month name which has the max string length. // We need to do this because some cultures (e.g. "vi-VN") which have // month names with the same prefix. // int monthsInYear = (dtfi.GetMonthName(13).Length == 0 ? 12: 13); for (int i = 1; i <= monthsInYear; i++) { String searchStr = dtfi.GetMonthName(i); if (str.MatchSpecifiedWord(searchStr)) { int matchStrLen = (searchStr.Length - 1); if (matchStrLen > maxMatchStrLen) { maxMatchStrLen = matchStrLen; result = i; } } } } if (result > 0) { str.Index += maxMatchStrLen; return (true); } return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } /*=================================MatchAbbreviatedDayName================================== **Action: Parse the abbreviated day of week name from string starting at str.Index. **Returns: A value from 0 to 6 indicating Sunday to Saturday. **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if a abbreviated day of week name can not be found. ==============================================================================*/ private static bool MatchAbbreviatedDayName(__DTString str, DateTimeFormatInfo dtfi, bool isThrowExp, ref int result) { if (str.GetNext()) { for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) { String searchStr = dtfi.GetAbbreviatedDayName(i); if (str.MatchSpecifiedWord(searchStr)) { str.Index += (searchStr.Length - 1); result = (int)i; return (true); } } } return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } /*=================================MatchDayName================================== **Action: Parse the day of week name from string starting at str.Index. **Returns: A value from 0 to 6 indicating Sunday to Saturday. **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if a day of week name can not be found. ==============================================================================*/ private static bool MatchDayName(__DTString str, DateTimeFormatInfo dtfi, bool isThrowExp, ref int result) { // Turkish (tr-TR) got day names with the same prefix. int maxMatchStrLen = 0; result = -1; if (str.GetNext()) { for (DayOfWeek i = DayOfWeek.Sunday; i <= DayOfWeek.Saturday; i++) { String searchStr = dtfi.GetDayName(i); if (str.MatchSpecifiedWord(searchStr)) { int matchStrLen = (searchStr.Length - 1); if (matchStrLen > maxMatchStrLen) { maxMatchStrLen = matchStrLen; result = (int)i; } } } } if (result >= 0) { str.Index += maxMatchStrLen; return (true); } return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } /*=================================MatchEraName================================== **Action: Parse era name from string starting at str.Index. **Returns: An era value. **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if an era name can not be found. ==============================================================================*/ private static bool MatchEraName(__DTString str, DateTimeFormatInfo dtfi, bool isThrowExp, ref int result) { if (str.GetNext()) { int[] eras = dtfi.Calendar.Eras; if (eras != null) { for (int i = 0; i <= eras.Length; i++) { String searchStr = dtfi.GetEraName(eras[i]); if (str.MatchSpecifiedWord(searchStr)) { str.Index += (searchStr.Length - 1); result = eras[i]; return (true); } searchStr = dtfi.GetAbbreviatedEraName(eras[i]); if (str.MatchSpecifiedWord(searchStr)) { str.Index += (searchStr.Length - 1); result = eras[i]; return (true); } } } } return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } /*=================================MatchTimeMark================================== **Action: Parse the time mark (AM/PM) from string starting at str.Index. **Returns: TM_AM or TM_PM. **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if a time mark can not be found. ==============================================================================*/ private static bool MatchTimeMark(__DTString str, DateTimeFormatInfo dtfi, bool isThrowExp, ref int result) { result = -1; // In some cultures have empty strings in AM/PM mark. E.g. af-ZA (0x0436), the AM mark is "", and PM mark is "nm". if (dtfi.AMDesignator.Length == 0) { result = TM_AM; } if (dtfi.PMDesignator.Length == 0) { result = TM_PM; } if (str.GetNext()) { String searchStr = dtfi.AMDesignator; if (searchStr.Length > 0) { if (str.MatchSpecifiedWord(searchStr)) { // Found an AM timemark with length > 0. str.Index += (searchStr.Length - 1); result = TM_AM; return (true); } } searchStr = dtfi.PMDesignator; if (searchStr.Length > 0) { if (str.MatchSpecifiedWord(searchStr)) { // Found a PM timemark with length > 0. str.Index += (searchStr.Length - 1); result = TM_PM; return (true); } } // If we can not match the time mark strings with length > 0, // just return the return (true); } if (result != -1) { // If one of the AM/PM marks is empty string, return the result. return (true); } return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } /*=================================MatchAbbreviatedTimeMark================================== **Action: Parse the abbreviated time mark (AM/PM) from string starting at str.Index. **Returns: TM_AM or TM_PM. **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if a abbreviated time mark can not be found. ==============================================================================*/ private static bool MatchAbbreviatedTimeMark(__DTString str, DateTimeFormatInfo dtfi, bool isThrowExp, ref int result) { // NOTENOTE : the assumption here is that abbreviated time mark is the first // character of the AM/PM designator. If this invariant changes, we have to // change the code below. if (str.GetNext()) { if (str.GetChar() == dtfi.AMDesignator[0]) { result = TM_AM; return (true); } if (str.GetChar() == dtfi.PMDesignator[0]) { result = TM_PM; return (true); } } return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } /*=================================CheckNewValue================================== **Action: Check if currentValue is initialized. If not, return the newValue. ** If yes, check if the current value is equal to newValue. Throw ArgumentException ** if they are not equal. This is used to check the case like "d" and "dd" are both ** used to format a string. **Returns: the correct value for currentValue. **Arguments: **Exceptions: ==============================================================================*/ private static bool CheckNewValue(ref int currentValue, int newValue, char patternChar, bool isThrowExp) { if (currentValue == -1) { currentValue = newValue; return (true); } else { if (newValue != currentValue) { BCLDebug.Trace("NLS", "DateTimeParse.CheckNewValue() : ", patternChar, " is repeated"); if (isThrowExp) { throw new ArgumentException( String.Format(Environment.GetResourceString("Format_RepeatDateTimePattern"), patternChar), "format"); } return (false); } } return (true); } private static void CheckDefaultDateTime(DateTimeResult result, ref Calendar cal, DateTimeStyles styles) { if ((result.Year == -1) || (result.Month == -1) || (result.Day == -1)) { /* The following table describes the behaviors of getting the default value when a certain year/month/day values are missing. An "X" means that the value exists. And "--" means that value is missing. Year Month Day => ResultYear ResultMonth ResultDay Note X X X Parsed year Parsed month Parsed day X X -- Parsed Year Parsed month First day If we have year and month, assume the first day of that month. X -- X Parsed year First month Parsed day If the month is missing, assume first month of that year. X -- -- Parsed year First month First day If we have only the year, assume the first day of that year. -- X X CurrentYear Parsed month Parsed day If the year is missing, assume the current year. -- X -- CurrentYear Parsed month First day If we have only a month value, assume the current year and current day. -- -- X CurrentYear First month Parsed day If we have only a day value, assume current year and first month. -- -- -- CurrentYear Current month Current day So this means that if the date string only contains time, you will get current date. */ DateTime now = DateTime.Now; if (result.Month == -1 && result.Day == -1) { if (result.Year == -1) { if ((styles & DateTimeStyles.NoCurrentDateDefault) != 0) { // If there is no year/month/day values, and NoCurrentDateDefault flag is used, // set the year/month/day value to the beginning year/month/day of DateTime(). // Note we should be using Gregorian for the year/month/day. cal = GregorianCalendar.GetDefaultInstance(); result.Year = result.Month = result.Day = 1; } else { // Year/Month/Day are all missing. result.Year = cal.GetYear(now); result.Month = cal.GetMonth(now); result.Day = cal.GetDayOfMonth(now); } } else { // Month/Day are both missing. result.Month = 1; result.Day = 1; } } else { if (result.Year == -1) { result.Year = cal.GetYear(now); } if (result.Month == -1) { result.Month = 1; } if (result.Day == -1) { result.Day = 1; } } } // Set Hour/Minute/Second to zero if these value are not in str. if (result.Hour == -1) result.Hour = 0; if (result.Minute == -1) result.Minute = 0; if (result.Second == -1) result.Second = 0; if (result.era == -1) result.era = Calendar.CurrentEra; } // Expand a pre-defined format string (like "D" for long date) to the real format that // we are going to use in the date time parsing. // This method also set the dtfi according/parseInfo to some special pre-defined // formats. // private static String ExpandPredefinedFormat(String format, ref DateTimeFormatInfo dtfi, ParsingInfo parseInfo) { // // Check the format to see if we need to override the dtfi to be InvariantInfo, // and see if we need to set up the userUniversalTime flag. // switch (format[0]) { case 'r': case 'R': // RFC 1123 Standard. (in Universal time) parseInfo.calendar = GregorianCalendar.GetDefaultInstance(); dtfi = DateTimeFormatInfo.InvariantInfo; break; case 's': // Sortable format (in local time) dtfi = DateTimeFormatInfo.InvariantInfo; parseInfo.calendar = GregorianCalendar.GetDefaultInstance(); break; case 'u': // Universal time format in sortable format. parseInfo.calendar = GregorianCalendar.GetDefaultInstance(); dtfi = DateTimeFormatInfo.InvariantInfo; break; case 'U': // Universal time format with culture-dependent format. parseInfo.calendar = GregorianCalendar.GetDefaultInstance(); parseInfo.fUseUniversalTime = true; if (dtfi.Calendar.GetType() != typeof(GregorianCalendar)) { dtfi = (DateTimeFormatInfo)dtfi.Clone(); dtfi.Calendar = GregorianCalendar.GetDefaultInstance(); } break; } // // Expand the pre-defined format character to the real format from DateTimeFormatInfo. // return (DateTimeFormat.GetRealFormat(format, dtfi)); } // Given a specified format character, parse and update the parsing result. // private static bool ParseByFormat( __DTString str, __DTString format, ParsingInfo parseInfo, DateTimeFormatInfo dtfi, bool isThrowExp, DateTimeResult result) { int tokenLen = 0; int tempYear = 0, tempMonth = 0, tempDay = 0, tempDayOfWeek = 0, tempHour = 0, tempMinute = 0, tempSecond = 0; double tempFraction = 0; int tempTimeMark = 0; char ch = format.GetChar(); switch (ch) { case 'y': tokenLen = format.GetRepeatCount(); if (tokenLen <= 2) { parseInfo.fUseTwoDigitYear = true; } if (!ParseDigits(str, tokenLen, isThrowExp, out tempYear)) { return (false); } if (!CheckNewValue(ref result.Year, tempYear, ch, isThrowExp)) { return (false); } break; case 'M': tokenLen = format.GetRepeatCount(); if (tokenLen <= 2) { if (!ParseDigits(str, tokenLen, isThrowExp, out tempMonth)) { return (false); } } else { if (tokenLen == 3) { if (!MatchAbbreviatedMonthName(str, dtfi, isThrowExp, ref tempMonth)) { return (false); } } else { if (!MatchMonthName(str, dtfi, isThrowExp, ref tempMonth)) { return (false); } } } if (!CheckNewValue(ref result.Month, tempMonth, ch, isThrowExp)) { return (false); } break; case 'd': // Day & Day of week tokenLen = format.GetRepeatCount(); if (tokenLen <= 2) { // "d" & "dd" if (!ParseDigits(str, tokenLen, isThrowExp, out tempDay)) { return (false); } if (!CheckNewValue(ref result.Day, tempDay, ch, isThrowExp)) { return (false); } } else { if (tokenLen == 3) { // "ddd" if (!MatchAbbreviatedDayName(str, dtfi, isThrowExp, ref tempDayOfWeek)) { return (false); } } else { // "dddd*" if (!MatchDayName(str, dtfi, isThrowExp, ref tempDayOfWeek)) { return (false); } } if (!CheckNewValue(ref parseInfo.dayOfWeek, tempDayOfWeek, ch, isThrowExp)) { return (false); } } break; case 'g': tokenLen = format.GetRepeatCount(); // Put the era value in result.era. if (!MatchEraName(str, dtfi, isThrowExp, ref result.era)) { return (false); } break; case 'h': parseInfo.fUseHour12 = true; tokenLen = format.GetRepeatCount(); if (!ParseDigits(str, (tokenLen < 2? 1 : 2), isThrowExp, out tempHour)) { return (false); } if (!CheckNewValue(ref result.Hour, tempHour, ch, isThrowExp)) { return (false); } break; case 'H': tokenLen = format.GetRepeatCount(); if (!ParseDigits(str, (tokenLen < 2? 1 : 2), isThrowExp, out tempHour)) { return (false); } if (!CheckNewValue(ref result.Hour, tempHour, ch, isThrowExp)) { return (false); } break; case 'm': tokenLen = format.GetRepeatCount(); if (!ParseDigits(str, (tokenLen < 2? 1 : 2), isThrowExp, out tempMinute)) { return (false); } if (!CheckNewValue(ref result.Minute, tempMinute, ch, isThrowExp)) { return (false); } break; case 's': tokenLen = format.GetRepeatCount(); if (!ParseDigits(str, (tokenLen < 2? 1 : 2), isThrowExp, out tempSecond)) { return (false); } if (!CheckNewValue(ref result.Second, tempSecond, ch, isThrowExp)) { return (false); } break; case 'f': tokenLen = format.GetRepeatCount(); if (tokenLen <= DateTimeFormat.MaxSecondsFractionDigits) { if (!ParseFractionExact(str, tokenLen, isThrowExp, ref tempFraction)) { return (false); } if (result.fraction < 0) { result.fraction = tempFraction; } else { if (tempFraction != result.fraction) { if (isThrowExp) { throw new ArgumentException( String.Format(Environment.GetResourceString("Format_RepeatDateTimePattern"), ch), "str"); } else { return (false); } } } } else { return ParseFormatError(isThrowExp, "Format_BadDateTime"); } break; case 't': // AM/PM designator tokenLen = format.GetRepeatCount(); if (tokenLen == 1) { if (!MatchAbbreviatedTimeMark(str, dtfi, isThrowExp, ref tempTimeMark)) { return (false); } } else { if (!MatchTimeMark(str, dtfi, isThrowExp, ref tempTimeMark)) { return (false); } } if (!CheckNewValue(ref parseInfo.timeMark, tempTimeMark, ch, isThrowExp)) { return (false); } break; case 'z': // timezone offset if (parseInfo.fUseTimeZone) { throw new ArgumentException(Environment.GetResourceString("Argument_TwoTimeZoneSpecifiers"), "str"); } parseInfo.fUseTimeZone = true; tokenLen = format.GetRepeatCount(); if (!ParseTimeZoneOffset(str, tokenLen, isThrowExp, ref parseInfo.timeZoneOffset)) { return (false); } break; case 'Z': if (parseInfo.fUseTimeZone) { throw new ArgumentException(Environment.GetResourceString("Argument_TwoTimeZoneSpecifiers"), "str"); } parseInfo.fUseTimeZone = true; parseInfo.timeZoneOffset = new TimeSpan(0); str.Index++; if (!GetTimeZoneName(str)) { BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): 'Z' or 'GMT' are expected"); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } break; case ':': if (!str.Match(dtfi.TimeSeparator)) { // A time separator is expected. BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): ':' is expected"); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } break; case '/': if (!str.Match(dtfi.DateSeparator)) { // A date separator is expected. BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): date separator is expected"); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } break; case '/"': case '/'': StringBuilder enquotedString = new StringBuilder(); try { // Use ParseQuoteString so that we can handle escape characters within the quoted string. tokenLen = DateTimeFormat.ParseQuoteString(format.Value, format.Index, enquotedString); } catch (Exception) { if (isThrowExp) { throw new FormatException(String.Format(Environment.GetResourceString("Format_BadQuote"), ch)); } else { return (false); } } format.Index += tokenLen - 1; // Some cultures uses space in the quoted string. E.g. Spanish has long date format as: // "dddd, dd' de 'MMMM' de 'yyyy". When inner spaces flag is set, we should skip whitespaces if there is space // in the quoted string. String quotedStr = enquotedString.ToString(); for (int i = 0; i < quotedStr.Length; i++) { if (quotedStr[i] == ' ' && parseInfo.fAllowInnerWhite) { str.SkipWhiteSpaces(); } else if (!str.Match(quotedStr[i])) { // Can not find the matching quoted string. BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse():Quote string doesn't match"); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } } break; case '%': // Skip this so we can get to the next pattern character. // Used in case like "%d", "%y" // Make sure the next character is not a '%' again. if (format.Index >= format.Value.Length - 1 || format.Value[format.Index + 1] == '%') { BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse():%% is not permitted"); return (ParseFormatError(isThrowExp, "Format_BadFormatSpecifier")); } break; case '//': // Escape character. For example, "/d". // Get the next character in format, and see if we can // find a match in str. if (format.GetNext()) { if (!str.Match(format.GetChar())) { // Can not find a match for the escaped character. BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): Can not find a match for the escaped character"); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } } else { BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): // is at the end of the format string"); return (ParseFormatError(isThrowExp, "Format_BadFormatSpecifier")); } break; default: if (ch == ' ') { if (parseInfo.fAllowInnerWhite) { // Skip whitespaces if AllowInnerWhite. // Do nothing here. } else { if (!str.Match(ch)) { // If the space does not match, and trailing space is allowed, we do // one more step to see if the next format character can lead to // successful parsing. // This is used to deal with special case that a empty string can match // a specific pattern. // The example here is af-ZA, which has a time format like "hh:mm:ss tt". However, // its AM symbol is "" (empty string). If fAllowTrailingWhite is used, and time is in // the AM, we will trim the whitespaces at the end, which will lead to a failure // when we are trying to match the space before "tt". if (parseInfo.fAllowTrailingWhite) { if (format.GetNext()) { if (ParseByFormat(str, format, parseInfo, dtfi, isThrowExp, result)) { return (true); } } } return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } // Found a macth. } } else { if (format.MatchSpecifiedWord(GMTName)) { format.Index += (GMTName.Length - 1); // Found GMT string in format. This means the DateTime string // is in GMT timezone. parseInfo.fUseTimeZone = true; if (!str.Match(GMTName)) { BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): GMT in format, but not in str"); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } } else if (!str.Match(ch)) { // ch is expected. BCLDebug.Trace ("NLS", "DateTimeParse.DoStrictParse(): '", ch, "' is expected"); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } } break; } // switch return (true); } // A very small utility method to either return false or throw format exception according to the flag. private static bool ParseFormatError(bool isThrowException, String resourceID) { if (isThrowException) { throw new FormatException(Environment.GetResourceString(resourceID)); } return (false); } /*=================================DoStrictParse================================== **Action: Do DateTime parsing using the format in formatParam. **Returns: The parsed DateTime. **Arguments: **Exceptions: ** **Notes: ** When the following general formats are used, InvariantInfo is used in dtfi: ** 'r', 'R', 's'. ** When the following general formats are used, the time is assumed to be in Universal time. ** **Limitations: ** Only GregarianCalendar is supported for now. ** Only support GMT timezone. ==============================================================================*/ private static bool DoStrictParse( String s, String formatParam, DateTimeStyles styles, DateTimeFormatInfo dtfi, bool isThrowExp, out DateTime returnValue) { bool bTimeOnly = false; returnValue = new DateTime(); ParsingInfo parseInfo = new ParsingInfo(); parseInfo.calendar = dtfi.Calendar; parseInfo.fAllowInnerWhite = ((styles & DateTimeStyles.AllowInnerWhite) != 0); parseInfo.fAllowTrailingWhite = ((styles & DateTimeStyles.AllowTrailingWhite) != 0); if (formatParam.Length == 1) { formatParam = ExpandPredefinedFormat(formatParam, ref dtfi, parseInfo); } DateTimeResult result = new DateTimeResult(); // Reset these values to negative one so that we could throw exception // if we have parsed every item twice. result.Hour = result.Minute = result.Second = -1; __DTString format = new __DTString(formatParam); __DTString str = new __DTString(s); if (parseInfo.fAllowTrailingWhite) { // Trim trailing spaces if AllowTrailingWhite. format.TrimTail(); format.RemoveTrailingInQuoteSpaces(); str.TrimTail(); } if ((styles & DateTimeStyles.AllowLeadingWhite) != 0) { format.SkipWhiteSpaces(); format.RemoveLeadingInQuoteSpaces(); str.SkipWhiteSpaces(); } // // Scan every character in format and match the pattern in str. // while (format.GetNext()) { // We trim inner spaces here, so that we will not eat trailing spaces when // AllowTrailingWhite is not used. if (parseInfo.fAllowInnerWhite) { str.SkipWhiteSpaces(); } if (!ParseByFormat(str, format, parseInfo, dtfi, isThrowExp, result) && !isThrowExp) { return (false); } } if (str.Index < str.Value.Length - 1) { // There are still remaining character in str. BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): Still characters in str, str.Index = ", str.Index); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } if (parseInfo.fUseTwoDigitYear) { // A two digit year value is expected. Check if the parsed year value is valid. if (result.Year >= 100) { BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): Invalid value for two-digit year"); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } result.Year = parseInfo.calendar.ToFourDigitYear(result.Year); } if (parseInfo.fUseHour12) { if (parseInfo.timeMark == -1) { // hh is used, but no AM/PM designator is specified. // Assume the time is AM. // Don't throw exceptions in here becasue it is very confusing for people. // I always got confused myself when I use "hh:mm:ss" to parse a time string, // and ParseExact() throws on me (because I didn't use the 24-hour clock 'HH'). parseInfo.timeMark = TM_AM; BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): hh is used, but no AM/PM designator is specified."); } if (result.Hour > 12) { // AM/PM is used, but the value for HH is too big. BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): AM/PM is used, but the value for HH is too big."); return (ParseFormatError(isThrowExp, "Format_BadDateTime")); } if (parseInfo.timeMark == TM_AM) { if (result.Hour == 12) { result.Hour = 0; } } else { result.Hour = (result.Hour == 12) ? 12 : result.Hour + 12; } } // Check if the parased string only contains hour/minute/second values. bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1); CheckDefaultDateTime(result, ref parseInfo.calendar, styles); try { returnValue = parseInfo.calendar.ToDateTime(result.Year, result.Month, result.Day, result.Hour, result.Minute, result.Second, 0, result.era); if (result.fraction > 0) { returnValue = returnValue.AddTicks((long)Math.Round(result.fraction * Calendar.TicksPerSecond)); } } catch (ArgumentOutOfRangeException) { return (ParseFormatError(isThrowExp, "Format_DateOutOfRange")); } catch (Exception exp) { if (isThrowExp) { throw exp; } else { return (false); } } // // NOTENOTE : // We have to check day of week before we adjust to the time zone. // It is because the value of day of week may change after adjusting // to the time zone. // if (parseInfo.dayOfWeek != -1) { // // Check if day of week is correct. // if (parseInfo.dayOfWeek != (int)parseInfo.calendar.GetDayOfWeek(returnValue)) { BCLDebug.Trace("NLS", "DateTimeParse.DoStrictParse(): day of week is not correct"); return (ParseFormatError(isThrowExp, "Format_BadDayOfWeek")); } } if (parseInfo.fUseTimeZone) { if ((styles & DateTimeStyles.AdjustToUniversal) != 0) { returnValue = AdjustTimeZoneToUniversal(returnValue, parseInfo.timeZoneOffset); } else { returnValue = AdjustTimeZoneToLocal(returnValue, parseInfo.timeZoneOffset, bTimeOnly); } } else if (parseInfo.fUseUniversalTime) { try { returnValue = returnValue.ToLocalTime(); } catch (ArgumentOutOfRangeException) { return (ParseFormatError(isThrowExp, "Format_DateOutOfRange")); } } return (true); } // This method should never be called. Its sole purpose is to shut up the compiler // because it warns about private fields that are never used. Most of these fields // are used in unmanaged code. #if _DEBUG internal String[] NeverCallThis() { BCLDebug.Assert(false,"NeverCallThis"); String[] i = invariantMonthNames; i = invariantAbbrevMonthNames; i = invariantDayNames; return invariantAbbrevDayNames; } #endif } // // This is a string parsing helper which wraps a String object. // It has a Index property which tracks // the current parsing pointer of the string. // [Serializable()] internal class __DTString { // // Value propery: stores the real string to be parsed. // internal String Value; // // Index property: points to the character that we are currently parsing. // internal int Index = -1; // The length of Value string. internal int len = 0; private CompareInfo m_info; internal __DTString() { Value = ""; } internal __DTString(String str) { Value = str; len = Value.Length; m_info = Thread.CurrentThread.CurrentCulture.CompareInfo; } internal CompareInfo CompareInfo { get { return m_info; } } // // Advance the Index. // Return true if Index is NOT at the end of the string. // // Typical usage: // while (str.GetNext()) // { // char ch = str.GetChar() // } internal bool GetNext() { Index++; return (Index < len); } // // Return the word starting from the current index. // Index will not be updated. // internal int FindEndOfCurrentWord() { int i = Index; while (i < len) { if (Value[i] == ' ' || Value[i] == ',' || Value[i] == '/'' || Char.IsDigit(Value[i])) { break; } i++; } return i; } internal String PeekCurrentWord() { int endIndex = FindEndOfCurrentWord(); return Value.Substring(Index, (endIndex - Index)); } internal bool MatchSpecifiedWord(String target) { return MatchSpecifiedWord(target, target.Length + Index); } internal bool MatchSpecifiedWord(String target, int endIndex) { int count = endIndex - Index; if (count != target.Length) { return false; } if (Index + count > len) { return false; } return (m_info.Compare(Value, Index, count, target, 0, count, CompareOptions.IgnoreCase)==0); } internal bool StartsWith(String target, bool checkWordBoundary) { if (target.Length > (Value.Length - Index)) { return false; } if (m_info.Compare(Value, Index, target.Length, target, 0, target.Length, CompareOptions.IgnoreCase)!=0) { return (false); } if (checkWordBoundary) { int nextCharIndex = Index + target.Length; if (nextCharIndex < Value.Length) { if (Char.IsLetter(Value[nextCharIndex])) { return (false); } } } return (true); } // // Check to see if the string starting from Index is a prefix of // str. // If a match is found, true value is returned and Index is updated to the next character to be parsed. // Otherwise, Index is unchanged. // internal bool Match(String str) { if (++Index >= len) { return (false); } if (str.Length > (Value.Length - Index)) { return false; } if (m_info.Compare(Value, Index, str.Length, str, 0, str.Length, CompareOptions.Ordinal)==0) { // Update the Index to the end of the matching string. // So the following GetNext()/Match() opeartion will get // the next character to be parsed. Index += (str.Length - 1); return (true); } return (false); } internal bool Match(char ch) { if (++Index >= len) { return (false); } if (Value[Index] == ch) { return (true); } return (false); } // // Trying to match an array of words. // Return true when one of the word in the array matching with substring // starting from the current index. // If the words array is null, also return true, assuming that there is a match. // internal bool MatchWords(String[] words) { if (words == null) { return (true); } if (Index >= len) { return (false); } for (int i = 0; i < words.Length; i++) { if (words[i].Length <= (Value.Length - Index)) { if (m_info.Compare( Value, Index, words[i].Length, words[i], 0, words[i].Length, CompareOptions.IgnoreCase)==0) { Index += words[i].Length; return (true); } } } return (false); } // // Get the number of repeat character after the current character. // For a string "hh:mm:ss" at Index of 3. GetRepeatCount() = 2, and Index // will point to the second ':'. // internal int GetRepeatCount() { char repeatChar = Value[Index]; int pos = Index + 1; while ((pos < len) && (Value[pos] == repeatChar)) { pos++; } int repeatCount = (pos - Index); // Update the Index to the end of the repeated characters. // So the following GetNext() opeartion will get // the next character to be parsed. Index = pos - 1; return (repeatCount); } // Return false when end of string is encountered or a non-digit character is found. internal bool GetNextDigit() { if (++Index >= len) { return (false); } return (DateTimeParse.IsDigit(Value[Index])); } // Return null when end of string is encountered or a matching quote character is not found. // Throws FormatException if the matching quote character can not be found. internal String GetQuotedString(char quoteChar) { // When we enter this method, Index points to the first quote character. int oldPos = ++Index; while ((Index < len) && (Value[Index] != quoteChar)) { Index++; } if (Index == len) { // If we move past len, it means a matching quote character is not found. // 'ABC' 'ABC // 01234 0123 return (null); } // When we leave this method, Index points to the matching quote character. return (Value.Substring(oldPos, Index - oldPos)); } // // Get the current character. // internal char GetChar() { BCLDebug.Assert(Index >= 0 && Index < len, "Index >= 0 && Index < len"); return (Value[Index]); } // // Convert the current character to a digit, and return it. // internal int GetDigit() { BCLDebug.Assert(Index >= 0 && Index < len, "Index >= 0 && Index < len"); BCLDebug.Assert(DateTimeParse.IsDigit(Value[Index]), "IsDigit(Value[Index])"); return (Value[Index] - '0'); } // // Enjoy eating white spaces. // // Return false if end of string is encountered. // internal void SkipWhiteSpaces() { // Look ahead to see if the next character // is a whitespace. while (Index+1 < len) { char ch = Value[Index+1]; if (!Char.IsWhiteSpace(ch)) { return; } Index++; } return; } // // Enjoy eating white spaces and commas. // // Return false if end of string is encountered. // internal bool SkipWhiteSpaceComma() { char ch; if (Index >= len) { return (false); } if (!Char.IsWhiteSpace(ch=Value[Index]) && ch!=',') { return (true); } while (++Index < len) { ch = Value[Index]; if (!Char.IsWhiteSpace(ch) && ch != ',') { return (true); } // Nothing here. } return (false); } internal void TrimTail() { int i = len - 1; while (i >= 0 && Char.IsWhiteSpace(Value[i])) { i--; } Value = Value.Substring(0, i + 1); len = Value.Length; } // Trim the trailing spaces within a quoted string. // Call this after TrimTail() is done. internal void RemoveTrailingInQuoteSpaces() { int i = len - 1; if (i <= 1) { return; } char ch = Value[i]; // Check if the last character is a quote. if (ch == '/'' || ch == '/"') { if (Char.IsWhiteSpace(Value[i-1])) { i--; while (i >= 1 && Char.IsWhiteSpace(Value[i-1])) { i--; } Value = Value.Remove(i, Value.Length - 1 - i); len = Value.Length; } } } // Trim the leading spaces within a quoted string. // Call this after the leading spaces before quoted string are trimmed. internal void RemoveLeadingInQuoteSpaces() { if (len <= 2) { return; } int i = 0; char ch = Value[i]; // Check if the last character is a quote. if (ch == '/'' || ch == '/"') { while ((i + 1) < len && Char.IsWhiteSpace(Value[i+1])) { i++; } if (i != 0) { Value = Value.Remove(1, i); len = Value.Length; } } } } // // The buffer to store the parsing token. // [Serializable()] internal class DateTimeToken { internal int dtt; // Store the token internal int suffix; // Store the CJK Year/Month/Day suffix (if any) internal int num; // Store the number that we are parsing (if any) } // // The buffer to store temporary parsing information. // [Serializable()] internal class DateTimeRawInfo { internal int[] num; internal int numCount = 0; internal int month = -1; internal int year = -1; internal int dayOfWeek = -1; internal int era = -1; internal int timeMark = -1; // Value could be -1, TM_AM or TM_PM. internal double fraction = -1; // // internal bool timeZone = false; internal DateTimeRawInfo() { num = new int[] {-1, -1, -1}; } } // // This will store the result of the parsing. And it will be eventually // used to construct a DateTime instance. // [Serializable()] internal class DateTimeResult { internal int Year = -1; internal int Month = -1; internal int Day = -1; // // Set time defualt to 00:00:00. // internal int Hour = 0; internal int Minute = 0; internal int Second = 0; internal double fraction = -1; internal int era = -1; internal bool timeZoneUsed = false; internal TimeSpan timeZoneOffset; internal Calendar calendar; internal DateTimeResult() { } internal virtual void SetDate(int year, int month, int day) { Year = year; Month = month; Day = day; } } [Serializable] internal class ParsingInfo { internal Calendar calendar; internal int dayOfWeek = -1; internal int timeMark = -1; internal TimeSpan timeZoneOffset = new TimeSpan(); internal bool fUseUniversalTime = false; internal bool fUseHour12 = false; internal bool fUseTwoDigitYear = false; internal bool fUseTimeZone = false; internal bool fAllowInnerWhite = false; internal bool fAllowTrailingWhite = false; internal ParsingInfo() { } } }
System.Directory___DTString
最新推荐文章于 2022-07-12 11:15:21 发布