< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 154
Coverable lines: 154
Total lines: 659
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 198
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfoScanner.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Text;
 6
 7namespace System.Globalization
 8{
 9    // from LocaleEx.txt header
 10    // IFORMATFLAGS
 11    internal enum FORMATFLAGS
 12    {
 13        None = 0x00000000,
 14        UseGenitiveMonth = 0x00000001,
 15        UseLeapYearMonth = 0x00000002,
 16        UseSpacesInMonthNames = 0x00000004,
 17        UseHebrewParsing = 0x00000008,
 18        UseSpacesInDayNames = 0x00000010,   // Has spaces or non-breaking space in the day names.
 19        UseDigitPrefixInTokens = 0x00000020,   // Has token starting with numbers.
 20    }
 21
 22    internal enum CalendarId : ushort
 23    {
 24        UNINITIALIZED_VALUE = 0,
 25        GREGORIAN = 1,     // Gregorian (localized) calendar
 26        GREGORIAN_US = 2,     // Gregorian (U.S.) calendar
 27        JAPAN = 3,     // Japanese Emperor Era calendar
 28                       /* SSS_WARNINGS_OFF */
 29        TAIWAN = 4,     // Taiwan Era calendar /* SSS_WARNINGS_ON */
 30        KOREA = 5,     // Korean Tangun Era calendar
 31        HIJRI = 6,     // Hijri (Arabic Lunar) calendar
 32        THAI = 7,     // Thai calendar
 33        HEBREW = 8,     // Hebrew (Lunar) calendar
 34        GREGORIAN_ME_FRENCH = 9,     // Gregorian Middle East French calendar
 35        GREGORIAN_ARABIC = 10,     // Gregorian Arabic calendar
 36        GREGORIAN_XLIT_ENGLISH = 11,     // Gregorian Transliterated English calendar
 37        GREGORIAN_XLIT_FRENCH = 12,
 38        // Note that all calendars after this point are MANAGED ONLY for now.
 39        JULIAN = 13,
 40        JAPANESELUNISOLAR = 14,
 41        CHINESELUNISOLAR = 15,
 42        SAKA = 16,     // reserved to match Office but not implemented in our code
 43        LUNAR_ETO_CHN = 17,     // reserved to match Office but not implemented in our code
 44        LUNAR_ETO_KOR = 18,     // reserved to match Office but not implemented in our code
 45        LUNAR_ETO_ROKUYOU = 19,     // reserved to match Office but not implemented in our code
 46        KOREANLUNISOLAR = 20,
 47        TAIWANLUNISOLAR = 21,
 48        PERSIAN = 22,
 49        UMALQURA = 23,
 50        LAST_CALENDAR = 23      // Last calendar ID
 51    }
 52
 53    /// <summary>
 54    /// Scans a specified DateTimeFormatInfo to search for data used in DateTime.Parse().
 55    ///
 56    /// The data includes:
 57    /// DateWords: such as "de" used in es-ES (Spanish) LongDatePattern.
 58    /// Postfix: such as "ta" used in fi-FI after the month name.
 59    /// </summary>
 60    internal sealed class DateTimeFormatInfoScanner
 61    {
 62        // Special prefix-like flag char in DateWord array.
 63
 64        // Use char in PUA area since we won't be using them in real data.
 65        // The char used to tell a read date word or a month postfix.  A month postfix
 66        // is "ta" in the long date pattern like "d. MMMM'ta 'yyyy" for fi-FI.
 67        // In this case, it will be stored as "\xfffeta" in the date word array.
 68        internal const char MonthPostfixChar = '\xe000';
 69
 70        // Add ignorable symbol in a DateWord array.
 71
 72        // hu-HU has:
 73        //      shrot date pattern: yyyy. MM. dd.;yyyy-MM-dd;yy-MM-dd
 74        //      long date pattern: yyyy. MMMM d.
 75        // Here, "." is the date separator (derived from short date pattern). However,
 76        // "." also appear at the end of long date pattern.  In this case, we just
 77        // "." as ignorable symbol so that the DateTime.Parse() state machine will not
 78        // treat the additional date separator at the end of y,m,d pattern as an error
 79        // condition.
 80        internal const char IgnorableSymbolChar = '\xe001';
 81
 82        // Known CJK suffix
 83        internal const char CJKYearSuff = '\u5e74';
 84        internal const char CJKMonthSuff = '\u6708';
 85        internal const char CJKDaySuff = '\u65e5';
 86
 87        internal const char KoreanYearSuff = '\ub144';
 88        internal const char KoreanMonthSuff = '\uc6d4';
 89        internal const char KoreanDaySuff = '\uc77c';
 90
 91        internal const char KoreanHourSuff = '\uc2dc';
 92        internal const char KoreanMinuteSuff = '\ubd84';
 93        internal const char KoreanSecondSuff = '\ucd08';
 94
 95        internal const char CJKHourSuff = '\u6642';
 96        internal const char ChineseHourSuff = '\u65f6';
 97
 98        internal const char CJKMinuteSuff = '\u5206';
 99        internal const char CJKSecondSuff = '\u79d2';
 100
 101        // The collection for date words & postfix.
 102        internal List<string>? m_dateWords;
 103
 104        ////////////////////////////////////////////////////////////////////////////
 105        //
 106        //  Parameters:
 107        //      pattern: The pattern to be scanned.
 108        //      currentIndex: the current index to start the scan.
 109        //
 110        //  Returns:
 111        //      Return the index with the first character that is a letter, which will
 112        //      be the start of a date word.
 113        //      Note that the index can be pattern.Length if we reach the end of the string.
 114        //
 115        ////////////////////////////////////////////////////////////////////////////
 116        internal static int SkipWhiteSpacesAndNonLetter(string pattern, int currentIndex)
 117        {
 0118            while (currentIndex < pattern.Length)
 119            {
 0120                char ch = pattern[currentIndex];
 0121                if (ch == '\\')
 122                {
 123                    // Escaped character. Look ahead one character.
 0124                    currentIndex++;
 0125                    if (currentIndex < pattern.Length)
 126                    {
 0127                        ch = pattern[currentIndex];
 0128                        if (ch == '\'')
 129                        {
 130                            // Skip the leading single quote.  We will
 131                            // stop at the first letter.
 132                            continue;
 133                        }
 134                        // Fall thru to check if this is a letter.
 135                    }
 136                    else
 137                    {
 138                        // End of string
 139                        break;
 140                    }
 141                }
 0142                if (char.IsLetter(ch) || ch == '\'' || ch == '.')
 143                {
 144                    break;
 145                }
 146                // Skip the current char since it is not a letter.
 0147                currentIndex++;
 148            }
 0149            return currentIndex;
 150        }
 151
 152        ////////////////////////////////////////////////////////////////////////////
 153        //
 154        // A helper to add the found date word or month postfix into ArrayList for date words.
 155        //
 156        // Parameters:
 157        //      formatPostfix: What kind of postfix this is.
 158        //          Possible values:
 159        //              null: This is a regular date word
 160        //              "MMMM": month postfix
 161        //      word: The date word or postfix to be added.
 162        //
 163        ////////////////////////////////////////////////////////////////////////////
 164        internal void AddDateWordOrPostfix(string? formatPostfix, string str)
 165        {
 0166            if (str.Length == 0)
 167            {
 0168                return;
 169            }
 170
 0171            if (str.Length == 1)
 172            {
 0173                switch (str[0])
 174                {
 175                    // Some cultures use . like an abbreviation
 176                    case '.':
 0177                        AddIgnorableSymbols(".");
 0178                        return;
 179
 180                    // Skip these special symbols.
 181                    case '/':
 182                    case '-':
 0183                        return;
 184
 185                    // Skip known CJK suffixes.
 186                    case CJKYearSuff:
 187                    case CJKMonthSuff:
 188                    case CJKDaySuff:
 189                    case KoreanYearSuff:
 190                    case KoreanMonthSuff:
 191                    case KoreanDaySuff:
 192                    case KoreanHourSuff:
 193                    case KoreanMinuteSuff:
 194                    case KoreanSecondSuff:
 195                    case CJKHourSuff:
 196                    case ChineseHourSuff:
 197                    case CJKMinuteSuff:
 198                    case CJKSecondSuff:
 0199                        return;
 200                }
 201            }
 202
 0203            m_dateWords ??= [];
 204
 0205            if (formatPostfix == "MMMM")
 206            {
 207                // Add the word into the ArrayList as "\xfffe" + real month postfix.
 0208                string temp = MonthPostfixChar + str;
 0209                if (!m_dateWords.Contains(temp))
 210                {
 0211                    m_dateWords.Add(temp);
 212                }
 213            }
 214            else
 215            {
 0216                if (!m_dateWords.Contains(str))
 217                {
 0218                    m_dateWords.Add(str);
 219                }
 220
 0221                if (str.EndsWith('.'))
 222                {
 223                    // Old version ignore the trailing dot in the date words. Support this as well.
 0224                    string strWithoutDot = str[0..^1];
 0225                    if (!m_dateWords.Contains(strWithoutDot))
 226                    {
 0227                        m_dateWords.Add(strWithoutDot);
 228                    }
 229                }
 230            }
 0231        }
 232
 233        ////////////////////////////////////////////////////////////////////////////
 234        //
 235        // Scan the pattern from the specified index and add the date word/postfix
 236        // when appropriate.
 237        //
 238        //  Parameters:
 239        //      pattern: The pattern to be scanned.
 240        //      index: The starting index to be scanned.
 241        //      formatPostfix: The kind of postfix to be scanned.
 242        //          Possible values:
 243        //              null: This is a regular date word
 244        //              "MMMM": month postfix
 245        //
 246        //
 247        ////////////////////////////////////////////////////////////////////////////
 248        internal int AddDateWords(string pattern, int index, string? formatPostfix)
 249        {
 250            // Skip any whitespaces so we will start from a letter.
 0251            int newIndex = SkipWhiteSpacesAndNonLetter(pattern, index);
 0252            if (newIndex != index && formatPostfix != null)
 253            {
 254                // There are whitespaces. This will not be a postfix.
 0255                formatPostfix = null;
 256            }
 0257            index = newIndex;
 258
 259            // This is the first char added into dateWord.
 260            // Skip all non-letter character.  We will add the first letter into DateWord.
 0261            StringBuilder dateWord = new StringBuilder();
 262            // We assume that date words should start with a letter.
 263            // Skip anything until we see a letter.
 264
 0265            while (index < pattern.Length)
 266            {
 0267                char ch = pattern[index];
 0268                if (ch == '\'')
 269                {
 270                    // We have seen the end of quote.  Add the word if we do not see it before,
 271                    // and break the while loop.
 0272                    AddDateWordOrPostfix(formatPostfix, dateWord.ToString());
 0273                    index++;
 0274                    break;
 275                }
 0276                else if (ch == '\\')
 277                {
 278                    //
 279                    // Escaped character.  Look ahead one character
 280                    //
 281
 282                    // Skip escaped backslash.
 0283                    index++;
 0284                    if (index < pattern.Length)
 285                    {
 0286                        dateWord.Append(pattern[index]);
 0287                        index++;
 288                    }
 289                }
 0290                else if (char.IsWhiteSpace(ch))
 291                {
 292                    // Found a whitespace.  We have to add the current date word/postfix.
 0293                    AddDateWordOrPostfix(formatPostfix, dateWord.ToString());
 0294                    if (formatPostfix != null)
 295                    {
 296                        // Done with postfix.  The rest will be regular date word.
 0297                        formatPostfix = null;
 298                    }
 299                    // Reset the dateWord.
 0300                    dateWord.Length = 0;
 0301                    index++;
 302                }
 303                else
 304                {
 0305                    dateWord.Append(ch);
 0306                    index++;
 307                }
 308            }
 0309            return index;
 310        }
 311
 312        ////////////////////////////////////////////////////////////////////////////
 313        //
 314        // A simple helper to find the repeat count for a specified char.
 315        //
 316        ////////////////////////////////////////////////////////////////////////////
 317        internal static int ScanRepeatChar(string pattern, char ch, int index, out int count)
 318        {
 0319            count = 1;
 0320            while ((uint)++index < (uint)pattern.Length && pattern[index] == ch)
 321            {
 0322                count++;
 323            }
 324            // Return the updated position.
 0325            return index;
 326        }
 327
 328        ////////////////////////////////////////////////////////////////////////////
 329        //
 330        // Add the text that is a date separator but is treated like ignorable symbol.
 331        // E.g.
 332        // hu-HU has:
 333        //      short date pattern: yyyy. MM. dd.;yyyy-MM-dd;yy-MM-dd
 334        //      long date pattern: yyyy. MMMM d.
 335        // Here, "." is the date separator (derived from short date pattern). However,
 336        // "." also appear at the end of long date pattern.  In this case, we just
 337        // "." as ignorable symbol so that the DateTime.Parse() state machine will not
 338        // treat the additional date separator at the end of y,m,d pattern as an error
 339        // condition.
 340        //
 341        ////////////////////////////////////////////////////////////////////////////
 342
 343        internal void AddIgnorableSymbols(string? text)
 344        {
 345            // Create the date word array.
 0346            m_dateWords ??= [];
 347
 348            // Add the ignorable symbol into the ArrayList.
 0349            string temp = IgnorableSymbolChar + text;
 0350            if (!m_dateWords.Contains(temp))
 351            {
 0352                m_dateWords.Add(temp);
 353            }
 0354        }
 355
 356        //
 357        // Flag used to trace the date patterns (yy/yyyyy/M/MM/MMM/MMM/d/dd) that we have seen.
 358        //
 359        private enum FoundDatePattern
 360        {
 361            None = 0x0000,
 362            FoundYearPatternFlag = 0x0001,
 363            FoundMonthPatternFlag = 0x0002,
 364            FoundDayPatternFlag = 0x0004,
 365            FoundYMDPatternFlag = 0x0007, // FoundYearPatternFlag | FoundMonthPatternFlag | FoundDayPatternFlag;
 366        }
 367
 368        // Check if we have found all of the year/month/day pattern.
 369        private FoundDatePattern _ymdFlags = FoundDatePattern.None;
 370
 371        ////////////////////////////////////////////////////////////////////////////
 372        //
 373        // Given a date format pattern, scan for date word or postfix.
 374        //
 375        // A date word should be always put in a single quoted string.  And it will
 376        // start from a letter, so whitespace and symbols will be ignored before
 377        // the first letter.
 378        //
 379        // Examples of date word:
 380        //  'de' in es-SP: dddd, dd' de 'MMMM' de 'yyyy
 381        //  "\x0443." in bg-BG: dd.M.yyyy '\x0433.'
 382        //
 383        // Example of postfix:
 384        //  month postfix:
 385        //      "ta" in fi-FI: d. MMMM'ta 'yyyy
 386        //  Currently, only month postfix is supported.
 387        //
 388        // Usage:
 389        //  Always call this with Framework-style pattern, instead of Windows style pattern.
 390        //  Windows style pattern uses '' for single quote, while .NET uses \'
 391        //
 392        ////////////////////////////////////////////////////////////////////////////
 393        internal void ScanDateWord(string pattern)
 394        {
 395            // Check if we have found all of the year/month/day pattern.
 0396            _ymdFlags = FoundDatePattern.None;
 397
 0398            int i = 0;
 0399            while (i < pattern.Length)
 400            {
 0401                char ch = pattern[i];
 402                int chCount;
 403
 404                switch (ch)
 405                {
 406                    case '\'':
 407                        // Find a beginning quote.  Search until the end quote.
 0408                        i = AddDateWords(pattern, i + 1, null);
 0409                        break;
 410                    case 'M':
 0411                        i = ScanRepeatChar(pattern, 'M', i, out chCount);
 0412                        if (chCount >= 4)
 413                        {
 0414                            if ((uint)i < (uint)pattern.Length && pattern[i] == '\'')
 415                            {
 0416                                i = AddDateWords(pattern, i + 1, "MMMM");
 417                            }
 418                        }
 0419                        _ymdFlags |= FoundDatePattern.FoundMonthPatternFlag;
 0420                        break;
 421                    case 'y':
 0422                        i = ScanRepeatChar(pattern, 'y', i, out _);
 0423                        _ymdFlags |= FoundDatePattern.FoundYearPatternFlag;
 0424                        break;
 425                    case 'd':
 0426                        i = ScanRepeatChar(pattern, 'd', i, out chCount);
 0427                        if (chCount <= 2)
 428                        {
 429                            // Only count "d" & "dd".
 430                            // ddd, dddd are day names.  Do not count them.
 0431                            _ymdFlags |= FoundDatePattern.FoundDayPatternFlag;
 432                        }
 0433                        break;
 434                    case '\\':
 435                        // Found a escaped char not in a quoted string.  Skip the current backslash
 436                        // and its next character.
 0437                        i += 2;
 0438                        break;
 439                    case '.':
 0440                        if (_ymdFlags == FoundDatePattern.FoundYMDPatternFlag)
 441                        {
 442                            // If we find a dot immediately after the we have seen all of the y, m, d pattern.
 443                            // treat it as a ignroable symbol.  Check for comments in AddIgnorableSymbols for
 444                            // more details.
 0445                            AddIgnorableSymbols(".");
 0446                            _ymdFlags = FoundDatePattern.None;
 447                        }
 0448                        i++;
 0449                        break;
 450                    default:
 0451                        if (_ymdFlags == FoundDatePattern.FoundYMDPatternFlag && !char.IsWhiteSpace(ch))
 452                        {
 453                            // We are not seeing "." after YMD. Clear the flag.
 0454                            _ymdFlags = FoundDatePattern.None;
 455                        }
 456                        // We are not in quote.  Skip the current character.
 0457                        i++;
 458                        break;
 459                }
 460            }
 0461        }
 462
 463        ////////////////////////////////////////////////////////////////////////////
 464        //
 465        // Given a DTFI, get all of the date words from date patterns and time patterns.
 466        //
 467        ////////////////////////////////////////////////////////////////////////////
 468
 469        internal string[]? GetDateWordsOfDTFI(DateTimeFormatInfo dtfi)
 470        {
 471            // Enumerate all LongDatePatterns, and get the DateWords and scan for month postfix.
 0472            string[] datePatterns = dtfi.GetAllDateTimePatterns('D');
 473            int i;
 474
 475            // Scan the long date patterns
 0476            for (i = 0; i < datePatterns.Length; i++)
 477            {
 0478                ScanDateWord(datePatterns[i]);
 479            }
 480
 481            // Scan the short date patterns
 0482            datePatterns = dtfi.GetAllDateTimePatterns('d');
 0483            for (i = 0; i < datePatterns.Length; i++)
 484            {
 0485                ScanDateWord(datePatterns[i]);
 486            }
 487            // Scan the YearMonth patterns.
 0488            datePatterns = dtfi.GetAllDateTimePatterns('y');
 0489            for (i = 0; i < datePatterns.Length; i++)
 490            {
 0491                ScanDateWord(datePatterns[i]);
 492            }
 493
 494            // Scan the month/day pattern
 0495            ScanDateWord(dtfi.MonthDayPattern);
 496
 497            // Scan the long time patterns.
 0498            datePatterns = dtfi.GetAllDateTimePatterns('T');
 0499            for (i = 0; i < datePatterns.Length; i++)
 500            {
 0501                ScanDateWord(datePatterns[i]);
 502            }
 503
 504            // Scan the short time patterns.
 0505            datePatterns = dtfi.GetAllDateTimePatterns('t');
 0506            for (i = 0; i < datePatterns.Length; i++)
 507            {
 0508                ScanDateWord(datePatterns[i]);
 509            }
 510
 0511            string[]? result = null;
 0512            if (m_dateWords is { Count: > 0 } dateWords)
 513            {
 0514                result = dateWords.ToArray();
 515            }
 516
 0517            return result;
 518        }
 519
 520        ////////////////////////////////////////////////////////////////////////////
 521        //
 522        // Scan the month names to see if genitive month names are used, and return
 523        // the format flag.
 524        //
 525        ////////////////////////////////////////////////////////////////////////////
 526        internal static FORMATFLAGS GetFormatFlagGenitiveMonth(string[] monthNames, string[] genitiveMonthNames, string[
 527        {
 528            // If we have different names in regular and genitive month names, use genitive month flag.
 0529            return (!monthNames.AsSpan().SequenceEqual(genitiveMonthNames) || !abbrevMonthNames.AsSpan().SequenceEqual(g
 0530                ? FORMATFLAGS.UseGenitiveMonth : 0;
 531        }
 532
 533        ////////////////////////////////////////////////////////////////////////////
 534        //
 535        // Scan the month names to see if spaces are used or start with a digit, and return the format flag
 536        //
 537        ////////////////////////////////////////////////////////////////////////////
 538        internal static FORMATFLAGS GetFormatFlagUseSpaceInMonthNames(string[] monthNames, string[] genitveMonthNames, s
 539        {
 0540            FORMATFLAGS formatFlags = 0;
 0541            formatFlags |= (ArrayElementsBeginWithDigit(monthNames) ||
 0542                    ArrayElementsBeginWithDigit(genitveMonthNames) ||
 0543                    ArrayElementsBeginWithDigit(abbrevMonthNames) ||
 0544                    ArrayElementsBeginWithDigit(genetiveAbbrevMonthNames)
 0545                    ? FORMATFLAGS.UseDigitPrefixInTokens : 0);
 546
 0547            formatFlags |= (ArrayElementsHaveSpace(monthNames) ||
 0548                    ArrayElementsHaveSpace(genitveMonthNames) ||
 0549                    ArrayElementsHaveSpace(abbrevMonthNames) ||
 0550                    ArrayElementsHaveSpace(genetiveAbbrevMonthNames)
 0551                    ? FORMATFLAGS.UseSpacesInMonthNames : 0);
 0552            return formatFlags;
 553        }
 554
 555        ////////////////////////////////////////////////////////////////////////////
 556        //
 557        // Scan the day names and set the correct format flag.
 558        //
 559        ////////////////////////////////////////////////////////////////////////////
 560        internal static FORMATFLAGS GetFormatFlagUseSpaceInDayNames(string[] dayNames, string[] abbrevDayNames)
 561        {
 0562            return (ArrayElementsHaveSpace(dayNames) ||
 0563                    ArrayElementsHaveSpace(abbrevDayNames))
 0564                    ? FORMATFLAGS.UseSpacesInDayNames : 0;
 565        }
 566
 567        ////////////////////////////////////////////////////////////////////////////
 568        //
 569        // Check the calendar to see if it is HebrewCalendar and set the Hebrew format flag if necessary.
 570        //
 571        ////////////////////////////////////////////////////////////////////////////
 572        internal static FORMATFLAGS GetFormatFlagUseHebrewCalendar(int calID)
 573        {
 0574            return calID == (int)CalendarId.HEBREW ?
 0575                FORMATFLAGS.UseHebrewParsing | FORMATFLAGS.UseLeapYearMonth : 0;
 576        }
 577
 578        //-----------------------------------------------------------------------------
 579        // ArrayElementsHaveSpace
 580        //      It checks all input array elements if any of them has space character
 581        //      returns true if found space character in one of the array elements.
 582        //      otherwise returns false.
 583        //-----------------------------------------------------------------------------
 584
 585        private static bool ArrayElementsHaveSpace(string[] array)
 586        {
 0587            for (int i = 0; i < array.Length; i++)
 588            {
 589                // it is faster to check for space character manually instead of calling IndexOf
 590                // so we don't have to go to native code side.
 0591                for (int j = 0; j < array[i].Length; j++)
 592                {
 0593                    if (char.IsWhiteSpace(array[i][j]))
 594                    {
 0595                        return true;
 596                    }
 597                }
 598            }
 599
 0600            return false;
 601        }
 602
 603        ////////////////////////////////////////////////////////////////////////////
 604        //
 605        // Check if any element of the array start with a digit.
 606        //
 607        ////////////////////////////////////////////////////////////////////////////
 608        private static bool ArrayElementsBeginWithDigit(string[] array)
 609        {
 0610            foreach (string s in array)
 611            {
 612                // it is faster to check for space character manually instead of calling IndexOf
 613                // so we don't have to go to native code side.
 0614                if (s.Length != 0 && char.IsAsciiDigit(s[0]))
 615                {
 0616                    int index = 1;
 0617                    while ((uint)index < (uint)s.Length && char.IsAsciiDigit(s[index]))
 618                    {
 619                        // Skip other digits.
 0620                        index++;
 621                    }
 0622                    if (index == s.Length)
 623                    {
 0624                        return false;
 625                    }
 626
 0627                    if (index == s.Length - 1)
 628                    {
 629                        // Skip known CJK month suffix.
 630                        // CJK uses month name like "1\x6708", since \x6708 is a known month suffix,
 631                        // we don't need the UseDigitPrefixInTokens since it is slower.
 0632                        switch (s[index])
 633                        {
 634                            case CJKMonthSuff:
 635                            case KoreanMonthSuff:
 0636                                return false;
 637                        }
 638                    }
 639
 0640                    if (index == s.Length - 4)
 641                    {
 642                        // Skip known CJK month suffix.
 643                        // Starting with Windows 8, the CJK months for some cultures looks like: "1' \x6708'"
 644                        // instead of just "1\x6708"
 0645                        if (s[index] == '\'' && s[index + 1] == ' ' &&
 0646                            s[index + 2] == CJKMonthSuff && s[index + 3] == '\'')
 647                        {
 0648                            return false;
 649                        }
 650                    }
 0651                    return true;
 652                }
 653            }
 654
 0655            return false;
 656        }
 657    }
 658}
 659