namespace System
{
- /*
+ /*
Customized format patterns:
P.S. Format in the table below is the internal number format used to display the pattern.
"ddd" short weekday name (abbreviation) Mon
"dddd" full weekday name Monday
"dddd*" full weekday name Monday
-
+
"M" "0" month w/o leading zero 2
"MM" "00" month with leading zero 02
"MMM" short month name (abbreviation) Feb
"MMMM" full month name Febuary
"MMMM*" full month name Febuary
-
+
"y" "0" two digit year (year % 100) w/o leading zero 0
"yy" "00" two digit year (year % 100) with leading zero 00
"yyy" "D3" year 2000
"zz" "+00;-00" timezone offset with leading zero -08
"zzz" "+00;-00" for hour offset, "00" for minute offset full timezone offset -07:30
"zzz*" "+00;-00" for hour offset, "00" for minute offset full timezone offset -08:00
-
+
"K" -Local "zzz", e.g. -08:00
-Utc "'Z'", representing UTC
- -Unspecified ""
+ -Unspecified ""
-DateTimeOffset "zzzzz" e.g -07:30:15
"g*" the current era name A.D.
'"' quoted string "ABC" will insert ABC into the formatted string.
"%" used to quote a single pattern characters E.g.The format character "%y" is to print two digit year.
"\" escaped character E.g. '\d' insert the character 'd' into the format string.
- other characters insert the character into the format string.
+ other characters insert the character into the format string.
- Pre-defined format characters:
+ Pre-defined format characters:
(U) to indicate Universal time is used.
(G) to indicate Gregorian calendar is used.
-
+
Format Description Real format Example
========= ================================= ====================== =======================
"d" short date culture-specific 10/31/1999
*/
- //This class contains only static members and does not require the serializable attribute.
+ //This class contains only static members and does not require the serializable attribute.
internal static
class DateTimeFormat
{
};
////////////////////////////////////////////////////////////////////////////
- //
- // Format the positive integer value to a string and perfix with assigned
+ //
+ // Format the positive integer value to a string and perfix with assigned
// length of leading zero.
//
// Parameters:
// value: The value to format
- // len: The maximum length for leading zero.
+ // len: The maximum length for leading zero.
// If the digits of the value is greater than len, no leading zero is added.
//
- // Notes:
+ // Notes:
// The function can format to int.MaxValue.
//
////////////////////////////////////////////////////////////////////////////
//
// Action: Return the Hebrew month name for the specified DateTime.
// Returns: The month name string for the specified DateTime.
- // Arguments:
+ // Arguments:
// time the time to format
- // month The month is the value of HebrewCalendar.GetMonth(time).
+ // month The month is the value of HebrewCalendar.GetMonth(time).
// repeat Return abbreviated month name if repeat=3, or full month name if repeat=4
// dtfi The DateTimeFormatInfo which uses the Hebrew calendars as its calendar.
// Exceptions: None.
- //
+ //
/* Note:
- If DTFI is using Hebrew calendar, GetMonthName()/GetAbbreviatedMonthName() will return month names like this:
+ If DTFI is using Hebrew calendar, GetMonthName()/GetAbbreviatedMonthName() will return month names like this:
1 Hebrew 1st Month
2 Hebrew 2nd Month
.. ...
12 Hebrew 11th Month
13 Hebrew 12th Month
- Therefore, if we are in a regular year, we have to increment the month name if moth is greater or eqaul to 7.
+ Therefore, if we are in a regular year, we have to increment the month name if moth is greater or eqaul to 7.
*/
private static string FormatHebrewMonthName(DateTime time, int month, int repeatCount, DateTimeFormatInfo dtfi)
{
//
// Actions: Check the format to see if we should use genitive month in the formatting.
// Starting at the position (index) in the (format) string, look back and look ahead to
- // see if there is "d" or "dd". In the case like "d MMMM" or "MMMM dd", we can use
+ // see if there is "d" or "dd". In the case like "d MMMM" or "MMMM dd", we can use
// genitive form. Genitive form is not used if there is more than two "d".
// Arguments:
// format The format string to be scanned.
// FormatCustomized
//
// Actions: Format the DateTime instance using the specified format.
- //
+ //
private static StringBuilder FormatCustomized(
DateTime dateTime, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, TimeSpan offset, StringBuilder result)
{
resultBuilderIsPooled = true;
result = StringBuilderCache.Acquire();
}
-
+
// This is a flag to indicate if we are format the dates using Hebrew calendar.
bool isHebrewCalendar = (cal.ID == CalendarId.HEBREW);
+ bool isJapaneseCalendar = (cal.ID == CalendarId.JAPAN);
// This is a flag to indicate if we are formating hour/minute/second only.
bool bTimeOnly = true;
bTimeOnly = false;
break;
case 'M':
- //
+ //
// tokenLen == 1 : Month as digits with no leading zero.
// tokenLen == 2 : Month as digits with leading zero for single-digit months.
// tokenLen == 3 : Month as a three-letter abbreviation.
int year = cal.GetYear(dateTime);
tokenLen = ParseRepeatPattern(format, i, ch);
- if (dtfi.HasForceTwoDigitYears)
+ if (isJapaneseCalendar &&
+ !AppContextSwitches.FormatJapaneseFirstYearAsANumber &&
+ year == 1 &&
+ i + tokenLen < format.Length - 1 &&
+ format[i + tokenLen] == '\'' &&
+ format[i + tokenLen + 1] == DateTimeFormatInfoScanner.CJKYearSuff[0])
+ {
+ // We are formatting a Japanese date with year equals 1 and the year number is followed by the year sign \u5e74
+ // In Japanese dates, the first year in the era is not formatted as a number 1 instead it is formatted as \u5143 which means
+ // first or beginning of the era.
+ result.Append(DateTimeFormatInfo.JapaneseEraStart[0]);
+ }
+ else if (dtfi.HasForceTwoDigitYears)
{
FormatDigits(result, year, tokenLen <= 2 ? tokenLen : 2);
}
break;
case '%':
// Optional format character.
- // For example, format string "%d" will print day of month
+ // For example, format string "%d" will print day of month
// without leading zero. Most of the cases, "%" can be ignored.
nextChar = ParseNextChar(format, i);
// nextChar will be -1 if we already reach the end of the format string.
// Escaped character. Can be used to insert character into the format string.
// For exmple, "\d" will insert the character 'd' into the string.
//
- // NOTENOTE : we can remove this format character if we enforce the enforced quote
+ // NOTENOTE : we can remove this format character if we enforce the enforced quote
// character rule.
// That is, we ask everyone to use single quote or double quote to insert characters,
// then we can remove this character.
if (timeOnly && dateTime.Ticks < Calendar.TicksPerDay)
{
- // For time only format and a time only input, the time offset on 0001/01/01 is less
+ // For time only format and a time only input, the time offset on 0001/01/01 is less
// accurate than the system's current offset because of daylight saving time.
offset = TimeZoneInfo.GetLocalUtcOffset(DateTime.Now, TimeZoneInfoOptions.NoThrowOnInvalidTime);
}
private static void FormatCustomizedRoundripTimeZone(DateTime dateTime, TimeSpan offset, StringBuilder result)
{
// The objective of this format is to round trip the data in the type
- // For DateTime it should round-trip the Kind value and preserve the time zone.
- // DateTimeOffset instance, it should do so by using the internal time zone.
+ // For DateTime it should round-trip the Kind value and preserve the time zone.
+ // DateTimeOffset instance, it should do so by using the internal time zone.
if (offset == NullOffset)
{
realFormat = RoundtripFormat;
break;
case 'r':
- case 'R': // RFC 1123 Standard
+ case 'R': // RFC 1123 Standard
realFormat = dtfi.RFC1123Pattern;
break;
case 's': // Sortable without Time Zone Info
dtfi = DateTimeFormatInfo.InvariantInfo;
break;
case 'r':
- case 'R': // RFC 1123 Standard
+ case 'R': // RFC 1123 Standard
if (offset != NullOffset)
{
// Convert to UTC invariants mean this will be in range
}
dtfi = DateTimeFormatInfo.InvariantInfo;
break;
- case 's': // Sortable without Time Zone Info
+ case 's': // Sortable without Time Zone Info
dtfi = DateTimeFormatInfo.InvariantInfo;
break;
case 'u': // Universal time in sortable format.
// If the time is less than 1 day, consider it as time of day.
// Just print out the short time format.
//
- // This is a workaround for VB, since they use ticks less then one day to be
+ // This is a workaround for VB, since they use ticks less then one day to be
// time of day. In cultures which use calendar other than Gregorian calendar, these
// alternative calendar may not support ticks less than a day.
// For example, Japanese calendar only supports date after 1868/9/8.
// The string contains the default pattern.
// When we initially construct our string[], we set the string to string[0]
- // The "default" Date/time patterns
+ // The "default" Date/time patterns
private string longDatePattern = null;
private string shortDatePattern = null;
private string yearMonthPattern = null;
SR.ArgumentNull_String);
}
- // The Era Name and Abbreviated Era Name
- // for Taiwan Calendar on non-Taiwan SKU returns empty string (which
+ // The Era Name and Abbreviated Era Name
+ // for Taiwan Calendar on non-Taiwan SKU returns empty string (which
// would be matched below) but we don't want the empty string to give
// us an Era number
// confer 85900 DTFI.GetEra("") should fail on all cultures
}
for (int i = 0; i < AbbreviatedEraNames.Length; i++)
{
- // Compare the abbreviated era name in a case-insensitive way for the appropriate culture.
+ // Compare the abbreviated era name in a case-insensitive way for the appropriate culture.
if (this.Culture.CompareInfo.Compare(eraName, m_abbrevEraNames[i], CompareOptions.IgnoreCase) == 0)
{
return (i + 1);
internal const string RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
internal const string RoundtripDateTimeUnfixed = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz";
- // Default string isn't necessarily in our string array, so get the
+ // Default string isn't necessarily in our string array, so get the
// merged patterns of both
private string[] AllYearMonthPatterns
{
private DateTimeFormatFlags InitializeFormatFlags()
{
// Build the format flags from the data in this DTFI
- formatFlags =
+ formatFlags =
(DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagGenitiveMonth(
MonthNames, internalGetGenitiveMonthNames(false), AbbreviatedMonthNames, internalGetGenitiveMonthNames(true)) |
(DateTimeFormatFlags)DateTimeFormatInfoScanner.GetFormatFlagUseSpaceInMonthNames(
internal const string CJKMinuteSuff = "\u5206";
internal const string CJKSecondSuff = "\u79d2";
+ internal const string JapaneseEraStart = "\u5143";
+
internal const string LocalTimeMark = "T";
internal const string GMTName = "GMT";
InsertHash(temp, CJKMinuteSuff, TokenType.SEP_MinuteSuff, 0);
InsertHash(temp, CJKSecondSuff, TokenType.SEP_SecondSuff, 0);
+ if (!AppContextSwitches.EnforceLegacyJapaneseDateParsing && Calendar.ID == CalendarId.JAPAN)
+ {
+ // We need to support parsing the dates has the start of era symbol which means it is year 1 in the era.
+ // The start of era symbol has to be followed by the year symbol suffix, otherwise it would be invalid date.
+ InsertHash(temp, JapaneseEraStart, TokenType.YearNumberToken, 1);
+ InsertHash(temp, "(", TokenType.IgnorableSymbol, 0);
+ InsertHash(temp, ")", TokenType.IgnorableSymbol, 0);
+ }
+
// TODO: This ignores other custom cultures that might want to do something similar
if (koreanLanguage)
{
return (ch >= '\x0590' && ch <= '\x05ff');
}
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private bool IsAllowedJapaneseTokenFollowedByNonSpaceLetter(string tokenString, char nextCh)
+ {
+ // Allow the parser to recognize the case when having some date part followed by JapaneseEraStart "\u5143"
+ // without spaces in between. e.g. Era name followed by \u5143 in the date formats ggy.
+ // Also, allow recognizing the year suffix symbol "\u5e74" followed the JapaneseEraStart "\u5143"
+ if (!AppContextSwitches.EnforceLegacyJapaneseDateParsing && Calendar.ID == CalendarId.JAPAN &&
+ (
+ // something like ggy, era followed by year and the year is specified using the JapaneseEraStart "\u5143"
+ nextCh == JapaneseEraStart[0] ||
+ // JapaneseEraStart followed by year suffix "\u5143"
+ (tokenString == JapaneseEraStart && nextCh == CJKYearSuff[0])
+ ))
+ {
+ return true;
+ }
+ return false;
+ }
+
internal bool Tokenize(TokenType TokenMask, out TokenType tokenType, out int tokenValue,
ref __DTString str)
{
}
else if (nextCharIndex < str.Length)
{
- // Check word boundary. The next character should NOT be a letter.
+ // Check word boundary. The next character should NOT be a letter.
char nextCh = str.Value[nextCharIndex];
- compareStrings = !(char.IsLetter(nextCh));
+ compareStrings = !(char.IsLetter(nextCh)) || IsAllowedJapaneseTokenFollowedByNonSpaceLetter(value.tokenString, nextCh);
}
}
-
+
if (compareStrings &&
((value.tokenString.Length == 1 && str.Value[str.Index] == value.tokenString[0]) ||
Culture.CompareInfo.Compare(str.Value.Slice(str.Index, value.tokenString.Length), value.tokenString, CompareOptions.IgnoreCase) == 0))
Debug.Assert(dtfi != null, "dtfi == null");
//
- // Do a loop through the provided formats and see if we can parse succesfully in
+ // Do a loop through the provided formats and see if we can parse successfully in
// one of the formats.
//
for (int i = 0; i < formats.Length; i++)
return false;
}
- // Check if the parased string only contains hour/minute/second values.
+ // Check if the parsed string only contains hour/minute/second values.
bool bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);
//
return (DateTimeFormat.GetRealFormat(format, dtfi));
}
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ private static bool ParseJapaneseEraStart(ref __DTString str, DateTimeFormatInfo dtfi)
+ {
+ // ParseJapaneseEraStart will be called when parsing the year number. We can have dates which not listing
+ // the year as a number and listing it as JapaneseEraStart symbol (which means year 1).
+ // This will be legitimate date to recognize.
+ if (AppContextSwitches.EnforceLegacyJapaneseDateParsing || dtfi.Calendar.ID != CalendarId.JAPAN || !str.GetNext())
+ return false;
+
+ if (str.m_current != DateTimeFormatInfo.JapaneseEraStart[0])
+ {
+ str.Index--;
+ return false;
+ }
+
+ return true;
+ }
+
private static void ConfigureFormatR(ref DateTimeFormatInfo dtfi, ref ParsingInfo parseInfo, ref DateTimeResult result)
{
parseInfo.calendar = GregorianCalendar.GetDefaultInstance();
case 'y':
tokenLen = format.GetRepeatCount();
bool parseResult;
- if (dtfi.HasForceTwoDigitYears)
+ if (ParseJapaneseEraStart(ref str, dtfi))
+ {
+ tempYear = 1;
+ parseResult = true;
+ }
+ else if (dtfi.HasForceTwoDigitYears)
{
parseResult = ParseDigits(ref str, 1, 4, out tempYear);
}
** When the following general formats are used, the time is assumed to be in Universal time.
**
**Limitations:
- ** Only GregarianCalendar is supported for now.
+ ** Only GregorianCalendar is supported for now.
** Only support GMT timezone.
==============================================================================*/
}
- // Check if the parased string only contains hour/minute/second values.
+ // Check if the parsed string only contains hour/minute/second values.
bTimeOnly = (result.Year == -1 && result.Month == -1 && result.Day == -1);
if (!CheckDefaultDateTime(ref result, ref parseInfo.calendar, styles))
{
internal ref struct __DTString
{
//
- // Value propery: stores the real string to be parsed.
+ // Value property: stores the real string to be parsed.
//
internal ReadOnlySpan<char> Value;
// The length of Value string.
internal int Length => Value.Length;
- // The current chracter to be looked at.
+ // The current character to be looked at.
internal char m_current;
private CompareInfo m_info;
// The index that contains the longest word to match
// Arguments:
// words The string array that contains words to search.
- // maxMatchStrLen [in/out] the initailized maximum length. This parameter can be used to
+ // maxMatchStrLen [in/out] the initialized maximum length. This parameter can be used to
// find the longest match in two string arrays.
//
internal int MatchLongestWords(string[] words, ref int maxMatchStrLen)