| | | 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 | | |
| | | 4 | | using System.Collections.Generic; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Diagnostics.CodeAnalysis; |
| | | 7 | | using System.Runtime.CompilerServices; |
| | | 8 | | using System.Runtime.InteropServices; |
| | | 9 | | using System.Text; |
| | | 10 | | |
| | | 11 | | namespace System.Globalization |
| | | 12 | | { |
| | | 13 | | /// <summary> |
| | | 14 | | /// Flags used to indicate different styles of month names. |
| | | 15 | | /// This is an internal flag used by internalGetMonthName(). |
| | | 16 | | /// Use flag here in case that we need to provide a combination of these styles |
| | | 17 | | /// (such as month name of a leap year in genitive form. Not likely for now, |
| | | 18 | | /// but would like to keep the option open). |
| | | 19 | | /// </summary> |
| | | 20 | | |
| | | 21 | | [Flags] |
| | | 22 | | internal enum MonthNameStyles |
| | | 23 | | { |
| | | 24 | | Regular = 0x00000000, |
| | | 25 | | Genitive = 0x00000001, |
| | | 26 | | LeapYear = 0x00000002, |
| | | 27 | | } |
| | | 28 | | |
| | | 29 | | /// <summary> |
| | | 30 | | /// Flags used to indicate special rule used in parsing/formatting |
| | | 31 | | /// for a specific DateTimeFormatInfo instance. |
| | | 32 | | /// This is an internal flag. |
| | | 33 | | /// |
| | | 34 | | /// This flag is different from MonthNameStyles because this flag |
| | | 35 | | /// can be expanded to accommodate parsing behaviors like CJK month names |
| | | 36 | | /// or alternative month names, etc. |
| | | 37 | | /// </summary> |
| | | 38 | | [Flags] |
| | | 39 | | internal enum DateTimeFormatFlags |
| | | 40 | | { |
| | | 41 | | None = 0x00000000, |
| | | 42 | | UseGenitiveMonth = 0x00000001, |
| | | 43 | | UseLeapYearMonth = 0x00000002, |
| | | 44 | | UseSpacesInMonthNames = 0x00000004, // Has spaces or non-breaking space in the month names. |
| | | 45 | | UseHebrewRule = 0x00000008, // Format/Parse using the Hebrew calendar rule. |
| | | 46 | | UseSpacesInDayNames = 0x00000010, // Has spaces or non-breaking space in the day names. |
| | | 47 | | UseDigitPrefixInTokens = 0x00000020, // Has token starting with numbers. |
| | | 48 | | |
| | | 49 | | NotInitialized = -1, |
| | | 50 | | } |
| | | 51 | | |
| | | 52 | | public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable |
| | | 53 | | { |
| | | 54 | | // an index which points to a record in Culture Data Table. |
| | | 55 | | private readonly CultureData _cultureData; |
| | | 56 | | |
| | | 57 | | // The culture name used to create this DTFI. |
| | | 58 | | private string? _name; |
| | | 59 | | |
| | | 60 | | // CompareInfo usually used by the parser. |
| | | 61 | | private CompareInfo? _compareInfo; |
| | | 62 | | |
| | | 63 | | private string? amDesignator; |
| | | 64 | | private string? pmDesignator; |
| | | 65 | | |
| | | 66 | | private string? dateSeparator; // derived from short date (whidbey expects, arrowhead doesn't) |
| | | 67 | | |
| | | 68 | | private string? generalShortTimePattern; // short date + short time (whidbey expects, arrowhead doesn't) |
| | | 69 | | |
| | | 70 | | private string? generalLongTimePattern; // short date + long time (whidbey expects, arrowhead doesn't) |
| | | 71 | | |
| | | 72 | | private string? timeSeparator; // derived from long time (whidbey expects, arrowhead doesn't) |
| | | 73 | | private string? monthDayPattern; |
| | | 74 | | private string? dateTimeOffsetPattern; |
| | | 75 | | |
| | | 76 | | private byte[]? amDesignatorUtf8; |
| | | 77 | | private byte[]? pmDesignatorUtf8; |
| | | 78 | | private byte[]? timeSeparatorUtf8; |
| | | 79 | | private byte[]? dateSeparatorUtf8; |
| | | 80 | | |
| | | 81 | | private const string rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"; |
| | | 82 | | |
| | | 83 | | // The sortable pattern is based on ISO 8601. |
| | | 84 | | private const string sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss"; |
| | | 85 | | private const string universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'"; |
| | | 86 | | |
| | | 87 | | private Calendar calendar; |
| | | 88 | | |
| | 0 | 89 | | private int firstDayOfWeek = -1; |
| | 0 | 90 | | private int calendarWeekRule = -1; |
| | | 91 | | |
| | | 92 | | private string? fullDateTimePattern; // long date + long time (whidbey expects, arrowhead doesn't) |
| | | 93 | | |
| | | 94 | | private string[]? abbreviatedDayNames; |
| | | 95 | | |
| | | 96 | | private string[]? m_superShortDayNames; |
| | | 97 | | |
| | | 98 | | private string[]? dayNames; |
| | | 99 | | private string[]? abbreviatedMonthNames; |
| | | 100 | | private string[]? monthNames; |
| | | 101 | | |
| | | 102 | | // Cache the genitive month names that we retrieve from the data table. |
| | | 103 | | |
| | | 104 | | private string[]? genitiveMonthNames; |
| | | 105 | | |
| | | 106 | | // Cache the abbreviated genitive month names that we retrieve from the data table. |
| | | 107 | | |
| | | 108 | | private string[]? m_genitiveAbbreviatedMonthNames; |
| | | 109 | | |
| | | 110 | | // Cache the month names of a leap year that we retrieve from the data table. |
| | | 111 | | private string[]? leapYearMonthNames; |
| | | 112 | | |
| | | 113 | | // For our "patterns" arrays we have 2 variables, a string and a string[] |
| | | 114 | | // |
| | | 115 | | // The string[] contains the list of patterns, EXCEPT the default may not be included. |
| | | 116 | | // The string contains the default pattern. |
| | | 117 | | // When we initially construct our string[], we set the string to string[0] |
| | | 118 | | |
| | | 119 | | // The "default" Date/time patterns |
| | | 120 | | private string? longDatePattern; |
| | | 121 | | private string? shortDatePattern; |
| | | 122 | | private string? yearMonthPattern; |
| | | 123 | | private string? longTimePattern; |
| | | 124 | | private string? shortTimePattern; |
| | | 125 | | |
| | | 126 | | private string[]? allYearMonthPatterns; |
| | | 127 | | |
| | | 128 | | private string[]? allShortDatePatterns; |
| | | 129 | | private string[]? allLongDatePatterns; |
| | | 130 | | private string[]? allShortTimePatterns; |
| | | 131 | | private string[]? allLongTimePatterns; |
| | | 132 | | |
| | | 133 | | // Cache the era names for this DateTimeFormatInfo instance. |
| | | 134 | | private string[]? m_eraNames; |
| | | 135 | | private string[]? m_abbrevEraNames; |
| | | 136 | | private string[]? m_abbrevEnglishEraNames; |
| | | 137 | | |
| | | 138 | | private const int DEFAULT_ALL_DATETIMES_SIZE = 132; |
| | | 139 | | |
| | | 140 | | // CultureInfo updates this |
| | | 141 | | internal bool _isReadOnly; |
| | | 142 | | |
| | | 143 | | // This flag gives hints about if formatting/parsing should perform special code path for things like |
| | | 144 | | // genitive form or leap year month names. |
| | | 145 | | |
| | 0 | 146 | | private DateTimeFormatFlags formatFlags = DateTimeFormatFlags.NotInitialized; |
| | | 147 | | |
| | 0 | 148 | | private string CultureName => _name ??= _cultureData.CultureName; |
| | | 149 | | |
| | | 150 | | // Culture matches current DTFI. mainly used for string comparisons during parsing. |
| | 0 | 151 | | private CultureInfo Culture => field ??= CultureInfo.GetCultureInfo(CultureName); |
| | | 152 | | |
| | | 153 | | // The language name of the culture used to create this DTFI. |
| | 0 | 154 | | private string LanguageName => field ??= _cultureData.TwoLetterISOLanguageName; |
| | | 155 | | |
| | | 156 | | /// <summary> |
| | | 157 | | /// Create an array of string which contains the abbreviated day names. |
| | | 158 | | /// </summary> |
| | 0 | 159 | | private string[] InternalGetAbbreviatedDayOfWeekNames() => abbreviatedDayNames ?? InternalGetAbbreviatedDayOfWee |
| | | 160 | | |
| | | 161 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 162 | | private string[] InternalGetAbbreviatedDayOfWeekNamesCore() |
| | | 163 | | { |
| | | 164 | | // Get the abbreviated day names for our current calendar |
| | 0 | 165 | | abbreviatedDayNames = _cultureData.AbbreviatedDayNames(Calendar.ID); |
| | 0 | 166 | | Debug.Assert(abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 |
| | 0 | 167 | | return abbreviatedDayNames; |
| | | 168 | | } |
| | | 169 | | |
| | | 170 | | /// <summary> |
| | | 171 | | /// Returns the string array of the one-letter day of week names. |
| | | 172 | | /// </summary> |
| | 0 | 173 | | private string[] InternalGetSuperShortDayNames() => m_superShortDayNames ?? InternalGetSuperShortDayNamesCore(); |
| | | 174 | | |
| | | 175 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 176 | | private string[] InternalGetSuperShortDayNamesCore() |
| | | 177 | | { |
| | | 178 | | // Get the super short day names for our current calendar |
| | 0 | 179 | | m_superShortDayNames = _cultureData.SuperShortDayNames(Calendar.ID); |
| | 0 | 180 | | Debug.Assert(m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.InternalGetSuperShortDayNames] Expected |
| | 0 | 181 | | return m_superShortDayNames; |
| | | 182 | | } |
| | | 183 | | |
| | | 184 | | /// <summary> |
| | | 185 | | /// Create an array of string which contains the day names. |
| | | 186 | | /// </summary> |
| | 0 | 187 | | private string[] InternalGetDayOfWeekNames() => dayNames ?? InternalGetDayOfWeekNamesCore(); |
| | | 188 | | |
| | | 189 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 190 | | private string[] InternalGetDayOfWeekNamesCore() |
| | | 191 | | { |
| | | 192 | | // Get the day names for our current calendar |
| | 0 | 193 | | dayNames = _cultureData.DayNames(Calendar.ID); |
| | 0 | 194 | | Debug.Assert(dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week"); |
| | 0 | 195 | | return dayNames; |
| | | 196 | | } |
| | | 197 | | |
| | | 198 | | /// <summary> |
| | | 199 | | /// Create an array of string which contains the abbreviated month names. |
| | | 200 | | /// </summary> |
| | 0 | 201 | | private string[] InternalGetAbbreviatedMonthNames() => abbreviatedMonthNames ?? InternalGetAbbreviatedMonthNames |
| | | 202 | | |
| | | 203 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 204 | | private string[] InternalGetAbbreviatedMonthNamesCore() |
| | | 205 | | { |
| | | 206 | | // Get the month names for our current calendar |
| | 0 | 207 | | abbreviatedMonthNames = _cultureData.AbbreviatedMonthNames(Calendar.ID); |
| | 0 | 208 | | Debug.Assert(abbreviatedMonthNames.Length == 12 || abbreviatedMonthNames.Length == 13, |
| | 0 | 209 | | "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year"); |
| | 0 | 210 | | return abbreviatedMonthNames; |
| | | 211 | | } |
| | | 212 | | |
| | | 213 | | /// <summary> |
| | | 214 | | /// Create an array of string which contains the month names. |
| | | 215 | | /// </summary> |
| | 0 | 216 | | private string[] InternalGetMonthNames() => monthNames ?? internalGetMonthNamesCore(); |
| | | 217 | | |
| | | 218 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 219 | | private string[] internalGetMonthNamesCore() |
| | | 220 | | { |
| | | 221 | | // Get the month names for our current calendar |
| | 0 | 222 | | monthNames = _cultureData.MonthNames(Calendar.ID); |
| | 0 | 223 | | Debug.Assert(monthNames.Length == 12 || monthNames.Length == 13, |
| | 0 | 224 | | "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year"); |
| | 0 | 225 | | return monthNames; |
| | | 226 | | } |
| | | 227 | | |
| | | 228 | | // Invariant DateTimeFormatInfo doesn't have user-overridden values |
| | | 229 | | // Default calendar is gregorian |
| | | 230 | | public DateTimeFormatInfo() |
| | 0 | 231 | | : this(CultureInfo.InvariantCulture._cultureData, GregorianCalendar.GetDefaultInstance()) |
| | | 232 | | { |
| | 0 | 233 | | } |
| | | 234 | | |
| | 0 | 235 | | internal DateTimeFormatInfo(CultureData cultureData, Calendar cal) |
| | | 236 | | { |
| | 0 | 237 | | Debug.Assert(cultureData != null); |
| | 0 | 238 | | Debug.Assert(cal != null); |
| | | 239 | | |
| | | 240 | | // Remember our culture |
| | 0 | 241 | | _cultureData = cultureData; |
| | 0 | 242 | | calendar = cal; |
| | 0 | 243 | | InitializeOverridableProperties(cultureData, calendar.ID); |
| | 0 | 244 | | } |
| | | 245 | | |
| | | 246 | | private void InitializeOverridableProperties(CultureData cultureData, CalendarId calendarId) |
| | | 247 | | { |
| | 0 | 248 | | Debug.Assert(cultureData != null); |
| | 0 | 249 | | Debug.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initializ |
| | | 250 | | |
| | 0 | 251 | | if (firstDayOfWeek == -1) |
| | | 252 | | { |
| | 0 | 253 | | firstDayOfWeek = cultureData.FirstDayOfWeek; |
| | | 254 | | } |
| | 0 | 255 | | if (calendarWeekRule == -1) |
| | | 256 | | { |
| | 0 | 257 | | calendarWeekRule = cultureData.CalendarWeekRule; |
| | | 258 | | } |
| | | 259 | | |
| | 0 | 260 | | amDesignator ??= cultureData.AMDesignator; |
| | 0 | 261 | | pmDesignator ??= cultureData.PMDesignator; |
| | 0 | 262 | | timeSeparator ??= cultureData.TimeSeparator; |
| | 0 | 263 | | dateSeparator ??= cultureData.DateSeparator(calendarId); |
| | | 264 | | |
| | 0 | 265 | | allLongTimePatterns = _cultureData.LongTimes; |
| | 0 | 266 | | Debug.Assert(allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns |
| | | 267 | | |
| | 0 | 268 | | allShortTimePatterns = _cultureData.ShortTimes; |
| | 0 | 269 | | Debug.Assert(allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patter |
| | | 270 | | |
| | 0 | 271 | | allLongDatePatterns = cultureData.LongDates(calendarId); |
| | 0 | 272 | | Debug.Assert(allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns |
| | | 273 | | |
| | 0 | 274 | | allShortDatePatterns = cultureData.ShortDates(calendarId); |
| | 0 | 275 | | Debug.Assert(allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patter |
| | | 276 | | |
| | 0 | 277 | | allYearMonthPatterns = cultureData.YearMonths(calendarId); |
| | 0 | 278 | | Debug.Assert(allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patter |
| | 0 | 279 | | } |
| | | 280 | | |
| | | 281 | | /// <summary> |
| | | 282 | | /// Returns a default DateTimeFormatInfo that will be universally |
| | | 283 | | /// supported and constant irrespective of the current culture. |
| | | 284 | | /// </summary> |
| | 0 | 285 | | public static DateTimeFormatInfo InvariantInfo => DateTimeFormat.InvariantFormatInfo; |
| | | 286 | | |
| | | 287 | | /// <summary> |
| | | 288 | | /// Returns the current culture's DateTimeFormatInfo. |
| | | 289 | | /// </summary> |
| | | 290 | | public static DateTimeFormatInfo CurrentInfo |
| | | 291 | | { |
| | | 292 | | get |
| | | 293 | | { |
| | 0 | 294 | | CultureInfo culture = CultureInfo.CurrentCulture; |
| | 0 | 295 | | if (!culture._isInherited) |
| | | 296 | | { |
| | 0 | 297 | | DateTimeFormatInfo? info = culture._dateTimeInfo; |
| | 0 | 298 | | if (info != null) |
| | | 299 | | { |
| | 0 | 300 | | return info; |
| | | 301 | | } |
| | | 302 | | } |
| | 0 | 303 | | return (DateTimeFormatInfo)culture.GetFormat(typeof(DateTimeFormatInfo))!; |
| | | 304 | | } |
| | | 305 | | } |
| | | 306 | | |
| | | 307 | | public static DateTimeFormatInfo GetInstance(IFormatProvider? provider) |
| | | 308 | | { |
| | 0 | 309 | | return provider == null ? CurrentInfo : GetProviderNonNull(provider); |
| | | 310 | | |
| | | 311 | | static DateTimeFormatInfo GetProviderNonNull(IFormatProvider provider) |
| | | 312 | | { |
| | 0 | 313 | | if (provider.GetType() == typeof(CultureInfo) && ((CultureInfo)provider)._dateTimeInfo is { } info) |
| | | 314 | | { |
| | 0 | 315 | | return info; |
| | | 316 | | } |
| | | 317 | | |
| | 0 | 318 | | return |
| | 0 | 319 | | provider as DateTimeFormatInfo ?? |
| | 0 | 320 | | provider.GetFormat(typeof(DateTimeFormatInfo)) as DateTimeFormatInfo ?? |
| | 0 | 321 | | CurrentInfo; |
| | | 322 | | } |
| | | 323 | | } |
| | | 324 | | |
| | | 325 | | public object? GetFormat(Type? formatType) |
| | | 326 | | { |
| | 0 | 327 | | return formatType == typeof(DateTimeFormatInfo) ? this : null; |
| | | 328 | | } |
| | | 329 | | |
| | | 330 | | public object Clone() |
| | | 331 | | { |
| | 0 | 332 | | DateTimeFormatInfo n = (DateTimeFormatInfo)MemberwiseClone(); |
| | | 333 | | // We can use the data member calendar in the setter, instead of the property Calendar, |
| | | 334 | | // since the cloned copy should have the same state as the original copy. |
| | 0 | 335 | | n.calendar = (Calendar)Calendar.Clone(); |
| | 0 | 336 | | n._isReadOnly = false; |
| | 0 | 337 | | return n; |
| | | 338 | | } |
| | | 339 | | |
| | | 340 | | public string AMDesignator |
| | | 341 | | { |
| | | 342 | | get |
| | | 343 | | { |
| | 0 | 344 | | amDesignator ??= _cultureData.AMDesignator; |
| | 0 | 345 | | Debug.Assert(amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null"); |
| | 0 | 346 | | return amDesignator; |
| | | 347 | | } |
| | | 348 | | set |
| | | 349 | | { |
| | 0 | 350 | | if (IsReadOnly) |
| | | 351 | | { |
| | 0 | 352 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 353 | | } |
| | 0 | 354 | | ArgumentNullException.ThrowIfNull(value); |
| | | 355 | | |
| | 0 | 356 | | ClearTokenHashTable(); |
| | 0 | 357 | | amDesignator = value; |
| | 0 | 358 | | amDesignatorUtf8 = null; |
| | 0 | 359 | | } |
| | | 360 | | } |
| | | 361 | | |
| | | 362 | | internal ReadOnlySpan<TChar> AMDesignatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar> |
| | | 363 | | { |
| | 0 | 364 | | Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); |
| | 0 | 365 | | return typeof(TChar) == typeof(char) ? |
| | 0 | 366 | | Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(AMDesignator) : |
| | 0 | 367 | | Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(amDesignatorUtf8 ??= Encoding.UTF8.GetBytes(AMDe |
| | | 368 | | } |
| | | 369 | | |
| | | 370 | | public Calendar Calendar |
| | | 371 | | { |
| | | 372 | | get |
| | | 373 | | { |
| | 0 | 374 | | Debug.Assert(calendar != null, "DateTimeFormatInfo.Calendar: calendar != null"); |
| | 0 | 375 | | return calendar; |
| | | 376 | | } |
| | | 377 | | [MemberNotNull(nameof(calendar))] |
| | | 378 | | set |
| | | 379 | | { |
| | 0 | 380 | | if (IsReadOnly) |
| | | 381 | | { |
| | 0 | 382 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 383 | | } |
| | 0 | 384 | | ArgumentNullException.ThrowIfNull(value); |
| | | 385 | | |
| | 0 | 386 | | if (value == calendar) |
| | | 387 | | { |
| | 0 | 388 | | return; |
| | | 389 | | } |
| | | 390 | | |
| | 0 | 391 | | for (int i = 0; i < OptionalCalendars.Length; i++) |
| | | 392 | | { |
| | 0 | 393 | | if (OptionalCalendars[i] == value.ID) |
| | | 394 | | { |
| | | 395 | | // We can use this one, so do so. |
| | | 396 | | // Clean related properties if we already had a calendar set |
| | 0 | 397 | | if (calendar != null) |
| | | 398 | | { |
| | | 399 | | // clean related properties which are affected by the calendar setting, |
| | | 400 | | // so that they will be refreshed when they are accessed next time. |
| | | 401 | | // These properites are in the order as appearing in calendar.xml. |
| | 0 | 402 | | m_eraNames = null; |
| | 0 | 403 | | m_abbrevEraNames = null; |
| | 0 | 404 | | m_abbrevEnglishEraNames = null; |
| | | 405 | | |
| | 0 | 406 | | monthDayPattern = null; |
| | | 407 | | |
| | 0 | 408 | | dayNames = null; |
| | 0 | 409 | | abbreviatedDayNames = null; |
| | 0 | 410 | | m_superShortDayNames = null; |
| | 0 | 411 | | monthNames = null; |
| | 0 | 412 | | abbreviatedMonthNames = null; |
| | 0 | 413 | | genitiveMonthNames = null; |
| | 0 | 414 | | m_genitiveAbbreviatedMonthNames = null; |
| | 0 | 415 | | leapYearMonthNames = null; |
| | 0 | 416 | | formatFlags = DateTimeFormatFlags.NotInitialized; |
| | | 417 | | |
| | 0 | 418 | | allShortDatePatterns = null; |
| | 0 | 419 | | allLongDatePatterns = null; |
| | 0 | 420 | | allYearMonthPatterns = null; |
| | 0 | 421 | | dateTimeOffsetPattern = null; |
| | | 422 | | |
| | | 423 | | // The defaults need reset as well: |
| | 0 | 424 | | longDatePattern = null; |
| | 0 | 425 | | shortDatePattern = null; |
| | 0 | 426 | | yearMonthPattern = null; |
| | | 427 | | |
| | | 428 | | // These properies are not in the OS data, but they are dependent on the values like shortDa |
| | 0 | 429 | | fullDateTimePattern = null; // Long date + long time |
| | 0 | 430 | | generalShortTimePattern = null; // short date + short time |
| | 0 | 431 | | generalLongTimePattern = null; // short date + long time |
| | | 432 | | |
| | | 433 | | // Derived item that changes |
| | 0 | 434 | | dateSeparator = null; |
| | | 435 | | |
| | | 436 | | // We don't need to do these because they are not changed by changing calendar |
| | | 437 | | // amDesignator |
| | | 438 | | // pmDesignator |
| | | 439 | | // timeSeparator |
| | | 440 | | // longTimePattern |
| | | 441 | | // firstDayOfWeek |
| | | 442 | | // calendarWeekRule |
| | | 443 | | |
| | | 444 | | // remember to reload tokens |
| | 0 | 445 | | ClearTokenHashTable(); |
| | | 446 | | } |
| | | 447 | | |
| | | 448 | | // Remember the new calendar |
| | 0 | 449 | | calendar = value; |
| | 0 | 450 | | InitializeOverridableProperties(_cultureData, calendar.ID); |
| | | 451 | | |
| | | 452 | | // We succeeded, return |
| | 0 | 453 | | return; |
| | | 454 | | } |
| | | 455 | | } |
| | | 456 | | |
| | | 457 | | // The assigned calendar is not a valid calendar for this culture, throw |
| | 0 | 458 | | throw new ArgumentOutOfRangeException(nameof(value), value, SR.Argument_InvalidCalendar); |
| | | 459 | | } |
| | | 460 | | } |
| | | 461 | | |
| | 0 | 462 | | private CalendarId[] OptionalCalendars => field ??= _cultureData.CalendarIds; |
| | | 463 | | |
| | | 464 | | /// <summary> |
| | | 465 | | /// Get the era value by parsing the name of the era. |
| | | 466 | | /// </summary> |
| | | 467 | | public int GetEra(string eraName) |
| | | 468 | | { |
| | 0 | 469 | | ArgumentNullException.ThrowIfNull(eraName); |
| | | 470 | | |
| | | 471 | | // The Era Name and Abbreviated Era Name |
| | | 472 | | // for Taiwan Calendar on non-Taiwan SKU returns empty string (which |
| | | 473 | | // would be matched below) but we don't want the empty string to give |
| | | 474 | | // us an Era number |
| | | 475 | | // confer 85900 DTFI.GetEra("") should fail on all cultures |
| | 0 | 476 | | if (eraName.Length == 0) |
| | | 477 | | { |
| | 0 | 478 | | return -1; |
| | | 479 | | } |
| | | 480 | | |
| | | 481 | | // The following is based on the assumption that the era value is starting from 1, and has a |
| | | 482 | | // serial values. |
| | | 483 | | // If that ever changes, the code has to be changed. |
| | | 484 | | |
| | | 485 | | // The calls to String.Compare should use the current culture for the string comparisons, but the |
| | | 486 | | // invariant culture when comparing against the english names. |
| | 0 | 487 | | for (int i = 0; i < EraNames.Length; i++) |
| | | 488 | | { |
| | | 489 | | // Compare the era name in a case-insensitive way for the appropriate culture. |
| | 0 | 490 | | if (m_eraNames![i].Length > 0) |
| | | 491 | | { |
| | 0 | 492 | | if (Culture.CompareInfo.Compare(eraName, m_eraNames[i], CompareOptions.IgnoreCase) == 0) |
| | | 493 | | { |
| | 0 | 494 | | return i + 1; |
| | | 495 | | } |
| | | 496 | | } |
| | | 497 | | } |
| | 0 | 498 | | for (int i = 0; i < AbbreviatedEraNames.Length; i++) |
| | | 499 | | { |
| | | 500 | | // Compare the abbreviated era name in a case-insensitive way for the appropriate culture. |
| | 0 | 501 | | if (Culture.CompareInfo.Compare(eraName, m_abbrevEraNames![i], CompareOptions.IgnoreCase) == 0) |
| | | 502 | | { |
| | 0 | 503 | | return i + 1; |
| | | 504 | | } |
| | | 505 | | } |
| | 0 | 506 | | for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) |
| | | 507 | | { |
| | | 508 | | // this comparison should use the InvariantCulture. The English name could have linguistically |
| | | 509 | | // interesting characters. |
| | 0 | 510 | | if (CompareInfo.Invariant.Compare(eraName, m_abbrevEnglishEraNames![i], CompareOptions.IgnoreCase) == 0) |
| | | 511 | | { |
| | 0 | 512 | | return i + 1; |
| | | 513 | | } |
| | | 514 | | } |
| | 0 | 515 | | return -1; |
| | | 516 | | } |
| | | 517 | | |
| | 0 | 518 | | internal string[] EraNames => m_eraNames ??= _cultureData.EraNames(Calendar.ID); |
| | | 519 | | |
| | | 520 | | /// <summary> |
| | | 521 | | /// Get the name of the era for the specified era value. |
| | | 522 | | /// Era names are 1 indexed |
| | | 523 | | /// </summary> |
| | | 524 | | public string GetEraName(int era) |
| | | 525 | | { |
| | 0 | 526 | | if (era == Calendar.CurrentEra) |
| | | 527 | | { |
| | 0 | 528 | | era = Calendar.CurrentEraValue; |
| | | 529 | | } |
| | | 530 | | |
| | | 531 | | // The following is based on the assumption that the era value is starting from 1, and has a |
| | | 532 | | // serial values. If that ever changes, the code has to be changed. |
| | 0 | 533 | | string[] names = EraNames; |
| | 0 | 534 | | era--; |
| | 0 | 535 | | if ((uint)era >= names.Length) |
| | | 536 | | { |
| | 0 | 537 | | throw new ArgumentOutOfRangeException(nameof(era), era + 1, SR.ArgumentOutOfRange_InvalidEraValue); |
| | | 538 | | } |
| | | 539 | | |
| | 0 | 540 | | return names[era]; |
| | | 541 | | } |
| | | 542 | | |
| | 0 | 543 | | internal string[] AbbreviatedEraNames => m_abbrevEraNames ??= _cultureData.AbbrevEraNames(Calendar.ID); |
| | | 544 | | |
| | | 545 | | /// <remarks> |
| | | 546 | | /// Era names are 1 indexed |
| | | 547 | | /// </remarks> |
| | | 548 | | public string GetAbbreviatedEraName(int era) |
| | | 549 | | { |
| | 0 | 550 | | if (AbbreviatedEraNames.Length == 0) |
| | | 551 | | { |
| | | 552 | | // If abbreviation era name is not used in this culture, |
| | | 553 | | // return the full era name. |
| | 0 | 554 | | return GetEraName(era); |
| | | 555 | | } |
| | | 556 | | |
| | 0 | 557 | | if (era == Calendar.CurrentEra) |
| | | 558 | | { |
| | 0 | 559 | | era = Calendar.CurrentEraValue; |
| | | 560 | | } |
| | | 561 | | |
| | 0 | 562 | | string[] names = m_abbrevEraNames!; |
| | 0 | 563 | | era--; |
| | 0 | 564 | | if ((uint)era >= (uint)names.Length) |
| | | 565 | | { |
| | 0 | 566 | | throw new ArgumentOutOfRangeException(nameof(era), era + 1, SR.ArgumentOutOfRange_InvalidEraValue); |
| | | 567 | | } |
| | | 568 | | |
| | 0 | 569 | | return names[era]; |
| | | 570 | | } |
| | | 571 | | |
| | | 572 | | internal string[] AbbreviatedEnglishEraNames |
| | | 573 | | { |
| | | 574 | | get |
| | | 575 | | { |
| | 0 | 576 | | if (m_abbrevEnglishEraNames == null) |
| | | 577 | | { |
| | 0 | 578 | | Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID |
| | 0 | 579 | | m_abbrevEnglishEraNames = _cultureData.AbbreviatedEnglishEraNames(Calendar.ID); |
| | | 580 | | } |
| | 0 | 581 | | return m_abbrevEnglishEraNames; |
| | | 582 | | } |
| | | 583 | | } |
| | | 584 | | |
| | | 585 | | /// <remarks> |
| | | 586 | | /// Note that cultureData derives this from the short date format (unless someone's set this previously) |
| | | 587 | | /// Note that this property is quite undesirable. |
| | | 588 | | /// </remarks> |
| | | 589 | | public string DateSeparator |
| | | 590 | | { |
| | | 591 | | get |
| | | 592 | | { |
| | 0 | 593 | | dateSeparator ??= _cultureData.DateSeparator(Calendar.ID); |
| | 0 | 594 | | Debug.Assert(dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null"); |
| | 0 | 595 | | return dateSeparator; |
| | | 596 | | } |
| | | 597 | | set |
| | | 598 | | { |
| | 0 | 599 | | if (IsReadOnly) |
| | | 600 | | { |
| | 0 | 601 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 602 | | } |
| | 0 | 603 | | ArgumentNullException.ThrowIfNull(value); |
| | | 604 | | |
| | 0 | 605 | | ClearTokenHashTable(); |
| | 0 | 606 | | dateSeparator = value; |
| | 0 | 607 | | dateSeparatorUtf8 = null; |
| | 0 | 608 | | } |
| | | 609 | | } |
| | | 610 | | |
| | | 611 | | internal ReadOnlySpan<TChar> DateSeparatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar> |
| | | 612 | | { |
| | 0 | 613 | | Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); |
| | 0 | 614 | | return typeof(TChar) == typeof(char) ? |
| | 0 | 615 | | Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(DateSeparator) : |
| | 0 | 616 | | Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(dateSeparatorUtf8 ??= Encoding.UTF8.GetBytes(Dat |
| | | 617 | | } |
| | | 618 | | |
| | | 619 | | public DayOfWeek FirstDayOfWeek |
| | | 620 | | { |
| | | 621 | | get |
| | | 622 | | { |
| | 0 | 623 | | if (firstDayOfWeek == -1) |
| | | 624 | | { |
| | 0 | 625 | | firstDayOfWeek = _cultureData.FirstDayOfWeek; |
| | | 626 | | } |
| | 0 | 627 | | Debug.Assert(firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1"); |
| | | 628 | | |
| | 0 | 629 | | return (DayOfWeek)firstDayOfWeek; |
| | | 630 | | } |
| | | 631 | | set |
| | | 632 | | { |
| | 0 | 633 | | if (IsReadOnly) |
| | | 634 | | { |
| | 0 | 635 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 636 | | } |
| | | 637 | | |
| | 0 | 638 | | if (value < DayOfWeek.Sunday || value > DayOfWeek.Saturday) |
| | | 639 | | { |
| | 0 | 640 | | throw new ArgumentOutOfRangeException( |
| | 0 | 641 | | nameof(value), |
| | 0 | 642 | | value, |
| | 0 | 643 | | SR.Format(SR.ArgumentOutOfRange_Range, DayOfWeek.Sunday, DayOfWeek.Saturday)); |
| | | 644 | | } |
| | | 645 | | |
| | 0 | 646 | | firstDayOfWeek = (int)value; |
| | 0 | 647 | | } |
| | | 648 | | } |
| | | 649 | | |
| | | 650 | | public CalendarWeekRule CalendarWeekRule |
| | | 651 | | { |
| | | 652 | | get |
| | | 653 | | { |
| | 0 | 654 | | if (calendarWeekRule == -1) |
| | | 655 | | { |
| | 0 | 656 | | calendarWeekRule = _cultureData.CalendarWeekRule; |
| | | 657 | | } |
| | | 658 | | |
| | 0 | 659 | | Debug.Assert(calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1"); |
| | 0 | 660 | | return (CalendarWeekRule)calendarWeekRule; |
| | | 661 | | } |
| | | 662 | | set |
| | | 663 | | { |
| | 0 | 664 | | if (IsReadOnly) |
| | | 665 | | { |
| | 0 | 666 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 667 | | } |
| | 0 | 668 | | if (value < CalendarWeekRule.FirstDay || value > CalendarWeekRule.FirstFourDayWeek) |
| | | 669 | | { |
| | 0 | 670 | | throw new ArgumentOutOfRangeException( |
| | 0 | 671 | | nameof(value), |
| | 0 | 672 | | value, |
| | 0 | 673 | | SR.Format(SR.ArgumentOutOfRange_Range, CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayW |
| | | 674 | | } |
| | | 675 | | |
| | 0 | 676 | | calendarWeekRule = (int)value; |
| | 0 | 677 | | } |
| | | 678 | | } |
| | | 679 | | |
| | | 680 | | public string FullDateTimePattern |
| | | 681 | | { |
| | 0 | 682 | | get => fullDateTimePattern ??= LongDatePattern + " " + LongTimePattern; |
| | | 683 | | set |
| | | 684 | | { |
| | 0 | 685 | | if (IsReadOnly) |
| | | 686 | | { |
| | 0 | 687 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 688 | | } |
| | 0 | 689 | | ArgumentNullException.ThrowIfNull(value); |
| | | 690 | | |
| | 0 | 691 | | fullDateTimePattern = value; |
| | 0 | 692 | | } |
| | | 693 | | } |
| | | 694 | | |
| | | 695 | | /// <summary> |
| | | 696 | | /// For our "patterns" arrays we have 2 variables, a string and a string[] |
| | | 697 | | /// The string[] contains the list of patterns, EXCEPT the default may not be included. |
| | | 698 | | /// The string contains the default pattern. |
| | | 699 | | /// When we initially construct our string[], we set the string to string[0] |
| | | 700 | | /// </summary> |
| | | 701 | | public string LongDatePattern |
| | | 702 | | { |
| | 0 | 703 | | get => longDatePattern ??= UnclonedLongDatePatterns[0]; // initialize from the 1st array value if not set |
| | | 704 | | set |
| | | 705 | | { |
| | 0 | 706 | | if (IsReadOnly) |
| | | 707 | | { |
| | 0 | 708 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 709 | | } |
| | 0 | 710 | | ArgumentNullException.ThrowIfNull(value); |
| | | 711 | | |
| | | 712 | | // Remember the new string |
| | 0 | 713 | | longDatePattern = value; |
| | | 714 | | |
| | 0 | 715 | | OnLongDatePatternChanged(); |
| | 0 | 716 | | } |
| | | 717 | | } |
| | | 718 | | |
| | | 719 | | private void OnLongDatePatternChanged() |
| | | 720 | | { |
| | | 721 | | // Clear the token hash table |
| | 0 | 722 | | ClearTokenHashTable(); |
| | | 723 | | |
| | | 724 | | // Clean up cached values that will be affected by this property. |
| | 0 | 725 | | fullDateTimePattern = null; |
| | 0 | 726 | | } |
| | | 727 | | |
| | | 728 | | /// <summary> |
| | | 729 | | /// For our "patterns" arrays we have 2 variables, a string and a string[] |
| | | 730 | | /// |
| | | 731 | | /// The string[] contains the list of patterns, EXCEPT the default may not be included. |
| | | 732 | | /// The string contains the default pattern. |
| | | 733 | | /// When we initially construct our string[], we set the string to string[0] |
| | | 734 | | /// </summary> |
| | | 735 | | public string LongTimePattern |
| | | 736 | | { |
| | 0 | 737 | | get => longTimePattern ??= UnclonedLongTimePatterns[0]; // initialize from the 1st array value if not set |
| | | 738 | | set |
| | | 739 | | { |
| | 0 | 740 | | if (IsReadOnly) |
| | | 741 | | { |
| | 0 | 742 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 743 | | } |
| | 0 | 744 | | ArgumentNullException.ThrowIfNull(value); |
| | | 745 | | |
| | | 746 | | // Remember the new string |
| | 0 | 747 | | longTimePattern = value; |
| | | 748 | | |
| | 0 | 749 | | OnLongTimePatternChanged(); |
| | 0 | 750 | | } |
| | | 751 | | } |
| | | 752 | | |
| | | 753 | | private void OnLongTimePatternChanged() |
| | | 754 | | { |
| | | 755 | | // Clear the token hash table |
| | 0 | 756 | | ClearTokenHashTable(); |
| | | 757 | | |
| | | 758 | | // Clean up cached values that will be affected by this property. |
| | 0 | 759 | | fullDateTimePattern = null; // Full date = long date + long Time |
| | 0 | 760 | | generalLongTimePattern = null; // General long date = short date + long Time |
| | 0 | 761 | | dateTimeOffsetPattern = null; |
| | 0 | 762 | | } |
| | | 763 | | |
| | | 764 | | /// <remarks> |
| | | 765 | | /// Just to be confusing there's only 1 month day pattern, not a whole list |
| | | 766 | | /// </remarks> |
| | | 767 | | public string MonthDayPattern |
| | | 768 | | { |
| | | 769 | | get |
| | | 770 | | { |
| | 0 | 771 | | if (monthDayPattern == null) |
| | | 772 | | { |
| | 0 | 773 | | Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0"); |
| | 0 | 774 | | monthDayPattern = _cultureData.MonthDay(Calendar.ID); |
| | | 775 | | } |
| | | 776 | | |
| | 0 | 777 | | Debug.Assert(monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null"); |
| | 0 | 778 | | return monthDayPattern; |
| | | 779 | | } |
| | | 780 | | set |
| | | 781 | | { |
| | 0 | 782 | | if (IsReadOnly) |
| | | 783 | | { |
| | 0 | 784 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 785 | | } |
| | 0 | 786 | | ArgumentNullException.ThrowIfNull(value); |
| | | 787 | | |
| | 0 | 788 | | monthDayPattern = value; |
| | 0 | 789 | | } |
| | | 790 | | } |
| | | 791 | | |
| | | 792 | | public string PMDesignator |
| | | 793 | | { |
| | | 794 | | get |
| | | 795 | | { |
| | 0 | 796 | | pmDesignator ??= _cultureData.PMDesignator; |
| | 0 | 797 | | Debug.Assert(pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null"); |
| | 0 | 798 | | return pmDesignator; |
| | | 799 | | } |
| | | 800 | | set |
| | | 801 | | { |
| | 0 | 802 | | if (IsReadOnly) |
| | | 803 | | { |
| | 0 | 804 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 805 | | } |
| | 0 | 806 | | ArgumentNullException.ThrowIfNull(value); |
| | | 807 | | |
| | 0 | 808 | | ClearTokenHashTable(); |
| | 0 | 809 | | pmDesignator = value; |
| | 0 | 810 | | pmDesignatorUtf8 = null; |
| | 0 | 811 | | } |
| | | 812 | | } |
| | | 813 | | |
| | | 814 | | internal ReadOnlySpan<TChar> PMDesignatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar> |
| | | 815 | | { |
| | 0 | 816 | | Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); |
| | 0 | 817 | | return typeof(TChar) == typeof(char) ? |
| | 0 | 818 | | Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(PMDesignator) : |
| | 0 | 819 | | Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(pmDesignatorUtf8 ??= Encoding.UTF8.GetBytes(PMDe |
| | | 820 | | } |
| | | 821 | | |
| | 0 | 822 | | public string RFC1123Pattern => rfc1123Pattern; |
| | | 823 | | |
| | | 824 | | /// <summary> |
| | | 825 | | /// For our "patterns" arrays we have 2 variables, a string and a string[] |
| | | 826 | | /// |
| | | 827 | | /// The string[] contains the list of patterns, EXCEPT the default may not be included. |
| | | 828 | | /// The string contains the default pattern. |
| | | 829 | | /// When we initially construct our string[], we set the string to string[0] |
| | | 830 | | /// </summary> |
| | | 831 | | public string ShortDatePattern |
| | | 832 | | { |
| | 0 | 833 | | get => shortDatePattern ??= UnclonedShortDatePatterns[0]; // initialize from the 1st array value if not set |
| | | 834 | | set |
| | | 835 | | { |
| | 0 | 836 | | if (IsReadOnly) |
| | | 837 | | { |
| | 0 | 838 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 839 | | } |
| | 0 | 840 | | ArgumentNullException.ThrowIfNull(value); |
| | | 841 | | |
| | | 842 | | // Remember the new string |
| | 0 | 843 | | shortDatePattern = value; |
| | | 844 | | |
| | 0 | 845 | | OnShortDatePatternChanged(); |
| | 0 | 846 | | } |
| | | 847 | | } |
| | | 848 | | |
| | | 849 | | private void OnShortDatePatternChanged() |
| | | 850 | | { |
| | | 851 | | // Clear the token hash table, note that even short dates could require this |
| | 0 | 852 | | ClearTokenHashTable(); |
| | | 853 | | |
| | | 854 | | // Clean up cached values that will be affected by this property. |
| | 0 | 855 | | generalLongTimePattern = null; // General long time = short date + long time |
| | 0 | 856 | | generalShortTimePattern = null; // General short time = short date + short Time |
| | 0 | 857 | | dateTimeOffsetPattern = null; |
| | 0 | 858 | | } |
| | | 859 | | |
| | | 860 | | /// <summary> |
| | | 861 | | /// For our "patterns" arrays we have 2 variables, a string and a string[] |
| | | 862 | | /// |
| | | 863 | | /// The string[] contains the list of patterns, EXCEPT the default may not be included. |
| | | 864 | | /// The string contains the default pattern. |
| | | 865 | | /// When we initially construct our string[], we set the string to string[0] |
| | | 866 | | /// </summary> |
| | | 867 | | public string ShortTimePattern |
| | | 868 | | { |
| | 0 | 869 | | get => shortTimePattern ??= UnclonedShortTimePatterns[0]; // initialize from the 1st array value if not set |
| | | 870 | | set |
| | | 871 | | { |
| | 0 | 872 | | if (IsReadOnly) |
| | | 873 | | { |
| | 0 | 874 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 875 | | } |
| | 0 | 876 | | ArgumentNullException.ThrowIfNull(value); |
| | | 877 | | |
| | | 878 | | // Remember the new string |
| | 0 | 879 | | shortTimePattern = value; |
| | | 880 | | |
| | 0 | 881 | | OnShortTimePatternChanged(); |
| | 0 | 882 | | } |
| | | 883 | | } |
| | | 884 | | |
| | | 885 | | private void OnShortTimePatternChanged() |
| | | 886 | | { |
| | | 887 | | // Clear the token hash table, note that even short times could require this |
| | 0 | 888 | | ClearTokenHashTable(); |
| | | 889 | | |
| | | 890 | | // Clean up cached values that will be affected by this property. |
| | 0 | 891 | | generalShortTimePattern = null; // General short date = short date + short time. |
| | 0 | 892 | | } |
| | | 893 | | |
| | 0 | 894 | | public string SortableDateTimePattern => sortableDateTimePattern; |
| | | 895 | | |
| | | 896 | | /// <summary> |
| | | 897 | | /// Return the pattern for 'g' general format: shortDate + short time |
| | | 898 | | /// This is used by DateTimeFormat.cs to get the pattern for 'g'. |
| | | 899 | | /// We put this internal property here so that we can avoid doing the |
| | | 900 | | /// concatation every time somebody asks for the general format. |
| | | 901 | | /// </summary> |
| | 0 | 902 | | internal string GeneralShortTimePattern => generalShortTimePattern ??= ShortDatePattern + " " + ShortTimePattern |
| | | 903 | | |
| | | 904 | | /// <summary> |
| | | 905 | | /// Return the pattern for 'G' general format: shortDate + Long time. |
| | | 906 | | /// We put this internal property here so that we can avoid doing the |
| | | 907 | | /// concatation every time somebody asks for the general format. |
| | | 908 | | /// </summary> |
| | 0 | 909 | | internal string GeneralLongTimePattern => generalLongTimePattern ??= ShortDatePattern + " " + LongTimePattern; |
| | | 910 | | |
| | | 911 | | /// Return the default pattern DateTimeOffset : shortDate + long time + time zone offset. |
| | | 912 | | /// This is used by DateTimeFormat.cs to get the pattern for short Date + long time + time zone offset |
| | | 913 | | /// We put this internal property here so that we can avoid doing the |
| | | 914 | | /// concatation every time somebody uses this form. |
| | | 915 | | internal string DateTimeOffsetPattern |
| | | 916 | | { |
| | | 917 | | get |
| | | 918 | | { |
| | 0 | 919 | | if (dateTimeOffsetPattern == null) |
| | | 920 | | { |
| | | 921 | | /* LongTimePattern might contain a "z" as part of the format string in which case we don't want to a |
| | | 922 | | |
| | 0 | 923 | | bool foundZ = false; |
| | 0 | 924 | | bool inQuote = false; |
| | 0 | 925 | | char quote = '\''; |
| | 0 | 926 | | string longTimePattern = LongTimePattern; |
| | 0 | 927 | | for (int i = 0; !foundZ && i < longTimePattern.Length; i++) |
| | | 928 | | { |
| | 0 | 929 | | switch (longTimePattern[i]) |
| | | 930 | | { |
| | | 931 | | case 'z': |
| | | 932 | | /* if we aren't in a quote, we've found a z */ |
| | 0 | 933 | | foundZ = !inQuote; |
| | | 934 | | /* we'll fall out of the loop now because the test includes !foundZ */ |
| | 0 | 935 | | break; |
| | | 936 | | case '\'': |
| | | 937 | | case '\"': |
| | 0 | 938 | | if (inQuote && (quote == longTimePattern[i])) |
| | | 939 | | { |
| | | 940 | | /* we were in a quote and found a matching exit quote, so we are outside a quote now |
| | 0 | 941 | | inQuote = false; |
| | | 942 | | } |
| | 0 | 943 | | else if (!inQuote) |
| | | 944 | | { |
| | 0 | 945 | | quote = longTimePattern[i]; |
| | 0 | 946 | | inQuote = true; |
| | | 947 | | } |
| | | 948 | | else |
| | | 949 | | { |
| | | 950 | | /* we were in a quote and saw the other type of quote character, so we are still in |
| | | 951 | | } |
| | | 952 | | break; |
| | | 953 | | case '%': |
| | | 954 | | case '\\': |
| | 0 | 955 | | i++; /* skip next character that is escaped by this backslash */ |
| | | 956 | | break; |
| | | 957 | | default: |
| | | 958 | | break; |
| | | 959 | | } |
| | | 960 | | } |
| | | 961 | | |
| | 0 | 962 | | dateTimeOffsetPattern = foundZ ? |
| | 0 | 963 | | ShortDatePattern + " " + longTimePattern : |
| | 0 | 964 | | ShortDatePattern + " " + longTimePattern + " zzz"; |
| | | 965 | | } |
| | 0 | 966 | | return dateTimeOffsetPattern; |
| | | 967 | | } |
| | | 968 | | } |
| | | 969 | | |
| | | 970 | | /// <remarks> |
| | | 971 | | /// Note that cultureData derives this from the long time format (unless someone's set this previously) |
| | | 972 | | /// Note that this property is quite undesirable. |
| | | 973 | | /// </remarks> |
| | | 974 | | public string TimeSeparator |
| | | 975 | | { |
| | | 976 | | get |
| | | 977 | | { |
| | 0 | 978 | | timeSeparator ??= _cultureData.TimeSeparator; |
| | 0 | 979 | | Debug.Assert(timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null"); |
| | 0 | 980 | | return timeSeparator; |
| | | 981 | | } |
| | | 982 | | set |
| | | 983 | | { |
| | 0 | 984 | | if (IsReadOnly) |
| | | 985 | | { |
| | 0 | 986 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 987 | | } |
| | 0 | 988 | | ArgumentNullException.ThrowIfNull(value); |
| | | 989 | | |
| | 0 | 990 | | ClearTokenHashTable(); |
| | 0 | 991 | | timeSeparator = value; |
| | 0 | 992 | | timeSeparatorUtf8 = null; |
| | 0 | 993 | | } |
| | | 994 | | } |
| | | 995 | | |
| | | 996 | | internal ReadOnlySpan<TChar> TimeSeparatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar> |
| | | 997 | | { |
| | 0 | 998 | | Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); |
| | 0 | 999 | | return typeof(TChar) == typeof(char) ? |
| | 0 | 1000 | | Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(TimeSeparator) : |
| | 0 | 1001 | | Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(timeSeparatorUtf8 ??= Encoding.UTF8.GetBytes(Tim |
| | | 1002 | | } |
| | | 1003 | | |
| | 0 | 1004 | | public string UniversalSortableDateTimePattern => universalSortableDateTimePattern; |
| | | 1005 | | |
| | | 1006 | | /// <summary> |
| | | 1007 | | /// For our "patterns" arrays we have 2 variables, a string and a string[] |
| | | 1008 | | /// |
| | | 1009 | | /// The string[] contains the list of patterns, EXCEPT the default may not be included. |
| | | 1010 | | /// The string contains the default pattern. |
| | | 1011 | | /// When we initially construct our string[], we set the string to string[0] |
| | | 1012 | | /// </summary> |
| | | 1013 | | public string YearMonthPattern |
| | | 1014 | | { |
| | 0 | 1015 | | get => yearMonthPattern ??= UnclonedYearMonthPatterns[0]; // initialize from the 1st array value if not set |
| | | 1016 | | set |
| | | 1017 | | { |
| | 0 | 1018 | | if (IsReadOnly) |
| | | 1019 | | { |
| | 0 | 1020 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 1021 | | } |
| | 0 | 1022 | | ArgumentNullException.ThrowIfNull(value); |
| | | 1023 | | |
| | | 1024 | | // Remember the new string |
| | 0 | 1025 | | yearMonthPattern = value; |
| | | 1026 | | |
| | 0 | 1027 | | OnYearMonthPatternChanged(); |
| | 0 | 1028 | | } |
| | | 1029 | | } |
| | | 1030 | | |
| | | 1031 | | private void OnYearMonthPatternChanged() |
| | | 1032 | | { |
| | | 1033 | | // Clear the token hash table, note that even short times could require this |
| | 0 | 1034 | | ClearTokenHashTable(); |
| | 0 | 1035 | | } |
| | | 1036 | | |
| | | 1037 | | /// <summary> |
| | | 1038 | | /// Check if a string array contains a null value, and throw ArgumentNullException with parameter name "value" |
| | | 1039 | | /// </summary> |
| | | 1040 | | private static void CheckNullValue(string[] values, int length) |
| | | 1041 | | { |
| | 0 | 1042 | | Debug.Assert(values != null, "value != null"); |
| | 0 | 1043 | | Debug.Assert(values.Length >= length); |
| | 0 | 1044 | | for (int i = 0; i < length; i++) |
| | | 1045 | | { |
| | 0 | 1046 | | if (values[i] == null) |
| | | 1047 | | { |
| | 0 | 1048 | | throw new ArgumentNullException("value", SR.ArgumentNull_ArrayValue); |
| | | 1049 | | } |
| | | 1050 | | } |
| | 0 | 1051 | | } |
| | | 1052 | | |
| | | 1053 | | public string[] AbbreviatedDayNames |
| | | 1054 | | { |
| | 0 | 1055 | | get => (string[])InternalGetAbbreviatedDayOfWeekNames().Clone(); |
| | | 1056 | | set |
| | | 1057 | | { |
| | 0 | 1058 | | if (IsReadOnly) |
| | | 1059 | | { |
| | 0 | 1060 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 1061 | | } |
| | 0 | 1062 | | ArgumentNullException.ThrowIfNull(value); |
| | 0 | 1063 | | if (value.Length != 7) |
| | | 1064 | | { |
| | 0 | 1065 | | throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); |
| | | 1066 | | } |
| | | 1067 | | |
| | 0 | 1068 | | CheckNullValue(value, value.Length); |
| | 0 | 1069 | | ClearTokenHashTable(); |
| | | 1070 | | |
| | 0 | 1071 | | abbreviatedDayNames = value; |
| | 0 | 1072 | | } |
| | | 1073 | | } |
| | | 1074 | | |
| | | 1075 | | /// <summary> |
| | | 1076 | | /// Returns the string array of the one-letter day of week names. |
| | | 1077 | | /// </summary> |
| | | 1078 | | public string[] ShortestDayNames |
| | | 1079 | | { |
| | 0 | 1080 | | get => (string[])InternalGetSuperShortDayNames().Clone(); |
| | | 1081 | | set |
| | | 1082 | | { |
| | 0 | 1083 | | if (IsReadOnly) |
| | | 1084 | | { |
| | 0 | 1085 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 1086 | | } |
| | 0 | 1087 | | ArgumentNullException.ThrowIfNull(value); |
| | 0 | 1088 | | if (value.Length != 7) |
| | | 1089 | | { |
| | 0 | 1090 | | throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); |
| | | 1091 | | } |
| | | 1092 | | |
| | 0 | 1093 | | CheckNullValue(value, value.Length); |
| | 0 | 1094 | | m_superShortDayNames = value; |
| | 0 | 1095 | | } |
| | | 1096 | | } |
| | | 1097 | | |
| | | 1098 | | public string[] DayNames |
| | | 1099 | | { |
| | 0 | 1100 | | get => (string[])InternalGetDayOfWeekNames().Clone(); |
| | | 1101 | | set |
| | | 1102 | | { |
| | 0 | 1103 | | if (IsReadOnly) |
| | | 1104 | | { |
| | 0 | 1105 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 1106 | | } |
| | 0 | 1107 | | ArgumentNullException.ThrowIfNull(value); |
| | 0 | 1108 | | if (value.Length != 7) |
| | | 1109 | | { |
| | 0 | 1110 | | throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); |
| | | 1111 | | } |
| | | 1112 | | |
| | 0 | 1113 | | CheckNullValue(value, value.Length); |
| | 0 | 1114 | | ClearTokenHashTable(); |
| | | 1115 | | |
| | 0 | 1116 | | dayNames = value; |
| | 0 | 1117 | | } |
| | | 1118 | | } |
| | | 1119 | | |
| | | 1120 | | public string[] AbbreviatedMonthNames |
| | | 1121 | | { |
| | 0 | 1122 | | get => (string[])InternalGetAbbreviatedMonthNames().Clone(); |
| | | 1123 | | set |
| | | 1124 | | { |
| | 0 | 1125 | | if (IsReadOnly) |
| | | 1126 | | { |
| | 0 | 1127 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 1128 | | } |
| | 0 | 1129 | | ArgumentNullException.ThrowIfNull(value); |
| | 0 | 1130 | | if (value.Length != 13) |
| | | 1131 | | { |
| | 0 | 1132 | | throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); |
| | | 1133 | | } |
| | | 1134 | | |
| | 0 | 1135 | | CheckNullValue(value, value.Length - 1); |
| | 0 | 1136 | | ClearTokenHashTable(); |
| | 0 | 1137 | | abbreviatedMonthNames = value; |
| | 0 | 1138 | | } |
| | | 1139 | | } |
| | | 1140 | | |
| | | 1141 | | public string[] MonthNames |
| | | 1142 | | { |
| | 0 | 1143 | | get => (string[])InternalGetMonthNames().Clone(); |
| | | 1144 | | set |
| | | 1145 | | { |
| | 0 | 1146 | | if (IsReadOnly) |
| | | 1147 | | { |
| | 0 | 1148 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 1149 | | } |
| | 0 | 1150 | | ArgumentNullException.ThrowIfNull(value); |
| | 0 | 1151 | | if (value.Length != 13) |
| | | 1152 | | { |
| | 0 | 1153 | | throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); |
| | | 1154 | | } |
| | | 1155 | | |
| | 0 | 1156 | | CheckNullValue(value, value.Length - 1); |
| | 0 | 1157 | | monthNames = value; |
| | 0 | 1158 | | ClearTokenHashTable(); |
| | 0 | 1159 | | } |
| | | 1160 | | } |
| | | 1161 | | |
| | 0 | 1162 | | internal bool HasSpacesInMonthNames => (FormatFlags & DateTimeFormatFlags.UseSpacesInMonthNames) != 0; |
| | | 1163 | | |
| | 0 | 1164 | | internal bool HasSpacesInDayNames => (FormatFlags & DateTimeFormatFlags.UseSpacesInDayNames) != 0; |
| | | 1165 | | |
| | | 1166 | | /// <summary> |
| | | 1167 | | /// Return the month name using the specified MonthNameStyles in either abbreviated form |
| | | 1168 | | /// or full form. |
| | | 1169 | | /// </summary> |
| | | 1170 | | internal string InternalGetMonthName(int month, MonthNameStyles style, bool abbreviated) |
| | | 1171 | | { |
| | 0 | 1172 | | string[] monthNamesArray = style switch |
| | 0 | 1173 | | { |
| | 0 | 1174 | | MonthNameStyles.Genitive => InternalGetGenitiveMonthNames(abbreviated), |
| | 0 | 1175 | | MonthNameStyles.LeapYear => InternalGetLeapYearMonthNames(), |
| | 0 | 1176 | | _ => (abbreviated ? InternalGetAbbreviatedMonthNames() : InternalGetMonthNames()), |
| | 0 | 1177 | | }; |
| | | 1178 | | |
| | | 1179 | | // The month range is from 1 ~ m_monthNames.Length |
| | | 1180 | | // (actually is 13 right now for all cases) |
| | 0 | 1181 | | month--; |
| | 0 | 1182 | | if ((uint)month >= (uint)monthNamesArray.Length) |
| | | 1183 | | { |
| | 0 | 1184 | | ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(month), month + 1, 1, monthNamesArray.Length); |
| | | 1185 | | } |
| | | 1186 | | |
| | 0 | 1187 | | return monthNamesArray[month]; |
| | | 1188 | | } |
| | | 1189 | | |
| | | 1190 | | /// <summary> |
| | | 1191 | | /// Retrieve the array which contains the month names in genitive form. |
| | | 1192 | | /// If this culture does not use the genitive form, the normal month name is returned. |
| | | 1193 | | /// </summary> |
| | | 1194 | | internal string[] InternalGetGenitiveMonthNames(bool abbreviated) |
| | | 1195 | | { |
| | 0 | 1196 | | if (abbreviated) |
| | | 1197 | | { |
| | 0 | 1198 | | if (m_genitiveAbbreviatedMonthNames == null) |
| | | 1199 | | { |
| | 0 | 1200 | | m_genitiveAbbreviatedMonthNames = _cultureData.AbbreviatedGenitiveMonthNames(Calendar.ID); |
| | 0 | 1201 | | Debug.Assert(m_genitiveAbbreviatedMonthNames.Length == 13, |
| | 0 | 1202 | | "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a ye |
| | | 1203 | | } |
| | | 1204 | | |
| | 0 | 1205 | | return m_genitiveAbbreviatedMonthNames; |
| | | 1206 | | } |
| | | 1207 | | |
| | 0 | 1208 | | if (genitiveMonthNames == null) |
| | | 1209 | | { |
| | 0 | 1210 | | genitiveMonthNames = _cultureData.GenitiveMonthNames(Calendar.ID); |
| | 0 | 1211 | | Debug.Assert(genitiveMonthNames.Length == 13, |
| | 0 | 1212 | | "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year"); |
| | | 1213 | | } |
| | | 1214 | | |
| | 0 | 1215 | | return genitiveMonthNames; |
| | | 1216 | | } |
| | | 1217 | | |
| | | 1218 | | /// <summary> |
| | | 1219 | | /// Retrieve the month names used in a leap year. |
| | | 1220 | | /// If this culture does not have different month names in a leap year, |
| | | 1221 | | /// the normal month name is returned. |
| | | 1222 | | /// </summary> |
| | | 1223 | | internal string[] InternalGetLeapYearMonthNames() |
| | | 1224 | | { |
| | 0 | 1225 | | if (leapYearMonthNames == null) |
| | | 1226 | | { |
| | 0 | 1227 | | Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.InternalGetLeapYearMonthNames] Expected Calendar.ID > |
| | 0 | 1228 | | leapYearMonthNames = _cultureData.LeapYearMonthNames(Calendar.ID); |
| | 0 | 1229 | | Debug.Assert(leapYearMonthNames.Length == 13, |
| | 0 | 1230 | | "[DateTimeFormatInfo.InternalGetLeapYearMonthNames] Expepcted 13 leap year month names"); |
| | | 1231 | | } |
| | | 1232 | | |
| | 0 | 1233 | | return leapYearMonthNames; |
| | | 1234 | | } |
| | | 1235 | | |
| | | 1236 | | public string GetAbbreviatedDayName(DayOfWeek dayofweek) |
| | | 1237 | | { |
| | 0 | 1238 | | string[] names = InternalGetAbbreviatedDayOfWeekNames(); // Use the internal method to avoid a clone. |
| | 0 | 1239 | | int dow = (int)dayofweek; |
| | 0 | 1240 | | if ((uint)dow >= (uint)names.Length) |
| | | 1241 | | { |
| | 0 | 1242 | | ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(dayofweek), dayofweek, DayOfWeek.Sunday, DayOfWeek.Satu |
| | | 1243 | | } |
| | | 1244 | | |
| | 0 | 1245 | | return names[dow]; |
| | | 1246 | | } |
| | | 1247 | | |
| | | 1248 | | /// <summary> |
| | | 1249 | | /// Returns the super short day of week names for the specified day of week. |
| | | 1250 | | /// </summary> |
| | | 1251 | | public string GetShortestDayName(DayOfWeek dayOfWeek) |
| | | 1252 | | { |
| | 0 | 1253 | | string[] names = InternalGetSuperShortDayNames(); |
| | 0 | 1254 | | int dow = (int)dayOfWeek; |
| | 0 | 1255 | | if ((uint)dow >= (uint)names.Length) |
| | | 1256 | | { |
| | 0 | 1257 | | ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(dayOfWeek), dayOfWeek, DayOfWeek.Sunday, DayOfWeek.Satu |
| | | 1258 | | } |
| | | 1259 | | |
| | 0 | 1260 | | return names[dow]; |
| | | 1261 | | } |
| | | 1262 | | |
| | | 1263 | | /// <summary> |
| | | 1264 | | /// Get all possible combination of inputs |
| | | 1265 | | /// </summary> |
| | | 1266 | | private static string[] GetCombinedPatterns(string[] patterns1, string[] patterns2, string connectString) |
| | | 1267 | | { |
| | 0 | 1268 | | Debug.Assert(patterns1 != null); |
| | 0 | 1269 | | Debug.Assert(patterns2 != null); |
| | | 1270 | | |
| | | 1271 | | // Get array size |
| | 0 | 1272 | | string[] result = new string[patterns1.Length * patterns2.Length]; |
| | | 1273 | | |
| | | 1274 | | // Counter of actual results |
| | 0 | 1275 | | int k = 0; |
| | 0 | 1276 | | for (int i = 0; i < patterns1.Length; i++) |
| | | 1277 | | { |
| | 0 | 1278 | | for (int j = 0; j < patterns2.Length; j++) |
| | | 1279 | | { |
| | | 1280 | | // Can't combine if null or empty |
| | 0 | 1281 | | result[k++] = patterns1[i] + connectString + patterns2[j]; |
| | | 1282 | | } |
| | | 1283 | | } |
| | | 1284 | | |
| | | 1285 | | // Return the combinations |
| | 0 | 1286 | | return result; |
| | | 1287 | | } |
| | | 1288 | | |
| | | 1289 | | public string[] GetAllDateTimePatterns() |
| | | 1290 | | { |
| | 0 | 1291 | | List<string> results = new List<string>(DEFAULT_ALL_DATETIMES_SIZE); |
| | | 1292 | | |
| | 0 | 1293 | | foreach (char standardFormat in DateTimeFormat.AllStandardFormats) |
| | | 1294 | | { |
| | 0 | 1295 | | foreach (string pattern in GetAllDateTimePatterns(standardFormat)) |
| | | 1296 | | { |
| | 0 | 1297 | | results.Add(pattern); |
| | | 1298 | | } |
| | | 1299 | | } |
| | | 1300 | | |
| | 0 | 1301 | | return results.ToArray(); |
| | | 1302 | | } |
| | | 1303 | | |
| | | 1304 | | public string[] GetAllDateTimePatterns(char format) => |
| | 0 | 1305 | | format switch |
| | 0 | 1306 | | { |
| | 0 | 1307 | | 'd' => AllShortDatePatterns, |
| | 0 | 1308 | | 'D' => AllLongDatePatterns, |
| | 0 | 1309 | | 'f' => GetCombinedPatterns(AllLongDatePatterns, AllShortTimePatterns, " "), |
| | 0 | 1310 | | 'F' or 'U' => GetCombinedPatterns(AllLongDatePatterns, AllLongTimePatterns, " "), |
| | 0 | 1311 | | 'g' => GetCombinedPatterns(AllShortDatePatterns, AllShortTimePatterns, " "), |
| | 0 | 1312 | | 'G' => GetCombinedPatterns(AllShortDatePatterns, AllLongTimePatterns, " "), |
| | 0 | 1313 | | 'm' or 'M' => [MonthDayPattern], |
| | 0 | 1314 | | 'o' or 'O' => [RoundtripFormat], |
| | 0 | 1315 | | 'r' or 'R' => [rfc1123Pattern], |
| | 0 | 1316 | | 's' => [sortableDateTimePattern], |
| | 0 | 1317 | | 't' => AllShortTimePatterns, |
| | 0 | 1318 | | 'T' => AllLongTimePatterns, |
| | 0 | 1319 | | 'u' => [UniversalSortableDateTimePattern], |
| | 0 | 1320 | | 'y' or 'Y' => AllYearMonthPatterns, |
| | 0 | 1321 | | _ => throw new ArgumentException(SR.Format(SR.Format_BadFormatSpecifier, format), nameof(format)), |
| | 0 | 1322 | | }; |
| | | 1323 | | |
| | | 1324 | | public string GetDayName(DayOfWeek dayofweek) |
| | | 1325 | | { |
| | 0 | 1326 | | string[] names = InternalGetDayOfWeekNames(); // Use the internal method so we don't clone the array unneces |
| | 0 | 1327 | | int dow = (int)dayofweek; |
| | 0 | 1328 | | if ((uint)dow >= (uint)names.Length) |
| | | 1329 | | { |
| | 0 | 1330 | | ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(dayofweek), dayofweek, DayOfWeek.Sunday, DayOfWeek.Satu |
| | | 1331 | | } |
| | | 1332 | | |
| | 0 | 1333 | | return names[dow]; |
| | | 1334 | | } |
| | | 1335 | | |
| | | 1336 | | public string GetAbbreviatedMonthName(int month) |
| | | 1337 | | { |
| | 0 | 1338 | | string[] names = InternalGetAbbreviatedMonthNames(); // Use the internal method so we don't clone the array |
| | 0 | 1339 | | month--; |
| | 0 | 1340 | | if ((uint)month >= (uint)names.Length) |
| | | 1341 | | { |
| | 0 | 1342 | | ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(month), month + 1, 1, 13); |
| | | 1343 | | } |
| | | 1344 | | |
| | 0 | 1345 | | return names[month]; |
| | | 1346 | | } |
| | | 1347 | | |
| | | 1348 | | public string GetMonthName(int month) |
| | | 1349 | | { |
| | 0 | 1350 | | string[] names = InternalGetMonthNames(); // Use the internal method so we don't clone the array unnecessari |
| | 0 | 1351 | | month--; |
| | 0 | 1352 | | if ((uint)month >= (uint)names.Length) |
| | | 1353 | | { |
| | 0 | 1354 | | ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(month), month + 1, 1, 13); |
| | | 1355 | | } |
| | | 1356 | | |
| | 0 | 1357 | | return names[month]; |
| | | 1358 | | } |
| | | 1359 | | |
| | | 1360 | | /// <summary> |
| | | 1361 | | /// For our "patterns" arrays we have 2 variables, a string and a string[] |
| | | 1362 | | /// |
| | | 1363 | | /// The string[] contains the list of patterns, EXCEPT the default may not be included. |
| | | 1364 | | /// The string contains the default pattern. |
| | | 1365 | | /// When we initially construct our string[], we set the string to string[0] |
| | | 1366 | | /// |
| | | 1367 | | /// The resulting [] can get returned to the calling app, so clone it. |
| | | 1368 | | /// </summary> |
| | | 1369 | | private static string[] GetMergedPatterns(string[] patterns, string defaultPattern) |
| | | 1370 | | { |
| | 0 | 1371 | | Debug.Assert(patterns != null && patterns.Length > 0, |
| | 0 | 1372 | | "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern"); |
| | 0 | 1373 | | Debug.Assert(defaultPattern != null, |
| | 0 | 1374 | | "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string"); |
| | | 1375 | | |
| | | 1376 | | // If the default happens to be the first in the list just return (a cloned) copy |
| | 0 | 1377 | | if (defaultPattern == patterns[0]) |
| | | 1378 | | { |
| | 0 | 1379 | | return (string[])patterns.Clone(); |
| | | 1380 | | } |
| | | 1381 | | |
| | | 1382 | | // We either need a bigger list, or the pattern from the list. |
| | | 1383 | | int i; |
| | 0 | 1384 | | for (i = 0; i < patterns.Length; i++) |
| | | 1385 | | { |
| | | 1386 | | // Stop if we found it |
| | 0 | 1387 | | if (defaultPattern == patterns[i]) |
| | | 1388 | | break; |
| | | 1389 | | } |
| | | 1390 | | |
| | | 1391 | | // Either way we're going to need a new array |
| | | 1392 | | string[] newPatterns; |
| | | 1393 | | |
| | | 1394 | | // Did we find it |
| | 0 | 1395 | | if (i < patterns.Length) |
| | | 1396 | | { |
| | | 1397 | | // Found it, output will be same size |
| | 0 | 1398 | | newPatterns = (string[])patterns.Clone(); |
| | | 1399 | | |
| | | 1400 | | // Have to move [0] item to [i] so we can re-write default at [0] |
| | | 1401 | | // (remember defaultPattern == [i] so this is OK) |
| | 0 | 1402 | | newPatterns[i] = newPatterns[0]; |
| | | 1403 | | } |
| | | 1404 | | else |
| | | 1405 | | { |
| | | 1406 | | // Not found, make room for it |
| | 0 | 1407 | | newPatterns = new string[patterns.Length + 1]; |
| | | 1408 | | |
| | | 1409 | | // Copy existing array |
| | 0 | 1410 | | Array.Copy(patterns, 0, newPatterns, 1, patterns.Length); |
| | | 1411 | | } |
| | | 1412 | | |
| | | 1413 | | // Remember the default |
| | 0 | 1414 | | newPatterns[0] = defaultPattern; |
| | | 1415 | | |
| | | 1416 | | // Return the reconstructed list |
| | 0 | 1417 | | return newPatterns; |
| | | 1418 | | } |
| | | 1419 | | |
| | | 1420 | | // Needed by DateTimeFormatInfo and DateTimeFormat |
| | | 1421 | | internal const string RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK"; |
| | | 1422 | | internal const string RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz"; |
| | | 1423 | | |
| | | 1424 | | /// <summary> |
| | | 1425 | | /// Default string isn't necessarily in our string array, so get the |
| | | 1426 | | /// merged patterns of both |
| | | 1427 | | /// </summary> |
| | 0 | 1428 | | private string[] AllYearMonthPatterns => GetMergedPatterns(UnclonedYearMonthPatterns, YearMonthPattern); |
| | | 1429 | | |
| | 0 | 1430 | | private string[] AllShortDatePatterns => GetMergedPatterns(UnclonedShortDatePatterns, ShortDatePattern); |
| | | 1431 | | |
| | 0 | 1432 | | private string[] AllShortTimePatterns => GetMergedPatterns(UnclonedShortTimePatterns, ShortTimePattern); |
| | | 1433 | | |
| | 0 | 1434 | | private string[] AllLongDatePatterns => GetMergedPatterns(UnclonedLongDatePatterns, LongDatePattern); |
| | | 1435 | | |
| | 0 | 1436 | | private string[] AllLongTimePatterns => GetMergedPatterns(UnclonedLongTimePatterns, LongTimePattern); |
| | | 1437 | | |
| | | 1438 | | /// <remarks> |
| | | 1439 | | /// Clone this string array if you want to return it to user. |
| | | 1440 | | /// Otherwise, you are returning a writable cache copy. |
| | | 1441 | | /// This won't include default, call AllYearMonthPatterns |
| | | 1442 | | /// </remarks> |
| | | 1443 | | private string[] UnclonedYearMonthPatterns |
| | | 1444 | | { |
| | | 1445 | | get |
| | | 1446 | | { |
| | 0 | 1447 | | if (allYearMonthPatterns == null) |
| | | 1448 | | { |
| | 0 | 1449 | | Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > |
| | 0 | 1450 | | allYearMonthPatterns = _cultureData.YearMonths(Calendar.ID); |
| | 0 | 1451 | | Debug.Assert(allYearMonthPatterns.Length > 0, |
| | 0 | 1452 | | "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns"); |
| | | 1453 | | } |
| | | 1454 | | |
| | 0 | 1455 | | return allYearMonthPatterns; |
| | | 1456 | | } |
| | | 1457 | | } |
| | | 1458 | | |
| | | 1459 | | /// <remarks> |
| | | 1460 | | /// Clone this string array if you want to return it to user. |
| | | 1461 | | /// Otherwise, you are returning a writable cache copy. |
| | | 1462 | | /// This won't include default, call AllShortDatePatterns |
| | | 1463 | | /// </remarks> |
| | | 1464 | | private string[] UnclonedShortDatePatterns |
| | | 1465 | | { |
| | | 1466 | | get |
| | | 1467 | | { |
| | 0 | 1468 | | if (allShortDatePatterns == null) |
| | | 1469 | | { |
| | 0 | 1470 | | Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > |
| | 0 | 1471 | | allShortDatePatterns = _cultureData.ShortDates(Calendar.ID); |
| | 0 | 1472 | | Debug.Assert(allShortDatePatterns.Length > 0, |
| | 0 | 1473 | | "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns"); |
| | | 1474 | | } |
| | | 1475 | | |
| | 0 | 1476 | | return allShortDatePatterns; |
| | | 1477 | | } |
| | | 1478 | | } |
| | | 1479 | | |
| | | 1480 | | /// <remarks> |
| | | 1481 | | /// Clone this string array if you want to return it to user. |
| | | 1482 | | /// Otherwise, you are returning a writable cache copy. |
| | | 1483 | | /// This won't include default, call AllLongDatePatterns |
| | | 1484 | | /// </remarks> |
| | | 1485 | | private string[] UnclonedLongDatePatterns |
| | | 1486 | | { |
| | | 1487 | | get |
| | | 1488 | | { |
| | 0 | 1489 | | if (allLongDatePatterns == null) |
| | | 1490 | | { |
| | 0 | 1491 | | Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > |
| | 0 | 1492 | | allLongDatePatterns = _cultureData.LongDates(Calendar.ID); |
| | 0 | 1493 | | Debug.Assert(allLongDatePatterns.Length > 0, |
| | 0 | 1494 | | "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns"); |
| | | 1495 | | } |
| | | 1496 | | |
| | 0 | 1497 | | return allLongDatePatterns; |
| | | 1498 | | } |
| | | 1499 | | } |
| | | 1500 | | |
| | | 1501 | | /// <remarks> |
| | | 1502 | | /// Clone this string array if you want to return it to user. |
| | | 1503 | | /// Otherwise, you are returning a writable cache copy. |
| | | 1504 | | /// This won't include default, call AllShortTimePatterns |
| | | 1505 | | /// </remarks> |
| | | 1506 | | private string[] UnclonedShortTimePatterns |
| | | 1507 | | { |
| | | 1508 | | get |
| | | 1509 | | { |
| | 0 | 1510 | | if (allShortTimePatterns == null) |
| | | 1511 | | { |
| | 0 | 1512 | | allShortTimePatterns = _cultureData.ShortTimes; |
| | 0 | 1513 | | Debug.Assert(allShortTimePatterns.Length > 0, |
| | 0 | 1514 | | "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns"); |
| | | 1515 | | } |
| | | 1516 | | |
| | 0 | 1517 | | return allShortTimePatterns; |
| | | 1518 | | } |
| | | 1519 | | } |
| | | 1520 | | |
| | | 1521 | | /// <remarks> |
| | | 1522 | | /// Clone this string array if you want to return it to user. |
| | | 1523 | | /// Otherwise, you are returning a writable cache copy. |
| | | 1524 | | /// This won't include default, call AllLongTimePatterns |
| | | 1525 | | /// </remarks> |
| | | 1526 | | private string[] UnclonedLongTimePatterns |
| | | 1527 | | { |
| | | 1528 | | get |
| | | 1529 | | { |
| | 0 | 1530 | | if (allLongTimePatterns == null) |
| | | 1531 | | { |
| | 0 | 1532 | | allLongTimePatterns = _cultureData.LongTimes; |
| | 0 | 1533 | | Debug.Assert(allLongTimePatterns.Length > 0, |
| | 0 | 1534 | | "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns"); |
| | | 1535 | | } |
| | | 1536 | | |
| | 0 | 1537 | | return allLongTimePatterns; |
| | | 1538 | | } |
| | | 1539 | | } |
| | | 1540 | | |
| | | 1541 | | public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi) |
| | | 1542 | | { |
| | 0 | 1543 | | ArgumentNullException.ThrowIfNull(dtfi); |
| | | 1544 | | |
| | 0 | 1545 | | if (dtfi.IsReadOnly) |
| | | 1546 | | { |
| | 0 | 1547 | | return dtfi; |
| | | 1548 | | } |
| | | 1549 | | |
| | 0 | 1550 | | DateTimeFormatInfo newInfo = (DateTimeFormatInfo)(dtfi.MemberwiseClone()); |
| | | 1551 | | // We can use the data member calendar in the setter, instead of the property Calendar, |
| | | 1552 | | // since the cloned copy should have the same state as the original copy. |
| | 0 | 1553 | | newInfo.calendar = Calendar.ReadOnly(dtfi.Calendar); |
| | 0 | 1554 | | newInfo._isReadOnly = true; |
| | 0 | 1555 | | return newInfo; |
| | | 1556 | | } |
| | | 1557 | | |
| | 0 | 1558 | | public bool IsReadOnly => _isReadOnly; |
| | | 1559 | | |
| | | 1560 | | /// <summary> |
| | | 1561 | | /// Return the native name for the calendar in DTFI.Calendar. The native name is referred to |
| | | 1562 | | /// the culture used to create the DTFI. E.g. in the following example, the native language is Japanese. |
| | | 1563 | | /// DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar(); |
| | | 1564 | | /// String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar. |
| | | 1565 | | /// DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(Gr |
| | | 1566 | | /// String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar. |
| | | 1567 | | /// </summary> |
| | 0 | 1568 | | public string NativeCalendarName => _cultureData.CalendarName(Calendar.ID); |
| | | 1569 | | |
| | | 1570 | | /// <summary> |
| | | 1571 | | /// Used by custom cultures and others to set the list of available formats. Note that none of them are |
| | | 1572 | | /// explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items |
| | | 1573 | | /// from the list. |
| | | 1574 | | /// |
| | | 1575 | | /// Most of the format characters that can be used in GetAllDateTimePatterns are |
| | | 1576 | | /// not really needed since they are one of the following: |
| | | 1577 | | /// |
| | | 1578 | | /// r/R/s/u locale-independent constants -- cannot be changed! |
| | | 1579 | | /// m/M/y/Y fields with a single string in them -- that can be set through props directly |
| | | 1580 | | /// f/F/g/G/U derived fields based on combinations of various of the below formats |
| | | 1581 | | /// |
| | | 1582 | | /// NOTE: No special validation is done here beyond what is done when the actual respective fields |
| | | 1583 | | /// are used (what would be the point of disallowing here what we allow in the appropriate property?) |
| | | 1584 | | /// |
| | | 1585 | | /// WARNING: If more validation is ever done in one place, it should be done in the other. |
| | | 1586 | | /// </summary> |
| | | 1587 | | public void SetAllDateTimePatterns(string[] patterns, char format) |
| | | 1588 | | { |
| | 0 | 1589 | | if (IsReadOnly) |
| | | 1590 | | { |
| | 0 | 1591 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 1592 | | } |
| | 0 | 1593 | | ArgumentNullException.ThrowIfNull(patterns); |
| | | 1594 | | |
| | 0 | 1595 | | if (patterns.Length == 0) |
| | | 1596 | | { |
| | 0 | 1597 | | throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(patterns)); |
| | | 1598 | | } |
| | | 1599 | | |
| | 0 | 1600 | | for (int i = 0; i < patterns.Length; i++) |
| | | 1601 | | { |
| | 0 | 1602 | | if (patterns[i] == null) |
| | | 1603 | | { |
| | 0 | 1604 | | throw new ArgumentNullException(nameof(patterns) + "[" + i + "]", SR.ArgumentNull_ArrayValue); |
| | | 1605 | | } |
| | | 1606 | | } |
| | | 1607 | | |
| | | 1608 | | // Remember the patterns, and use the 1st as default |
| | | 1609 | | switch (format) |
| | | 1610 | | { |
| | | 1611 | | case 'd': |
| | 0 | 1612 | | allShortDatePatterns = patterns; |
| | 0 | 1613 | | shortDatePattern = allShortDatePatterns[0]; |
| | 0 | 1614 | | OnShortDatePatternChanged(); |
| | 0 | 1615 | | break; |
| | | 1616 | | |
| | | 1617 | | case 'D': |
| | 0 | 1618 | | allLongDatePatterns = patterns; |
| | 0 | 1619 | | longDatePattern = allLongDatePatterns[0]; |
| | 0 | 1620 | | OnLongDatePatternChanged(); |
| | 0 | 1621 | | break; |
| | | 1622 | | |
| | | 1623 | | case 't': |
| | 0 | 1624 | | allShortTimePatterns = patterns; |
| | 0 | 1625 | | shortTimePattern = allShortTimePatterns[0]; |
| | 0 | 1626 | | OnShortTimePatternChanged(); |
| | 0 | 1627 | | break; |
| | | 1628 | | |
| | | 1629 | | case 'T': |
| | 0 | 1630 | | allLongTimePatterns = patterns; |
| | 0 | 1631 | | longTimePattern = allLongTimePatterns[0]; |
| | 0 | 1632 | | OnLongTimePatternChanged(); |
| | 0 | 1633 | | break; |
| | | 1634 | | |
| | | 1635 | | case 'y': |
| | | 1636 | | case 'Y': |
| | 0 | 1637 | | allYearMonthPatterns = patterns; |
| | 0 | 1638 | | yearMonthPattern = allYearMonthPatterns[0]; |
| | 0 | 1639 | | OnYearMonthPatternChanged(); |
| | 0 | 1640 | | break; |
| | | 1641 | | |
| | | 1642 | | default: |
| | 0 | 1643 | | throw new ArgumentException(SR.Format(SR.Format_BadFormatSpecifier, format), nameof(format)); |
| | | 1644 | | } |
| | | 1645 | | } |
| | | 1646 | | |
| | | 1647 | | public string[] AbbreviatedMonthGenitiveNames |
| | | 1648 | | { |
| | 0 | 1649 | | get => (string[])InternalGetGenitiveMonthNames(true).Clone(); |
| | | 1650 | | set |
| | | 1651 | | { |
| | 0 | 1652 | | if (IsReadOnly) |
| | | 1653 | | { |
| | 0 | 1654 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 1655 | | } |
| | 0 | 1656 | | ArgumentNullException.ThrowIfNull(value); |
| | 0 | 1657 | | if (value.Length != 13) |
| | | 1658 | | { |
| | 0 | 1659 | | throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); |
| | | 1660 | | } |
| | | 1661 | | |
| | 0 | 1662 | | CheckNullValue(value, value.Length - 1); |
| | 0 | 1663 | | ClearTokenHashTable(); |
| | 0 | 1664 | | m_genitiveAbbreviatedMonthNames = value; |
| | 0 | 1665 | | } |
| | | 1666 | | } |
| | | 1667 | | |
| | | 1668 | | public string[] MonthGenitiveNames |
| | | 1669 | | { |
| | 0 | 1670 | | get => (string[])InternalGetGenitiveMonthNames(false).Clone(); |
| | | 1671 | | set |
| | | 1672 | | { |
| | 0 | 1673 | | if (IsReadOnly) |
| | | 1674 | | { |
| | 0 | 1675 | | throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); |
| | | 1676 | | } |
| | 0 | 1677 | | ArgumentNullException.ThrowIfNull(value); |
| | 0 | 1678 | | if (value.Length != 13) |
| | | 1679 | | { |
| | 0 | 1680 | | throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); |
| | | 1681 | | } |
| | | 1682 | | |
| | 0 | 1683 | | CheckNullValue(value, value.Length - 1); |
| | 0 | 1684 | | genitiveMonthNames = value; |
| | 0 | 1685 | | ClearTokenHashTable(); |
| | 0 | 1686 | | } |
| | | 1687 | | } |
| | | 1688 | | |
| | | 1689 | | // Decimal separator used by positive TimeSpan pattern |
| | | 1690 | | private string? _decimalSeparator; |
| | | 1691 | | internal string DecimalSeparator => |
| | 0 | 1692 | | _decimalSeparator ??= |
| | 0 | 1693 | | new NumberFormatInfo(_cultureData.UseUserOverride ? CultureData.GetCultureData(_cultureData.CultureName, fal |
| | | 1694 | | |
| | | 1695 | | private byte[]? _decimalSeparatorUtf8; |
| | | 1696 | | internal ReadOnlySpan<TChar> DecimalSeparatorTChar<TChar>() where TChar : unmanaged, IUtfChar<TChar> |
| | | 1697 | | { |
| | 0 | 1698 | | Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte)); |
| | 0 | 1699 | | return typeof(TChar) == typeof(char) ? |
| | 0 | 1700 | | Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(DecimalSeparator) : |
| | 0 | 1701 | | Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(_decimalSeparatorUtf8 ??= Encoding.UTF8.GetBytes |
| | | 1702 | | } |
| | | 1703 | | |
| | | 1704 | | // Positive TimeSpan Pattern |
| | | 1705 | | private string? _fullTimeSpanPositivePattern; |
| | | 1706 | | internal string FullTimeSpanPositivePattern => |
| | 0 | 1707 | | _fullTimeSpanPositivePattern ??= "d':'h':'mm':'ss'" + DecimalSeparator + "'FFFFFFF"; |
| | | 1708 | | |
| | | 1709 | | // Negative TimeSpan Pattern |
| | | 1710 | | private string? _fullTimeSpanNegativePattern; |
| | | 1711 | | internal string FullTimeSpanNegativePattern => |
| | 0 | 1712 | | _fullTimeSpanNegativePattern ??= "'-'" + FullTimeSpanPositivePattern; |
| | | 1713 | | |
| | | 1714 | | // Get suitable CompareInfo from current DTFI object. |
| | | 1715 | | internal CompareInfo CompareInfo => |
| | | 1716 | | // We use the regular GetCompareInfo here to make sure the created CompareInfo object is stored in the |
| | | 1717 | | // CompareInfo cache. otherwise we would just create CompareInfo using _cultureData. |
| | 0 | 1718 | | _compareInfo ??= CompareInfo.GetCompareInfo(_cultureData.SortName); |
| | | 1719 | | |
| | | 1720 | | internal const DateTimeStyles InvalidDateTimeStyles = ~(DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowT |
| | | 1721 | | | DateTimeStyles.AllowInnerWhite | DateTimeStyles.NoCurre |
| | | 1722 | | | DateTimeStyles.AdjustToUniversal | DateTimeStyles.Assum |
| | | 1723 | | | DateTimeStyles.AssumeUniversal | DateTimeStyles.Roundtr |
| | | 1724 | | |
| | | 1725 | | internal static void ValidateStyles(DateTimeStyles style, bool styles = false) |
| | | 1726 | | { |
| | | 1727 | | const DateTimeStyles localUniversal = DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal; |
| | | 1728 | | |
| | 0 | 1729 | | if ((style & InvalidDateTimeStyles) != 0 || |
| | 0 | 1730 | | (style & localUniversal) == localUniversal || |
| | 0 | 1731 | | (style & (DateTimeStyles.RoundtripKind | localUniversal | DateTimeStyles.AdjustToUniversal)) > DateTimeS |
| | | 1732 | | { |
| | 0 | 1733 | | ThrowInvalid(style, styles); |
| | | 1734 | | } |
| | | 1735 | | |
| | | 1736 | | static void ThrowInvalid(DateTimeStyles style, bool styles) |
| | | 1737 | | { |
| | 0 | 1738 | | string message = (style & InvalidDateTimeStyles) != 0 ? SR.Argument_InvalidDateTimeStyles |
| | 0 | 1739 | | : (style & localUniversal) == localUniversal ? SR.Argument_ConflictingDateTimeStyles |
| | 0 | 1740 | | : SR.Argument_ConflictingDateTimeRoundtripStyles; |
| | 0 | 1741 | | throw new ArgumentException(message, styles ? nameof(styles) : nameof(style)); |
| | | 1742 | | } |
| | 0 | 1743 | | } |
| | | 1744 | | |
| | | 1745 | | /// <summary> |
| | | 1746 | | /// Return the internal flag used in formatting and parsing. |
| | | 1747 | | /// The flag can be used to indicate things like if genitive forms is used in |
| | | 1748 | | /// this DTFi, or if leap year gets different month names. |
| | | 1749 | | /// </summary> |
| | 0 | 1750 | | internal DateTimeFormatFlags FormatFlags => formatFlags != DateTimeFormatFlags.NotInitialized ? formatFlags : In |
| | | 1751 | | |
| | | 1752 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 1753 | | private DateTimeFormatFlags InitializeFormatFlags() |
| | | 1754 | | { |
| | | 1755 | | // Build the format flags from the data in this DTFI |
| | 0 | 1756 | | formatFlags = |
| | 0 | 1757 | | (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth( |
| | 0 | 1758 | | MonthNames, InternalGetGenitiveMonthNames(false), AbbreviatedMonthNames, InternalGetGenitiveMonthNam |
| | 0 | 1759 | | (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames( |
| | 0 | 1760 | | MonthNames, InternalGetGenitiveMonthNames(false), AbbreviatedMonthNames, InternalGetGenitiveMonthNam |
| | 0 | 1761 | | (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInDayNames(DayNames, AbbreviatedDayN |
| | 0 | 1762 | | (DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseHebrewCalendar((int)Calendar.ID); |
| | 0 | 1763 | | return formatFlags; |
| | | 1764 | | } |
| | | 1765 | | |
| | | 1766 | | internal bool HasForceTwoDigitYears => |
| | | 1767 | | // Handle Japanese and Taiwan cases. |
| | | 1768 | | // If is y/yy, do not get (year % 100). "y" will print |
| | | 1769 | | // year without leading zero. "yy" will print year with two-digit in leading zero. |
| | | 1770 | | // If pattern is yyy/yyyy/..., print year value with two-digit in leading zero. |
| | | 1771 | | // So year 5 is "05", and year 125 is "125". |
| | | 1772 | | // The reason for not doing (year % 100) is for Taiwan calendar. |
| | | 1773 | | // If year 125, then output 125 and not 25. |
| | | 1774 | | // Note: OS uses "yyyy" for Taiwan calendar by default. |
| | 0 | 1775 | | calendar.ID is CalendarId.JAPAN or CalendarId.TAIWAN; |
| | | 1776 | | |
| | | 1777 | | /// <summary> |
| | | 1778 | | /// Returns whether the YearMonthAdjustment function has any fix-up work to do for this culture/calendar. |
| | | 1779 | | /// </summary> |
| | 0 | 1780 | | internal bool HasYearMonthAdjustment => (FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0; |
| | | 1781 | | |
| | | 1782 | | /// <summary> |
| | | 1783 | | /// This is a callback that the parser can make back into the DTFI to let it fiddle with special |
| | | 1784 | | /// cases associated with that culture or calendar. Currently this only has special cases for |
| | | 1785 | | /// the Hebrew calendar, but this could be extended to other cultures. |
| | | 1786 | | /// |
| | | 1787 | | /// The return value is whether the year and month are actually valid for this calendar. |
| | | 1788 | | /// </summary> |
| | | 1789 | | internal bool YearMonthAdjustment(ref int year, ref int month, bool parsedMonthName) |
| | | 1790 | | { |
| | 0 | 1791 | | if ((FormatFlags & DateTimeFormatFlags.UseHebrewRule) != 0) |
| | | 1792 | | { |
| | | 1793 | | // Special rules to fix up the Hebrew year/month |
| | | 1794 | | |
| | | 1795 | | // When formatting, we only format up to the hundred digit of the Hebrew year, although Hebrew year is n |
| | | 1796 | | // E.g. if the year is 5763, we only format as 763. |
| | 0 | 1797 | | if (year < 1000) |
| | | 1798 | | { |
| | 0 | 1799 | | year += 5000; |
| | | 1800 | | } |
| | | 1801 | | |
| | | 1802 | | // Because we need to calculate leap year, we should fall out now for an invalid year. |
| | 0 | 1803 | | if (year < Calendar.GetYear(Calendar.MinSupportedDateTime) || year > Calendar.GetYear(Calendar.MaxSuppor |
| | | 1804 | | { |
| | 0 | 1805 | | return false; |
| | | 1806 | | } |
| | | 1807 | | |
| | | 1808 | | // To handle leap months, the set of month names in the symbol table does not always correspond to the n |
| | | 1809 | | // For non-leap years, month 7 (Adar Bet) is not present, so we need to make using this month invalid an |
| | | 1810 | | // shuffle the other months down. |
| | 0 | 1811 | | if (parsedMonthName) |
| | | 1812 | | { |
| | 0 | 1813 | | if (!Calendar.IsLeapYear(year)) |
| | | 1814 | | { |
| | 0 | 1815 | | if (month >= 8) |
| | | 1816 | | { |
| | 0 | 1817 | | month--; |
| | | 1818 | | } |
| | 0 | 1819 | | else if (month == 7) |
| | | 1820 | | { |
| | 0 | 1821 | | return false; |
| | | 1822 | | } |
| | | 1823 | | } |
| | | 1824 | | } |
| | | 1825 | | } |
| | 0 | 1826 | | return true; |
| | | 1827 | | } |
| | | 1828 | | |
| | | 1829 | | // DateTimeFormatInfo tokenizer. This is used by DateTime.Parse() to break input string into tokens. |
| | | 1830 | | private TokenHashValue[]? _dtfiTokenHash; |
| | | 1831 | | |
| | | 1832 | | private const int TOKEN_HASH_SIZE = 199; |
| | | 1833 | | private const int SECOND_PRIME = 197; |
| | | 1834 | | private const string dateSeparatorOrTimeZoneOffset = "-"; |
| | | 1835 | | private const string invariantDateSeparator = "/"; |
| | | 1836 | | private const string invariantTimeSeparator = ":"; |
| | | 1837 | | |
| | | 1838 | | // Common Ignorable Symbols |
| | | 1839 | | internal const string IgnorablePeriod = "."; |
| | | 1840 | | internal const string IgnorableComma = ","; |
| | | 1841 | | |
| | | 1842 | | // Year/Month/Day suffixes |
| | | 1843 | | internal const string CJKYearSuff = "\u5e74"; |
| | | 1844 | | internal const string CJKMonthSuff = "\u6708"; |
| | | 1845 | | internal const string CJKDaySuff = "\u65e5"; |
| | | 1846 | | |
| | | 1847 | | internal const string KoreanYearSuff = "\ub144"; |
| | | 1848 | | internal const string KoreanMonthSuff = "\uc6d4"; |
| | | 1849 | | internal const string KoreanDaySuff = "\uc77c"; |
| | | 1850 | | |
| | | 1851 | | internal const string KoreanHourSuff = "\uc2dc"; |
| | | 1852 | | internal const string KoreanMinuteSuff = "\ubd84"; |
| | | 1853 | | internal const string KoreanSecondSuff = "\ucd08"; |
| | | 1854 | | |
| | | 1855 | | internal const string CJKHourSuff = "\u6642"; |
| | | 1856 | | internal const string ChineseHourSuff = "\u65f6"; |
| | | 1857 | | |
| | | 1858 | | internal const string CJKMinuteSuff = "\u5206"; |
| | | 1859 | | internal const string CJKSecondSuff = "\u79d2"; |
| | | 1860 | | |
| | | 1861 | | internal const string JapaneseEraStart = "\u5143"; |
| | | 1862 | | |
| | | 1863 | | internal const string LocalTimeMark = "T"; |
| | | 1864 | | |
| | | 1865 | | internal const string GMTName = "GMT"; |
| | | 1866 | | internal const string ZuluName = "Z"; |
| | | 1867 | | |
| | | 1868 | | internal const string KoreanLangName = "ko"; |
| | | 1869 | | internal const string JapaneseLangName = "ja"; |
| | | 1870 | | internal const string EnglishLangName = "en"; |
| | | 1871 | | |
| | | 1872 | | private static DateTimeFormatInfo? s_jajpDTFI; |
| | | 1873 | | private static DateTimeFormatInfo? s_zhtwDTFI; |
| | | 1874 | | |
| | | 1875 | | /// <summary> |
| | | 1876 | | /// Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse |
| | | 1877 | | /// date string with Japanese era name correctly even when the supplied DTFI |
| | | 1878 | | /// does not use Japanese calendar. |
| | | 1879 | | /// The created instance is stored in global s_jajpDTFI. |
| | | 1880 | | /// </summary> |
| | | 1881 | | internal static DateTimeFormatInfo GetJapaneseCalendarDTFI() |
| | | 1882 | | { |
| | 0 | 1883 | | DateTimeFormatInfo? temp = s_jajpDTFI; |
| | 0 | 1884 | | if (temp == null) |
| | | 1885 | | { |
| | 0 | 1886 | | temp = new CultureInfo("ja-JP", false).DateTimeFormat; |
| | 0 | 1887 | | temp.Calendar = JapaneseCalendar.GetDefaultInstance(); |
| | 0 | 1888 | | s_jajpDTFI = temp; |
| | | 1889 | | } |
| | 0 | 1890 | | return temp; |
| | | 1891 | | } |
| | | 1892 | | |
| | | 1893 | | /// <summary> |
| | | 1894 | | /// Create a Taiwan DTFI which uses TaiwanCalendar. This is used to parse |
| | | 1895 | | /// date string with era name correctly even when the supplied DTFI |
| | | 1896 | | /// does not use Taiwan calendar. |
| | | 1897 | | /// The created instance is stored in global s_zhtwDTFI. |
| | | 1898 | | /// </summary> |
| | | 1899 | | internal static DateTimeFormatInfo GetTaiwanCalendarDTFI() |
| | | 1900 | | { |
| | 0 | 1901 | | DateTimeFormatInfo? temp = s_zhtwDTFI; |
| | 0 | 1902 | | if (temp == null) |
| | | 1903 | | { |
| | 0 | 1904 | | temp = new CultureInfo("zh-TW", false).DateTimeFormat; |
| | 0 | 1905 | | temp.Calendar = TaiwanCalendar.GetDefaultInstance(); |
| | 0 | 1906 | | s_zhtwDTFI = temp; |
| | | 1907 | | } |
| | 0 | 1908 | | return temp; |
| | | 1909 | | } |
| | | 1910 | | |
| | | 1911 | | /// <summary> |
| | | 1912 | | /// DTFI properties should call this when the setter are called. |
| | | 1913 | | /// </summary> |
| | | 1914 | | private void ClearTokenHashTable() |
| | | 1915 | | { |
| | 0 | 1916 | | _dtfiTokenHash = null; |
| | 0 | 1917 | | formatFlags = DateTimeFormatFlags.NotInitialized; |
| | 0 | 1918 | | } |
| | | 1919 | | |
| | | 1920 | | internal TokenHashValue[] CreateTokenHashTable() |
| | | 1921 | | { |
| | 0 | 1922 | | TokenHashValue[]? temp = _dtfiTokenHash; |
| | 0 | 1923 | | if (temp == null) |
| | | 1924 | | { |
| | 0 | 1925 | | temp = new TokenHashValue[TOKEN_HASH_SIZE]; |
| | | 1926 | | |
| | 0 | 1927 | | string sep = TimeSeparator.Trim(); |
| | 0 | 1928 | | if (IgnorableComma != sep) InsertHash(temp, IgnorableComma, TokenType.IgnorableSymbol, 0); |
| | 0 | 1929 | | if (IgnorablePeriod != sep) InsertHash(temp, IgnorablePeriod, TokenType.IgnorableSymbol, 0); |
| | | 1930 | | |
| | 0 | 1931 | | if (KoreanHourSuff != sep && CJKHourSuff != sep && ChineseHourSuff != sep) |
| | | 1932 | | { |
| | | 1933 | | // |
| | | 1934 | | // On the Macintosh, the default TimeSeparator is identical to the KoreanHourSuff, CJKHourSuff, or C |
| | | 1935 | | // ja-JP and ko-KR. In these cases having the same symbol inserted into the hash table with multipl |
| | | 1936 | | // DateTime.Parse behavior. For instance, the DateTimeFormatInfo.Tokenize() method might return SEP |
| | | 1937 | | // instead of SEP_HourSuff. |
| | | 1938 | | // |
| | 0 | 1939 | | InsertHash(temp, TimeSeparator, TokenType.SEP_Time, 0); |
| | | 1940 | | } |
| | | 1941 | | |
| | 0 | 1942 | | if (_name == "fr-CA") |
| | | 1943 | | { |
| | 0 | 1944 | | InsertHash(temp, " h", TokenType.SEP_HourSuff, 0); |
| | 0 | 1945 | | InsertHash(temp, " h ", TokenType.SEP_HourSuff, 0); |
| | 0 | 1946 | | InsertHash(temp, " min", TokenType.SEP_MinuteSuff, 0); |
| | 0 | 1947 | | InsertHash(temp, " min ", TokenType.SEP_MinuteSuff, 0); |
| | 0 | 1948 | | InsertHash(temp, " s", TokenType.SEP_SecondSuff, 0); |
| | 0 | 1949 | | InsertHash(temp, " s ", TokenType.SEP_SecondSuff, 0); |
| | | 1950 | | } |
| | | 1951 | | |
| | 0 | 1952 | | InsertHash(temp, AMDesignator, TokenType.SEP_Am | TokenType.Am, 0); |
| | 0 | 1953 | | InsertHash(temp, PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1); |
| | | 1954 | | |
| | 0 | 1955 | | if (LanguageName.Equals("sq")) |
| | | 1956 | | { |
| | | 1957 | | // Albanian allows time formats like "12:00.PD" |
| | 0 | 1958 | | InsertHash(temp, IgnorablePeriod + AMDesignator, TokenType.SEP_Am | TokenType.Am, 0); |
| | 0 | 1959 | | InsertHash(temp, IgnorablePeriod + PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1); |
| | | 1960 | | } |
| | | 1961 | | |
| | | 1962 | | // CJK suffix |
| | 0 | 1963 | | InsertHash(temp, CJKYearSuff, TokenType.SEP_YearSuff, 0); |
| | 0 | 1964 | | InsertHash(temp, KoreanYearSuff, TokenType.SEP_YearSuff, 0); |
| | 0 | 1965 | | InsertHash(temp, CJKMonthSuff, TokenType.SEP_MonthSuff, 0); |
| | 0 | 1966 | | InsertHash(temp, KoreanMonthSuff, TokenType.SEP_MonthSuff, 0); |
| | 0 | 1967 | | InsertHash(temp, CJKDaySuff, TokenType.SEP_DaySuff, 0); |
| | 0 | 1968 | | InsertHash(temp, KoreanDaySuff, TokenType.SEP_DaySuff, 0); |
| | | 1969 | | |
| | 0 | 1970 | | InsertHash(temp, CJKHourSuff, TokenType.SEP_HourSuff, 0); |
| | 0 | 1971 | | InsertHash(temp, ChineseHourSuff, TokenType.SEP_HourSuff, 0); |
| | 0 | 1972 | | InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0); |
| | 0 | 1973 | | InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0); |
| | | 1974 | | |
| | 0 | 1975 | | if (!LocalAppContextSwitches.EnforceLegacyJapaneseDateParsing && Calendar.ID == CalendarId.JAPAN) |
| | | 1976 | | { |
| | | 1977 | | // We need to support parsing the dates has the start of era symbol which means it is year 1 in the |
| | | 1978 | | // The start of era symbol has to be followed by the year symbol suffix, otherwise it would be inval |
| | 0 | 1979 | | InsertHash(temp, JapaneseEraStart, TokenType.YearNumberToken, 1); |
| | 0 | 1980 | | InsertHash(temp, "(", TokenType.IgnorableSymbol, 0); |
| | 0 | 1981 | | InsertHash(temp, ")", TokenType.IgnorableSymbol, 0); |
| | | 1982 | | } |
| | | 1983 | | |
| | | 1984 | | // TODO: This ignores other custom cultures that might want to do something similar |
| | 0 | 1985 | | if (LanguageName.Equals(KoreanLangName)) |
| | | 1986 | | { |
| | | 1987 | | // Korean suffix |
| | 0 | 1988 | | InsertHash(temp, KoreanHourSuff, TokenType.SEP_HourSuff, 0); |
| | 0 | 1989 | | InsertHash(temp, KoreanMinuteSuff, TokenType.SEP_MinuteSuff, 0); |
| | 0 | 1990 | | InsertHash(temp, KoreanSecondSuff, TokenType.SEP_SecondSuff, 0); |
| | | 1991 | | } |
| | | 1992 | | |
| | 0 | 1993 | | if (LanguageName.Equals("ky")) |
| | | 1994 | | { |
| | | 1995 | | // For some cultures, the date separator works more like a comma, being allowed before or after any |
| | 0 | 1996 | | InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.IgnorableSymbol, 0); |
| | | 1997 | | } |
| | | 1998 | | else |
| | | 1999 | | { |
| | 0 | 2000 | | InsertHash(temp, dateSeparatorOrTimeZoneOffset, TokenType.SEP_DateOrOffset, 0); |
| | | 2001 | | } |
| | | 2002 | | |
| | | 2003 | | // We need to rescan the date words since we're always synthetic |
| | 0 | 2004 | | DateTimeFormatInfoScanner scanner = new DateTimeFormatInfoScanner(); |
| | 0 | 2005 | | string[]? dateWords = scanner.GetDateWordsOfDTFI(this); |
| | | 2006 | | |
| | | 2007 | | // Ensure the formatflags is initialized. |
| | 0 | 2008 | | _ = FormatFlags; |
| | | 2009 | | |
| | | 2010 | | // For some cultures, the date separator works more like a comma, being allowed before or after any date |
| | | 2011 | | // In these cultures, we do not use normal date separator since we disallow date separator after a date |
| | | 2012 | | // This is determined in DateTimeFormatInfoScanner. Use this flag to determine if we should treat date |
| | 0 | 2013 | | bool useDateSepAsIgnorableSymbol = false; |
| | | 2014 | | |
| | 0 | 2015 | | if (dateWords != null) |
| | | 2016 | | { |
| | | 2017 | | // There are DateWords. It could be a real date word (such as "de"), or a monthPostfix. |
| | | 2018 | | // The monthPostfix starts with '\xfffe' (MonthPostfixChar), followed by the real monthPostfix. |
| | 0 | 2019 | | for (int i = 0; i < dateWords.Length; i++) |
| | | 2020 | | { |
| | 0 | 2021 | | switch (dateWords[i][0]) |
| | | 2022 | | { |
| | | 2023 | | // This is a month postfix |
| | | 2024 | | case DateTimeFormatInfoScanner.MonthPostfixChar: |
| | | 2025 | | // Get the real month postfix. |
| | 0 | 2026 | | ReadOnlySpan<char> monthPostfix = dateWords[i].AsSpan(1); |
| | | 2027 | | // Add the month name + postfix into the token. |
| | 0 | 2028 | | AddMonthNames(temp, monthPostfix); |
| | 0 | 2029 | | break; |
| | | 2030 | | case DateTimeFormatInfoScanner.IgnorableSymbolChar: |
| | 0 | 2031 | | string symbol = dateWords[i].Substring(1); |
| | 0 | 2032 | | InsertHash(temp, symbol, TokenType.IgnorableSymbol, 0); |
| | 0 | 2033 | | if (DateSeparator.Trim().Equals(symbol)) |
| | | 2034 | | { |
| | | 2035 | | // The date separator is the same as the ignorable symbol. |
| | 0 | 2036 | | useDateSepAsIgnorableSymbol = true; |
| | | 2037 | | } |
| | 0 | 2038 | | break; |
| | | 2039 | | default: |
| | 0 | 2040 | | InsertHash(temp, dateWords[i], TokenType.DateWordToken, 0); |
| | | 2041 | | // TODO: This ignores similar custom cultures |
| | 0 | 2042 | | if (LanguageName.Equals("eu")) |
| | | 2043 | | { |
| | | 2044 | | // Basque has date words with leading dots |
| | 0 | 2045 | | InsertHash(temp, IgnorablePeriod + dateWords[i], TokenType.DateWordToken, 0); |
| | | 2046 | | } |
| | | 2047 | | break; |
| | | 2048 | | } |
| | | 2049 | | } |
| | | 2050 | | } |
| | | 2051 | | |
| | 0 | 2052 | | if (!useDateSepAsIgnorableSymbol) |
| | | 2053 | | { |
| | | 2054 | | // Use the normal date separator. |
| | 0 | 2055 | | InsertHash(temp, DateSeparator, TokenType.SEP_Date, 0); |
| | | 2056 | | } |
| | | 2057 | | // Add the regular month names. |
| | 0 | 2058 | | AddMonthNames(temp); |
| | | 2059 | | |
| | | 2060 | | // Add the abbreviated month names. |
| | 0 | 2061 | | for (int i = 1; i <= 13; i++) |
| | | 2062 | | { |
| | 0 | 2063 | | InsertHash(temp, GetAbbreviatedMonthName(i), TokenType.MonthToken, i); |
| | | 2064 | | } |
| | | 2065 | | |
| | 0 | 2066 | | if ((FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0) |
| | | 2067 | | { |
| | 0 | 2068 | | string[] genitiveMonthNames = InternalGetGenitiveMonthNames(abbreviated: false); |
| | 0 | 2069 | | string[] abbreviatedGenitiveMonthNames = InternalGetGenitiveMonthNames(abbreviated: true); |
| | | 2070 | | |
| | 0 | 2071 | | for (int i = 1; i <= 13; i++) |
| | | 2072 | | { |
| | 0 | 2073 | | InsertHash(temp, genitiveMonthNames[i - 1], TokenType.MonthToken, i); |
| | 0 | 2074 | | InsertHash(temp, abbreviatedGenitiveMonthNames[i - 1], TokenType.MonthToken, i); |
| | | 2075 | | } |
| | | 2076 | | } |
| | | 2077 | | |
| | 0 | 2078 | | if ((FormatFlags & DateTimeFormatFlags.UseLeapYearMonth) != 0) |
| | | 2079 | | { |
| | 0 | 2080 | | for (int i = 1; i <= 13; i++) |
| | | 2081 | | { |
| | 0 | 2082 | | string str = InternalGetMonthName(i, MonthNameStyles.LeapYear, false); |
| | 0 | 2083 | | InsertHash(temp, str, TokenType.MonthToken, i); |
| | | 2084 | | } |
| | | 2085 | | } |
| | | 2086 | | |
| | 0 | 2087 | | for (int i = 0; i < 7; i++) |
| | | 2088 | | { |
| | | 2089 | | // We have to call public methods here to work with inherited DTFI. |
| | 0 | 2090 | | string str = GetDayName((DayOfWeek)i); |
| | 0 | 2091 | | InsertHash(temp, str, TokenType.DayOfWeekToken, i); |
| | | 2092 | | |
| | 0 | 2093 | | str = GetAbbreviatedDayName((DayOfWeek)i); |
| | 0 | 2094 | | InsertHash(temp, str, TokenType.DayOfWeekToken, i); |
| | | 2095 | | } |
| | | 2096 | | |
| | 0 | 2097 | | int[] eras = calendar.Eras; |
| | 0 | 2098 | | for (int i = 1; i <= eras.Length; i++) |
| | | 2099 | | { |
| | 0 | 2100 | | InsertHash(temp, GetEraName(i), TokenType.EraToken, i); |
| | 0 | 2101 | | InsertHash(temp, GetAbbreviatedEraName(i), TokenType.EraToken, i); |
| | | 2102 | | } |
| | | 2103 | | |
| | 0 | 2104 | | if (!GlobalizationMode.Invariant) |
| | | 2105 | | { |
| | 0 | 2106 | | if (LanguageName.Equals(JapaneseLangName)) |
| | | 2107 | | { |
| | | 2108 | | // Japanese allows day of week forms like: "(Tue)" |
| | 0 | 2109 | | for (int i = 0; i < 7; i++) |
| | | 2110 | | { |
| | 0 | 2111 | | string specialDayOfWeek = "(" + GetAbbreviatedDayName((DayOfWeek)i) + ")"; |
| | 0 | 2112 | | InsertHash(temp, specialDayOfWeek, TokenType.DayOfWeekToken, i); |
| | | 2113 | | } |
| | 0 | 2114 | | if (Calendar.GetType() != typeof(JapaneseCalendar)) |
| | | 2115 | | { |
| | | 2116 | | // Special case for Japanese. If this is a Japanese DTFI, and the calendar is not Japanese |
| | | 2117 | | // we will check Japanese Era name as well when the calendar is Gregorian. |
| | 0 | 2118 | | DateTimeFormatInfo jaDtfi = GetJapaneseCalendarDTFI(); |
| | 0 | 2119 | | for (int i = 1; i <= jaDtfi.Calendar.Eras.Length; i++) |
| | | 2120 | | { |
| | 0 | 2121 | | InsertHash(temp, jaDtfi.GetEraName(i), TokenType.JapaneseEraToken, i); |
| | 0 | 2122 | | InsertHash(temp, jaDtfi.GetAbbreviatedEraName(i), TokenType.JapaneseEraToken, i); |
| | | 2123 | | // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1. |
| | 0 | 2124 | | InsertHash(temp, jaDtfi.AbbreviatedEnglishEraNames[i - 1], TokenType.JapaneseEraToken, i |
| | | 2125 | | } |
| | | 2126 | | } |
| | | 2127 | | } |
| | | 2128 | | // TODO: This prohibits similar custom cultures, but we hard coded the name |
| | 0 | 2129 | | else if (CultureName.Equals("zh-TW")) |
| | | 2130 | | { |
| | 0 | 2131 | | DateTimeFormatInfo twDtfi = GetTaiwanCalendarDTFI(); |
| | 0 | 2132 | | for (int i = 1; i <= twDtfi.Calendar.Eras.Length; i++) |
| | | 2133 | | { |
| | 0 | 2134 | | if (twDtfi.GetEraName(i).Length > 0) |
| | | 2135 | | { |
| | 0 | 2136 | | InsertHash(temp, twDtfi.GetEraName(i), TokenType.TEraToken, i); |
| | | 2137 | | } |
| | | 2138 | | } |
| | | 2139 | | } |
| | | 2140 | | } |
| | | 2141 | | |
| | 0 | 2142 | | InsertHash(temp, InvariantInfo.AMDesignator, TokenType.SEP_Am | TokenType.Am, 0); |
| | 0 | 2143 | | InsertHash(temp, InvariantInfo.PMDesignator, TokenType.SEP_Pm | TokenType.Pm, 1); |
| | | 2144 | | |
| | | 2145 | | // Add invariant month names and day names. |
| | 0 | 2146 | | for (int i = 1; i <= 12; i++) |
| | | 2147 | | { |
| | | 2148 | | // We have to call public methods here to work with inherited DTFI. |
| | | 2149 | | // Insert the month name first, so that they are at the front of abbreviated |
| | | 2150 | | // month names. |
| | 0 | 2151 | | string str = InvariantInfo.GetMonthName(i); |
| | 0 | 2152 | | InsertHash(temp, str, TokenType.MonthToken, i); |
| | 0 | 2153 | | str = InvariantInfo.GetAbbreviatedMonthName(i); |
| | 0 | 2154 | | InsertHash(temp, str, TokenType.MonthToken, i); |
| | | 2155 | | } |
| | | 2156 | | |
| | 0 | 2157 | | for (int i = 0; i < 7; i++) |
| | | 2158 | | { |
| | | 2159 | | // We have to call public methods here to work with inherited DTFI. |
| | 0 | 2160 | | string str = InvariantInfo.GetDayName((DayOfWeek)i); |
| | 0 | 2161 | | InsertHash(temp, str, TokenType.DayOfWeekToken, i); |
| | | 2162 | | |
| | 0 | 2163 | | str = InvariantInfo.GetAbbreviatedDayName((DayOfWeek)i); |
| | 0 | 2164 | | InsertHash(temp, str, TokenType.DayOfWeekToken, i); |
| | | 2165 | | } |
| | | 2166 | | |
| | 0 | 2167 | | for (int i = 0; i < AbbreviatedEnglishEraNames.Length; i++) |
| | | 2168 | | { |
| | | 2169 | | // m_abbrevEnglishEraNames[0] contains the name for era 1, so the token value is i+1. |
| | 0 | 2170 | | InsertHash(temp, AbbreviatedEnglishEraNames[i], TokenType.EraToken, i + 1); |
| | | 2171 | | } |
| | | 2172 | | |
| | 0 | 2173 | | InsertHash(temp, LocalTimeMark, TokenType.SEP_LocalTimeMark, 0); |
| | 0 | 2174 | | InsertHash(temp, GMTName, TokenType.TimeZoneToken, 0); |
| | 0 | 2175 | | InsertHash(temp, ZuluName, TokenType.TimeZoneToken, 0); |
| | | 2176 | | |
| | 0 | 2177 | | InsertHash(temp, invariantDateSeparator, TokenType.SEP_Date, 0); |
| | 0 | 2178 | | InsertHash(temp, invariantTimeSeparator, TokenType.SEP_Time, 0); |
| | | 2179 | | |
| | 0 | 2180 | | _dtfiTokenHash = temp; |
| | | 2181 | | } |
| | 0 | 2182 | | return temp; |
| | | 2183 | | } |
| | | 2184 | | |
| | | 2185 | | private void AddMonthNames(TokenHashValue[] temp, ReadOnlySpan<char> monthPostfix = default) |
| | | 2186 | | { |
| | 0 | 2187 | | for (int i = 1; i <= 13; i++) |
| | | 2188 | | { |
| | | 2189 | | // We have to call public methods here to work with inherited DTFI. |
| | | 2190 | | // Insert the month name first, so that they are at the front of abbreviated |
| | | 2191 | | // month names. |
| | 0 | 2192 | | string str = GetMonthName(i); |
| | 0 | 2193 | | if (str.Length > 0) |
| | | 2194 | | { |
| | 0 | 2195 | | if (!monthPostfix.IsEmpty) |
| | | 2196 | | { |
| | | 2197 | | // Insert the month name with the postfix first, so it can be matched first. |
| | 0 | 2198 | | InsertHash(temp, string.Concat(str, monthPostfix), TokenType.MonthToken, i); |
| | | 2199 | | } |
| | | 2200 | | else |
| | | 2201 | | { |
| | 0 | 2202 | | InsertHash(temp, str, TokenType.MonthToken, i); |
| | | 2203 | | } |
| | | 2204 | | } |
| | 0 | 2205 | | str = GetAbbreviatedMonthName(i); |
| | 0 | 2206 | | InsertHash(temp, str, TokenType.MonthToken, i); |
| | | 2207 | | } |
| | 0 | 2208 | | } |
| | | 2209 | | |
| | | 2210 | | /// <summary> |
| | | 2211 | | /// Try to parse the current word to see if it is a Hebrew number. |
| | | 2212 | | /// Tokens will be updated accordingly. |
| | | 2213 | | /// This is called by the Lexer of DateTime.Parse(). |
| | | 2214 | | /// |
| | | 2215 | | /// Unlike most of the functions in this class, the return value indicates |
| | | 2216 | | /// whether or not it started to parse. The badFormat parameter indicates |
| | | 2217 | | /// if parsing began, but the format was bad. |
| | | 2218 | | /// </summary> |
| | | 2219 | | private static bool TryParseHebrewNumber( |
| | | 2220 | | ref __DTString str, |
| | | 2221 | | out bool badFormat, |
| | | 2222 | | out int number) |
| | | 2223 | | { |
| | 0 | 2224 | | number = -1; |
| | 0 | 2225 | | badFormat = false; |
| | | 2226 | | |
| | 0 | 2227 | | int i = str.Index; |
| | 0 | 2228 | | if (!HebrewNumber.IsDigit(str.Value[i])) |
| | | 2229 | | { |
| | | 2230 | | // If the current character is not a Hebrew digit, just return false. |
| | | 2231 | | // There is no chance that we can parse a valid Hebrew number from here. |
| | 0 | 2232 | | return false; |
| | | 2233 | | } |
| | | 2234 | | // The current character is a Hebrew digit. Try to parse this word as a Hebrew number. |
| | 0 | 2235 | | HebrewNumberParsingContext context = new HebrewNumberParsingContext(0); |
| | | 2236 | | HebrewNumberParsingState state; |
| | | 2237 | | |
| | | 2238 | | do |
| | | 2239 | | { |
| | 0 | 2240 | | state = HebrewNumber.ParseByChar(str.Value[i++], ref context); |
| | | 2241 | | switch (state) |
| | | 2242 | | { |
| | | 2243 | | case HebrewNumberParsingState.InvalidHebrewNumber: // Not a valid Hebrew number. |
| | | 2244 | | case HebrewNumberParsingState.NotHebrewDigit: // The current character is not a Hebrew digit |
| | | 2245 | | // Break out so that we don't continue to try parse this as a Hebrew number. |
| | 0 | 2246 | | return false; |
| | | 2247 | | } |
| | 0 | 2248 | | } while (i < str.Value.Length && (state != HebrewNumberParsingState.FoundEndOfHebrewNumber)); |
| | | 2249 | | |
| | | 2250 | | // When we are here, we are either at the end of the string, or we find a valid Hebrew number. |
| | 0 | 2251 | | Debug.Assert(state == HebrewNumberParsingState.ContinueParsing || state == HebrewNumberParsingState.FoundEnd |
| | 0 | 2252 | | "Invalid returned state from HebrewNumber.ParseByChar()"); |
| | | 2253 | | |
| | 0 | 2254 | | if (state != HebrewNumberParsingState.FoundEndOfHebrewNumber) |
| | | 2255 | | { |
| | | 2256 | | // We reach end of the string but we can't find a terminal state in parsing Hebrew number. |
| | 0 | 2257 | | return false; |
| | | 2258 | | } |
| | | 2259 | | |
| | | 2260 | | // We have found a valid Hebrew number. Update the index. |
| | 0 | 2261 | | str.Advance(i - str.Index); |
| | | 2262 | | |
| | | 2263 | | // Get the final Hebrew number value from the HebrewNumberParsingContext. |
| | 0 | 2264 | | number = context.result; |
| | | 2265 | | |
| | 0 | 2266 | | return true; |
| | | 2267 | | } |
| | | 2268 | | |
| | | 2269 | | private static bool IsHebrewChar(char ch) |
| | | 2270 | | { |
| | 0 | 2271 | | return ch >= '\x0590' && ch <= '\x05ff'; |
| | | 2272 | | } |
| | | 2273 | | |
| | | 2274 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 2275 | | private bool IsAllowedJapaneseTokenFollowedByNonSpaceLetter(string tokenString, char nextCh) |
| | | 2276 | | { |
| | | 2277 | | // Allow the parser to recognize the case when having some date part followed by JapaneseEraStart "\u5143" |
| | | 2278 | | // without spaces in between. e.g. Era name followed by \u5143 in the date formats ggy. |
| | | 2279 | | // Also, allow recognizing the year suffix symbol "\u5e74" followed the JapaneseEraStart "\u5143" |
| | 0 | 2280 | | if (!LocalAppContextSwitches.EnforceLegacyJapaneseDateParsing && Calendar.ID == CalendarId.JAPAN && |
| | 0 | 2281 | | ( |
| | 0 | 2282 | | // something like ggy, era followed by year and the year is specified using the JapaneseEraStart "\u |
| | 0 | 2283 | | nextCh == JapaneseEraStart[0] || |
| | 0 | 2284 | | // JapaneseEraStart followed by year suffix "\u5143" |
| | 0 | 2285 | | (tokenString == JapaneseEraStart && nextCh == CJKYearSuff[0]) |
| | 0 | 2286 | | )) |
| | | 2287 | | { |
| | 0 | 2288 | | return true; |
| | | 2289 | | } |
| | 0 | 2290 | | return false; |
| | | 2291 | | } |
| | | 2292 | | |
| | | 2293 | | internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue, |
| | | 2294 | | ref __DTString str) |
| | | 2295 | | { |
| | 0 | 2296 | | tokenType = TokenType.UnknownToken; |
| | 0 | 2297 | | tokenValue = 0; |
| | | 2298 | | |
| | | 2299 | | TokenHashValue value; |
| | 0 | 2300 | | Debug.Assert(str.Index < str.Value.Length, "DateTimeFormatInfo.Tokenize(): start < value.Length"); |
| | | 2301 | | |
| | 0 | 2302 | | char ch = str.m_current; |
| | 0 | 2303 | | bool isLetter = char.IsLetter(ch); |
| | 0 | 2304 | | if (isLetter) |
| | | 2305 | | { |
| | 0 | 2306 | | ch = Culture.TextInfo.ToLower(ch); |
| | 0 | 2307 | | if (!GlobalizationMode.Invariant && IsHebrewChar(ch) && TokenMask == TokenType.RegularTokenMask) |
| | | 2308 | | { |
| | 0 | 2309 | | if (TryParseHebrewNumber(ref str, out bool badFormat, out tokenValue)) |
| | | 2310 | | { |
| | 0 | 2311 | | if (badFormat) |
| | | 2312 | | { |
| | 0 | 2313 | | tokenType = TokenType.UnknownToken; |
| | 0 | 2314 | | return false; |
| | | 2315 | | } |
| | | 2316 | | // This is a Hebrew number. |
| | | 2317 | | // Do nothing here. TryParseHebrewNumber() will update token accordingly. |
| | 0 | 2318 | | tokenType = TokenType.HebrewNumber; |
| | 0 | 2319 | | return true; |
| | | 2320 | | } |
| | | 2321 | | } |
| | | 2322 | | } |
| | | 2323 | | |
| | 0 | 2324 | | int hashcode = ch % TOKEN_HASH_SIZE; |
| | 0 | 2325 | | int hashProbe = 1 + ch % SECOND_PRIME; |
| | 0 | 2326 | | int remaining = str.Length - str.Index; |
| | 0 | 2327 | | int i = 0; |
| | | 2328 | | |
| | 0 | 2329 | | TokenHashValue[] hashTable = _dtfiTokenHash ?? CreateTokenHashTable(); |
| | | 2330 | | do |
| | | 2331 | | { |
| | 0 | 2332 | | value = hashTable[hashcode]; |
| | 0 | 2333 | | if (value == null) |
| | | 2334 | | { |
| | | 2335 | | // Not found. |
| | | 2336 | | break; |
| | | 2337 | | } |
| | | 2338 | | // Check this value has the right category (regular token or separator token) that we are looking for. |
| | 0 | 2339 | | if (((int)value.tokenType & (int)TokenMask) > 0 && value.tokenString.Length <= remaining) |
| | | 2340 | | { |
| | 0 | 2341 | | bool compareStrings = true; |
| | 0 | 2342 | | if (isLetter) |
| | | 2343 | | { |
| | | 2344 | | // If this token starts with a letter, make sure that we won't allow partial match. So you can' |
| | | 2345 | | // Also an optimization to avoid string comparison |
| | 0 | 2346 | | int nextCharIndex = str.Index + value.tokenString.Length; |
| | 0 | 2347 | | if (nextCharIndex > str.Length) |
| | | 2348 | | { |
| | 0 | 2349 | | compareStrings = false; |
| | | 2350 | | } |
| | 0 | 2351 | | else if (nextCharIndex < str.Length) |
| | | 2352 | | { |
| | | 2353 | | // Check word boundary. The next character should NOT be a letter. |
| | 0 | 2354 | | char nextCh = str.Value[nextCharIndex]; |
| | 0 | 2355 | | compareStrings = !char.IsLetter(nextCh) || IsAllowedJapaneseTokenFollowedByNonSpaceLetter(va |
| | | 2356 | | } |
| | | 2357 | | } |
| | | 2358 | | |
| | 0 | 2359 | | if (compareStrings && |
| | 0 | 2360 | | ((value.tokenString.Length == 1 && str.Value[str.Index] == value.tokenString[0]) || |
| | 0 | 2361 | | Culture.CompareInfo.Compare(str.Value.Slice(str.Index, value.tokenString.Length), value.tokenSt |
| | | 2362 | | { |
| | 0 | 2363 | | tokenType = value.tokenType & TokenMask; |
| | 0 | 2364 | | tokenValue = value.tokenValue; |
| | 0 | 2365 | | str.Advance(value.tokenString.Length); |
| | 0 | 2366 | | return true; |
| | | 2367 | | } |
| | 0 | 2368 | | else if ((value.tokenType == TokenType.MonthToken && HasSpacesInMonthNames) || |
| | 0 | 2369 | | (value.tokenType == TokenType.DayOfWeekToken && HasSpacesInDayNames)) |
| | | 2370 | | { |
| | | 2371 | | // For month or day token, we will match the names which have spaces. |
| | 0 | 2372 | | int matchStrLen = 0; |
| | 0 | 2373 | | if (str.MatchSpecifiedWords(value.tokenString, true, ref matchStrLen)) |
| | | 2374 | | { |
| | 0 | 2375 | | tokenType = value.tokenType & TokenMask; |
| | 0 | 2376 | | tokenValue = value.tokenValue; |
| | 0 | 2377 | | str.Advance(matchStrLen); |
| | 0 | 2378 | | return true; |
| | | 2379 | | } |
| | | 2380 | | } |
| | | 2381 | | } |
| | 0 | 2382 | | i++; |
| | 0 | 2383 | | hashcode += hashProbe; |
| | 0 | 2384 | | if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; |
| | 0 | 2385 | | } while (i < TOKEN_HASH_SIZE); |
| | | 2386 | | |
| | 0 | 2387 | | return false; |
| | | 2388 | | } |
| | | 2389 | | |
| | | 2390 | | private void InsertAtCurrentHashNode(TokenHashValue[] hashTable, string str, char ch, TokenType tokenType, int t |
| | | 2391 | | { |
| | | 2392 | | // Remember the current slot. |
| | 0 | 2393 | | TokenHashValue previousNode = hashTable[hashcode]; |
| | | 2394 | | |
| | | 2395 | | // Insert the new node into the current slot. |
| | 0 | 2396 | | hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue); |
| | | 2397 | | |
| | 0 | 2398 | | while (++pos < TOKEN_HASH_SIZE) |
| | | 2399 | | { |
| | 0 | 2400 | | hashcode += hashProbe; |
| | 0 | 2401 | | if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; |
| | | 2402 | | // Remember this slot |
| | 0 | 2403 | | TokenHashValue temp = hashTable[hashcode]; |
| | | 2404 | | |
| | 0 | 2405 | | if (temp != null && Culture.TextInfo.ToLower(temp.tokenString[0]) != ch) |
| | | 2406 | | { |
| | | 2407 | | continue; |
| | | 2408 | | } |
| | | 2409 | | // Put the previous slot into this slot. |
| | 0 | 2410 | | hashTable[hashcode] = previousNode; |
| | 0 | 2411 | | if (temp == null) |
| | | 2412 | | { |
| | | 2413 | | // Done |
| | 0 | 2414 | | return; |
| | | 2415 | | } |
| | 0 | 2416 | | previousNode = temp; |
| | | 2417 | | } |
| | 0 | 2418 | | Debug.Fail("The hashtable is full. This should not happen."); |
| | | 2419 | | } |
| | | 2420 | | |
| | | 2421 | | private void InsertHash(TokenHashValue[] hashTable, string str, TokenType tokenType, int tokenValue) |
| | | 2422 | | { |
| | | 2423 | | // The month of the 13th month is allowed to be null, so make sure that we ignore null value here. |
| | 0 | 2424 | | if (string.IsNullOrEmpty(str)) |
| | | 2425 | | { |
| | 0 | 2426 | | return; |
| | | 2427 | | } |
| | | 2428 | | |
| | | 2429 | | TokenHashValue value; |
| | 0 | 2430 | | int i = 0; |
| | | 2431 | | // If there is whitespace characters in the beginning and end of the string, trim them since whitespaces are |
| | | 2432 | | // DateTime.Parse(). |
| | 0 | 2433 | | if (char.IsWhiteSpace(str[0]) || char.IsWhiteSpace(str[^1])) |
| | | 2434 | | { |
| | 0 | 2435 | | str = str.Trim(); // Trim white space characters. |
| | | 2436 | | // Could have space for separators |
| | 0 | 2437 | | if (str.Length == 0) |
| | | 2438 | | { |
| | 0 | 2439 | | return; |
| | | 2440 | | } |
| | | 2441 | | } |
| | | 2442 | | |
| | 0 | 2443 | | char ch = Culture.TextInfo.ToLower(str[0]); |
| | 0 | 2444 | | int hashcode = ch % TOKEN_HASH_SIZE; |
| | 0 | 2445 | | int hashProbe = 1 + ch % SECOND_PRIME; |
| | | 2446 | | do |
| | | 2447 | | { |
| | 0 | 2448 | | value = hashTable[hashcode]; |
| | 0 | 2449 | | if (value == null) |
| | | 2450 | | { |
| | 0 | 2451 | | hashTable[hashcode] = new TokenHashValue(str, tokenType, tokenValue); |
| | 0 | 2452 | | return; |
| | | 2453 | | } |
| | | 2454 | | else |
| | | 2455 | | { |
| | | 2456 | | // Collision happens. Find another slot. |
| | 0 | 2457 | | if (str.Length >= value.tokenString.Length) |
| | | 2458 | | { |
| | | 2459 | | // If there are two tokens with the same prefix, we have to make sure that the longer token shou |
| | | 2460 | | // the shorter ones. |
| | 0 | 2461 | | if (CompareStringIgnoreCaseOptimized(str, 0, value.tokenString.Length, value.tokenString, 0, val |
| | | 2462 | | { |
| | 0 | 2463 | | if (str.Length > value.tokenString.Length) |
| | | 2464 | | { |
| | | 2465 | | // The str to be inserted has the same prefix as the current token, and str is longer. |
| | | 2466 | | // Insert str into this node, and shift every node behind it. |
| | 0 | 2467 | | InsertAtCurrentHashNode(hashTable, str, ch, tokenType, tokenValue, i, hashcode, hashProb |
| | 0 | 2468 | | return; |
| | | 2469 | | } |
| | | 2470 | | else |
| | | 2471 | | { |
| | | 2472 | | // Same token. If they have different types (regular token vs separator token). Add th |
| | | 2473 | | // If we have the same regular token or separator token in the hash already, do NOT upda |
| | | 2474 | | // Therefore, the order of inserting token is significant here regarding what tokenType |
| | | 2475 | | |
| | | 2476 | | // Check the current value of RegularToken (stored in the lower 8-bit of tokenType) , an |
| | | 2477 | | // Also check the current value of SeparatorToken (stored in the upper 8-bit of token), |
| | | 2478 | | |
| | 0 | 2479 | | int nTokenType = (int)tokenType; |
| | 0 | 2480 | | int nCurrentTokenTypeInHash = (int)value.tokenType; |
| | | 2481 | | |
| | | 2482 | | // The following is the fix for the issue of throwing FormatException when "mar" is pass |
| | 0 | 2483 | | if (((nCurrentTokenTypeInHash & (int)TokenType.RegularTokenMask) == 0) && ((nTokenType & |
| | 0 | 2484 | | ((nCurrentTokenTypeInHash & (int)TokenType.SeparatorTokenMask) == 0) && ((nTokenType |
| | | 2485 | | { |
| | 0 | 2486 | | value.tokenType |= tokenType; |
| | 0 | 2487 | | if (tokenValue != 0) |
| | | 2488 | | { |
| | 0 | 2489 | | value.tokenValue = tokenValue; |
| | | 2490 | | } |
| | | 2491 | | } |
| | | 2492 | | // The token to be inserted is already in the table. Skip it. |
| | 0 | 2493 | | return; |
| | | 2494 | | } |
| | | 2495 | | } |
| | | 2496 | | } |
| | | 2497 | | } |
| | 0 | 2498 | | i++; |
| | 0 | 2499 | | hashcode += hashProbe; |
| | 0 | 2500 | | if (hashcode >= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; |
| | 0 | 2501 | | } while (i < TOKEN_HASH_SIZE); |
| | 0 | 2502 | | Debug.Fail("The hashtable is full. This should not happen."); |
| | | 2503 | | } |
| | | 2504 | | |
| | | 2505 | | private bool CompareStringIgnoreCaseOptimized(string string1, int offset1, int length1, string string2, int offs |
| | | 2506 | | { |
| | | 2507 | | // Optimize for one character cases which are common due to date and time separators (/ and :) |
| | 0 | 2508 | | if (length1 == 1 && length2 == 1 && string1[offset1] == string2[offset2]) |
| | | 2509 | | { |
| | 0 | 2510 | | return true; |
| | | 2511 | | } |
| | | 2512 | | |
| | 0 | 2513 | | return Culture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, CompareOptions.Igno |
| | | 2514 | | } |
| | | 2515 | | |
| | | 2516 | | internal sealed class TokenHashValue |
| | | 2517 | | { |
| | | 2518 | | internal string tokenString; |
| | | 2519 | | internal TokenType tokenType; |
| | | 2520 | | internal int tokenValue; |
| | | 2521 | | |
| | 0 | 2522 | | internal TokenHashValue(string tokenString, TokenType tokenType, int tokenValue) |
| | | 2523 | | { |
| | 0 | 2524 | | this.tokenString = tokenString; |
| | 0 | 2525 | | this.tokenType = tokenType; |
| | 0 | 2526 | | this.tokenValue = tokenValue; |
| | 0 | 2527 | | } |
| | | 2528 | | } |
| | | 2529 | | } |
| | | 2530 | | } |
| | | 2531 | | |