diff --git a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj
index bd3f70ad7a344a..0dd5b4be0cc5e0 100644
--- a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj
+++ b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj
@@ -42,6 +42,14 @@
True
TextUtf8RawTextWriter.tt
+
+
+
+
+
+
+
+
TextTemplatingFileGenerator
HtmlEncodedRawTextWriter.cs
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs
new file mode 100644
index 00000000000000..102df6e65b1a53
--- /dev/null
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Converters/DateAndTimeConverter.cs
@@ -0,0 +1,644 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Xml.Schema.DateAndTime.Helpers;
+using System.Xml.Schema.DateAndTime.Specifications;
+
+namespace System.Xml.Schema.DateAndTime.Converters
+{
+ internal static class DateAndTimeConverter
+ {
+ ///
+ /// Maximum number of fraction digits.
+ ///
+ public const short MaxFractionDigits = 7;
+
+ public static readonly int s_Lz_ = "-".Length;
+ public static readonly int s_lz_zz = "-zz".Length;
+ public static readonly int s_lz_zz_ = "-zz:".Length;
+ public static readonly int s_lz_zz_zz = "-zz:zz".Length;
+ public static readonly int s_lzHH = "HH".Length;
+ public static readonly int s_lzHH_ = "HH:".Length;
+ public static readonly int s_lzHH_mm = "HH:mm".Length;
+ public static readonly int s_lzHH_mm_ = "HH:mm:".Length;
+ public static readonly int s_lzHH_mm_ss = "HH:mm:ss".Length;
+ public static readonly int s_lzyyyy = "yyyy".Length;
+ public static readonly int s_lzyyyy_ = "yyyy-".Length;
+ public static readonly int s_lzyyyy_MM = "yyyy-MM".Length;
+ public static readonly int s_lzyyyy_MM_ = "yyyy-MM-".Length;
+ public static readonly int s_lzyyyy_MM_dd = "yyyy-MM-dd".Length;
+ private static readonly int s_Lz__ = "--".Length;
+ private static readonly int s_Lz___ = "---".Length;
+ private static readonly int s_lz___dd = "---dd".Length;
+ private static readonly int s_lz__mm = "--MM".Length;
+ private static readonly int s_lz__mm_ = "--MM-".Length;
+ private static readonly int s_lz__mm__ = "--MM--".Length;
+ private static readonly int s_lz__mm_dd = "--MM-dd".Length;
+ private static readonly int s_lzyyyy_MM_ddT = "yyyy-MM-ddT".Length;
+
+ private static ReadOnlySpan Power10 => [-1, 10, 100, 1000, 10000, 100000, 1000000];
+
+ public static bool TryParse(string text, XsdDateTimeFlags kinds, out DateAndTimeInfo parsedValue)
+ {
+ // Skip leading whitespace
+ int start = 0;
+ while (start < text.Length && char.IsWhiteSpace(text[start]))
+ {
+ start++;
+ }
+
+ if (TryParseAsDateTime(text, kinds, start, out parsedValue))
+ {
+ return true;
+ }
+
+ if (TryParseAsDate(text, kinds, start, out parsedValue))
+ {
+ return true;
+ }
+
+ if (TryParseAsTime(text, kinds, start, out parsedValue))
+ {
+ return true;
+ }
+
+ if (TryParseAsXdrTimeNoTz(text, kinds, start, out parsedValue))
+ {
+ return true;
+ }
+
+ if (TryParseAsGYearOrGYearMonth(text, kinds, start, out parsedValue))
+ {
+ return true;
+ }
+
+ if (TryParseAsGMonthOrGMonthDay(text, kinds, start, out parsedValue))
+ {
+ return true;
+ }
+
+ if (TryParseAsGDay(text, kinds, start, out parsedValue))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool TryParse(string text, out DateInfo parsedValue)
+ {
+ int start = 0;
+ while (start < text.Length && char.IsWhiteSpace(text[start]))
+ {
+ start++;
+ }
+
+ if (TryParseAsDate(text, XsdDateTimeFlags.Date, start, out DateAndTimeInfo rawParsedValue))
+ {
+ parsedValue = rawParsedValue.Date;
+ return true;
+ }
+
+ parsedValue = default;
+ return false;
+ }
+
+ public static bool TryParse(string text, out TimeInfo parsedValue)
+ {
+ const int start = 0;
+
+ DateAndTimeInfo rawParsedValue;
+ string normalizedText = text.Trim();
+
+ if (TryParseAsTime(normalizedText, XsdDateTimeFlags.Time, start, out rawParsedValue))
+ {
+ parsedValue = rawParsedValue.Time;
+ return true;
+ }
+ else
+ {
+ if (TryParseAsDateTime(normalizedText, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.XdrDateTime, start, out rawParsedValue))
+ {
+ parsedValue = rawParsedValue.Time;
+ return true;
+ }
+ }
+
+ parsedValue = default;
+ return false;
+ }
+
+ private static bool TryParseAsDate(
+ string text,
+ XsdDateTimeFlags kinds,
+ int start,
+ out DateAndTimeInfo parsedValue)
+ {
+ DateInfo date;
+ int? zoneHour, zoneMinute;
+ XsdDateTimeKind? kind;
+
+ if (Test(kinds, XsdDateTimeFlags.Date) && TryParseDate(text, start, out date))
+ {
+ if (TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM_dd, out kind, out zoneHour, out zoneMinute))
+ {
+ parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.Date, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+
+ if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T')
+ && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out kind, out _, out zoneHour, out zoneMinute))
+ {
+ parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.Date, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+ }
+
+ parsedValue = default;
+ return false;
+ }
+
+ private static bool TryParseAsDateTime(
+ string text,
+ XsdDateTimeFlags kinds,
+ int start,
+ out DateAndTimeInfo parsedValue)
+ {
+ const XsdDateTimeFlags dateTimeVariants = XsdDateTimeFlags.DateTime
+ | XsdDateTimeFlags.XdrDateTime
+ | XsdDateTimeFlags.XdrDateTimeNoTz;
+
+ DateInfo date;
+ TimeInfo time;
+ int? zoneHour, zoneMinute;
+ XsdDateTimeKind? kind;
+
+ // Choose format starting from the most common and trying not to reparse the same thing too many times
+ if (Test(kinds, dateTimeVariants) && TryParseDate(text, start, out date))
+ {
+ if (Test(kinds, XsdDateTimeFlags.DateTime) &&
+ ParseChar(text, start + s_lzyyyy_MM_dd, 'T') &&
+ TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out kind, out time, out zoneHour, out zoneMinute))
+ {
+ parsedValue = new DateAndTimeInfo(date, kind.Value, time, DateTimeTypeCode.DateTime, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+
+ if (Test(kinds, XsdDateTimeFlags.XdrDateTime))
+ {
+ if (TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM_dd, out kind, out zoneHour, out zoneMinute))
+ {
+ time = default;
+ parsedValue = new DateAndTimeInfo(date, kind.Value, time, DateTimeTypeCode.XdrDateTime, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+
+ if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T')
+ && TryParseTimeAndZoneAndWhitespace(text, start + s_lzyyyy_MM_ddT, out kind, out time, out zoneHour, out zoneMinute))
+ {
+ parsedValue = new DateAndTimeInfo(date, kind.Value, time, DateTimeTypeCode.XdrDateTime, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+ }
+
+ if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz))
+ {
+ if (ParseChar(text, start + s_lzyyyy_MM_dd, 'T'))
+ {
+ if (ParseTimeAndWhitespace(text, start + s_lzyyyy_MM_ddT, out time))
+ {
+ parsedValue = new DateAndTimeInfo(date, XsdDateTimeKind.Unspecified, time, DateTimeTypeCode.XdrDateTime, 0, 0);
+ return true;
+ }
+ }
+ else
+ {
+ time = default;
+ parsedValue = new DateAndTimeInfo(date, XsdDateTimeKind.Unspecified, time, DateTimeTypeCode.XdrDateTime, 0, 0);
+ return true;
+ }
+ }
+ }
+
+ parsedValue = default;
+ return false;
+ }
+
+ private static bool TryParseAsTime(
+ string text,
+ XsdDateTimeFlags kinds,
+ int start,
+ out DateAndTimeInfo parsedValue)
+ {
+ if (Test(kinds, XsdDateTimeFlags.Time)
+ && TryParseTimeAndZoneAndWhitespace(text, start, out XsdDateTimeKind? kind, out TimeInfo time, out int? zoneHour, out int? zoneMinute))
+ {
+ parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, kind.Value, time, DateTimeTypeCode.Time, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+
+ parsedValue = default;
+ return false;
+ }
+
+ private static bool TryParseAsXdrTimeNoTz(
+ string text,
+ XsdDateTimeFlags kinds,
+ int start,
+ out DateAndTimeInfo parsedValue)
+ {
+ if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz) && ParseTimeAndWhitespace(text, start, out TimeInfo time))
+ {
+ parsedValue = new DateAndTimeInfo(DateInfo.DefaultValue, XsdDateTimeKind.Unspecified, time, DateTimeTypeCode.Time, default, default);
+ return true;
+ }
+
+ parsedValue = default;
+ return false;
+ }
+
+ private static bool TryParseAsGYearOrGYearMonth(
+ string text,
+ XsdDateTimeFlags kinds,
+ int start,
+ out DateAndTimeInfo parsedValue)
+ {
+ int? month, year, zoneHour, zoneMinute;
+ DateInfo date;
+ XsdDateTimeKind? kind;
+
+ if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear) && ParseFourDigits(text, start, out year) && 1 <= year)
+ {
+ if (Test(kinds, XsdDateTimeFlags.GYearMonth) &&
+ ParseChar(text, start + s_lzyyyy, '-') &&
+ ParseTwoDigits(text, start + s_lzyyyy_, out month) &&
+ 1 <= month &&
+ month <= 12 &&
+ TryParseZoneAndWhitespace(text, start + s_lzyyyy_MM, out kind, out zoneHour, out zoneMinute))
+ {
+ date = new DateInfo(DateInfo.FirstDay, month.Value, year.Value);
+ parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GYearMonth, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+
+ if (Test(kinds, XsdDateTimeFlags.GYear) && TryParseZoneAndWhitespace(text, start + s_lzyyyy, out kind, out zoneHour, out zoneMinute))
+ {
+ date = new DateInfo(DateInfo.FirstDay, DateInfo.FirstMonth, year.Value);
+ parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GYear, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+ }
+
+ parsedValue = new DateAndTimeInfo(default, XsdDateTimeKind.Unspecified, default, default, default, default);
+ return false;
+ }
+
+ private static bool TryParseAsGMonthOrGMonthDay(
+ string text,
+ XsdDateTimeFlags kinds,
+ int start,
+ out DateAndTimeInfo parsedValue)
+ {
+ int? day, month, zoneHour, zoneMinute;
+ DateInfo date;
+ XsdDateTimeKind? kind;
+
+ if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth) &&
+ ParseChar(text, start, '-') &&
+ ParseChar(text, start + s_Lz_, '-') &&
+ ParseTwoDigits(text, start + s_Lz__, out month) && 1 <= month && month <= 12)
+ {
+ if (Test(kinds, XsdDateTimeFlags.GMonthDay) &&
+ ParseChar(text, start + s_lz__mm, '-') &&
+ ParseTwoDigits(text, start + s_lz__mm_, out day) && 1 <= day && day <= DateTime.DaysInMonth(DateInfo.LeapYear, month.Value) &&
+ TryParseZoneAndWhitespace(text, start + s_lz__mm_dd, out kind, out zoneHour, out zoneMinute))
+ {
+ date = new DateInfo(day.Value, month.Value, DateInfo.LeapYear);
+ parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GMonthDay, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+
+ if (Test(kinds, XsdDateTimeFlags.GMonth) &&
+ (TryParseZoneAndWhitespace(text, start + s_lz__mm, out kind, out zoneHour, out zoneMinute) ||
+ ParseChar(text, start + s_lz__mm, '-') &&
+ ParseChar(text, start + s_lz__mm_, '-') &&
+ TryParseZoneAndWhitespace(text, start + s_lz__mm__, out kind, out zoneHour, out zoneMinute)))
+ {
+ date = new DateInfo(DateInfo.FirstDay, month.Value, DateInfo.LeapYear);
+ parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GMonth, zoneHour.Value, zoneMinute.Value);
+ return true;
+ }
+ }
+
+ parsedValue = new DateAndTimeInfo();
+ return false;
+ }
+
+ private static bool TryParseAsGDay(
+ string text,
+ XsdDateTimeFlags kinds,
+ int start,
+ out DateAndTimeInfo parsedValue)
+ {
+ int? day, zoneHour, zoneMinute;
+ DateInfo date;
+ XsdDateTimeKind? kind;
+
+ if (Test(kinds, XsdDateTimeFlags.GDay) &&
+ ParseChar(text, start, '-') &&
+ ParseChar(text, start + s_Lz_, '-') &&
+ ParseChar(text, start + s_Lz__, '-') &&
+ ParseTwoDigits(text, start + s_Lz___, out day) &&
+ 1 <= day &&
+ day <= DateTime.DaysInMonth(DateInfo.LeapYear, DateInfo.FirstMonth) &&
+ TryParseZoneAndWhitespace(text, start + s_lz___dd, out kind, out zoneHour, out zoneMinute))
+ {
+ date = new DateInfo(day.Value, DateInfo.FirstMonth, DateInfo.LeapYear);
+ parsedValue = new DateAndTimeInfo(date, kind.Value, default, DateTimeTypeCode.GDay, zoneHour.Value, zoneMinute.Value);
+
+ return true;
+ }
+
+ parsedValue = new DateAndTimeInfo();
+ return false;
+ }
+
+ private static bool ParseTwoDigits(
+ string rawValue,
+ int start,
+ [NotNullWhen(true)] out int? num)
+ {
+ if (start + 1 < rawValue.Length)
+ {
+ int d2 = rawValue[start] - '0';
+ int d1 = rawValue[start + 1] - '0';
+ if (0 <= d2 && d2 < 10 &&
+ 0 <= d1 && d1 < 10
+ )
+ {
+ num = d2 * 10 + d1;
+ return true;
+ }
+ }
+
+ num = default;
+ return false;
+ }
+
+ private static bool ParseFourDigits(
+ string rawValue,
+ int start,
+ [NotNullWhen(true)] out int? num)
+ {
+ if (start + 3 < rawValue.Length)
+ {
+ int d4 = rawValue[start] - '0';
+ int d3 = rawValue[start + 1] - '0';
+ int d2 = rawValue[start + 2] - '0';
+ int d1 = rawValue[start + 3] - '0';
+ if (0 <= d4 && d4 < 10 &&
+ 0 <= d3 && d3 < 10 &&
+ 0 <= d2 && d2 < 10 &&
+ 0 <= d1 && d1 < 10
+ )
+ {
+ num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1;
+ return true;
+ }
+ }
+
+ num = default;
+ return false;
+ }
+
+ private static bool ParseChar(string rawValue, int start, char ch)
+ {
+ return start < rawValue.Length && rawValue[start] == ch;
+ }
+
+ private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right)
+ {
+ return (left & right) != 0;
+ }
+
+ private static bool TryParseDate(
+ string rawValue,
+ int start,
+ out DateInfo parsedDate)
+ {
+ int? day, month, year;
+
+ if (ParseFourDigits(rawValue, start, out year) &&
+ 1 <= year &&
+ ParseChar(rawValue, start + s_lzyyyy, '-') &&
+ ParseTwoDigits(rawValue, start + s_lzyyyy_, out month) &&
+ 1 <= month && month <= 12 &&
+ ParseChar(rawValue, start + s_lzyyyy_MM, '-') &&
+ ParseTwoDigits(rawValue, start + s_lzyyyy_MM_, out day) &&
+ 1 <= day &&
+ day <= DateTime.DaysInMonth(year.Value, month.Value)
+ )
+ {
+ parsedDate = new DateInfo(day.Value, month.Value, year.Value);
+ return true;
+ }
+
+ parsedDate = default;
+ return false;
+ }
+
+ private static bool TryParseTime(
+ string rawValue,
+ ref int start,
+ out TimeInfo time)
+ {
+ int? fraction, hour, minute, second;
+
+ if (
+ ParseTwoDigits(rawValue, start, out hour) && hour < 24 &&
+ ParseChar(rawValue, start + s_lzHH, ':') &&
+ ParseTwoDigits(rawValue, start + s_lzHH_, out minute) && minute < 60 &&
+ ParseChar(rawValue, start + s_lzHH_mm, ':') &&
+ ParseTwoDigits(rawValue, start + s_lzHH_mm_, out second) && second < 60
+ )
+ {
+ start += s_lzHH_mm_ss;
+ if (ParseChar(rawValue, start, '.'))
+ {
+ // Parse factional part of seconds
+ // We allow any number of digits, but keep only first 7
+ fraction = 0;
+ int fractionDigits = 0;
+ int round = 0;
+ while (++start < rawValue.Length)
+ {
+ int d = rawValue[start] - '0';
+ if (9u < unchecked((uint)d))
+ { // d < 0 || 9 < d
+ break;
+ }
+ if (fractionDigits < MaxFractionDigits)
+ {
+ fraction = fraction * 10 + d;
+ }
+ else if (fractionDigits == MaxFractionDigits)
+ {
+ if (5 < d)
+ {
+ round = 1;
+ }
+ else if (d == 5)
+ {
+ round = -1;
+ }
+ }
+ else if (round < 0 && d != 0)
+ {
+ round = 1;
+ }
+ fractionDigits++;
+ }
+ if (fractionDigits < MaxFractionDigits)
+ {
+ if (fractionDigits == 0)
+ {
+ time = default;
+ return false; // cannot end with .
+ }
+ fraction *= Power10[MaxFractionDigits - fractionDigits];
+ }
+ else
+ {
+ if (round < 0)
+ {
+ round = fraction.Value & 1;
+ }
+ fraction += round;
+ }
+ }
+ else
+ {
+ fraction = 0;
+ }
+
+ time = new TimeInfo(fraction.Value, hour.Value, minute.Value, second.Value);
+ return true;
+ }
+
+ time = default;
+ return false;
+ }
+
+ private static bool ParseTimeAndWhitespace(
+ string rawValue,
+ int start,
+ out TimeInfo time)
+ {
+ if (TryParseTime(rawValue, ref start, out time))
+ {
+ while (start < rawValue.Length)
+ {
+ start++;
+ }
+ return start == rawValue.Length;
+ }
+
+ return false;
+ }
+
+ private static bool TryParseTimeAndZoneAndWhitespace(
+ string rawValue,
+ int start,
+ [NotNullWhen(true)] out XsdDateTimeKind? kind,
+ out TimeInfo time,
+ [NotNullWhen(true)] out int? zoneHour,
+ [NotNullWhen(true)] out int? zoneMinute)
+ {
+ if (TryParseTime(rawValue, ref start, out time))
+ {
+ if (TryParseZoneAndWhitespace(rawValue, start, out kind, out zoneHour, out zoneMinute))
+ {
+ return true;
+ }
+ }
+
+ kind = default;
+ zoneHour = default;
+ zoneMinute = default;
+ return false;
+ }
+
+ private static bool TryParseZoneAndWhitespace(
+ string rawValue,
+ int start,
+ [NotNullWhen(true)] out XsdDateTimeKind? kind,
+ [NotNullWhen(true)] out int? zoneHour,
+ [NotNullWhen(true)] out int? zoneMinute)
+ {
+ const int zoneHourOfUnspecified = 0;
+ const int zoneHourOfZulu = 0;
+ const int zoneMinuteOfUnspecified = 0;
+ const int zoneMinuteOfZulu = 0;
+
+ if (start < rawValue.Length)
+ {
+ char ch = rawValue[start];
+ if (ch == 'Z' || ch == 'z')
+ {
+ kind = XsdDateTimeKind.Zulu;
+ zoneHour = zoneHourOfZulu;
+ zoneMinute = zoneMinuteOfZulu;
+ start++;
+ }
+ else if (start + 5 < rawValue.Length &&
+ ParseTwoDigits(rawValue, start + s_Lz_, out zoneHour) &&
+ zoneHour <= 99 &&
+ ParseChar(rawValue, start + s_lz_zz, ':') &&
+ ParseTwoDigits(rawValue, start + s_lz_zz_, out zoneMinute) &&
+ zoneMinute <= 99)
+ {
+ switch (ch)
+ {
+ case '-':
+ {
+ kind = XsdDateTimeKind.LocalWestOfZulu;
+ start += s_lz_zz_zz;
+ break;
+ }
+ case '+':
+ {
+ kind = XsdDateTimeKind.LocalEastOfZulu;
+ start += s_lz_zz_zz;
+ break;
+ }
+ default:
+ {
+ kind = default;
+ break;
+ }
+ }
+ }
+ else
+ {
+ kind = XsdDateTimeKind.Unspecified;
+ zoneHour = zoneHourOfUnspecified;
+ zoneMinute = zoneMinuteOfUnspecified;
+ }
+ }
+ else
+ {
+ kind = XsdDateTimeKind.Unspecified;
+ zoneHour = zoneHourOfUnspecified;
+ zoneMinute = zoneMinuteOfUnspecified;
+ }
+
+ while (start < rawValue.Length && char.IsWhiteSpace(rawValue[start]))
+ {
+ start++;
+ }
+
+ return start == rawValue.Length;
+ }
+ }
+}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs
new file mode 100644
index 00000000000000..3307a89606e0cd
--- /dev/null
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateAndTimeInfo.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Xml.Schema.DateAndTime.Specifications;
+
+namespace System.Xml.Schema.DateAndTime.Helpers
+{
+ internal struct DateAndTimeInfo
+ {
+ public DateInfo Date { get; }
+ public XsdDateTimeKind Kind { get; }
+ public TimeInfo Time { get; }
+ public DateTimeTypeCode TypeCode { get; }
+ public int ZoneHour { get; }
+ public int ZoneMinute { get; }
+
+ public DateAndTimeInfo(
+ DateInfo date,
+ XsdDateTimeKind kind,
+ TimeInfo time,
+ DateTimeTypeCode typeCode,
+ int zoneHour,
+ int zoneMinute)
+ {
+ Date = date;
+ Kind = kind;
+ Time = time;
+ TypeCode = typeCode;
+ ZoneHour = zoneHour;
+ ZoneMinute = zoneMinute;
+ }
+
+ public DateAndTimeInfo()
+ : this(
+ default,
+ XsdDateTimeKind.Unspecified,
+ default,
+ default,
+ 0,
+ 0)
+ {
+ }
+ }
+}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateInfo.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateInfo.cs
new file mode 100644
index 00000000000000..c238d3a31af727
--- /dev/null
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/DateInfo.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Xml.Schema.DateAndTime.Helpers
+{
+ internal struct DateInfo
+ {
+ public static readonly DateInfo DefaultValue = new DateInfo(FirstDay, FirstMonth, LeapYear);
+
+ public const int FirstDay = 1;
+ public const int FirstMonth = 1;
+ public const int LeapYear = 1904;
+
+ public int Day { get; }
+ public int Month { get; }
+ public int Year { get; }
+
+ public DateInfo(int day, int month, int year)
+ {
+ Day = day;
+ Month = month;
+ Year = year;
+ }
+ }
+}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/TimeInfo.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/TimeInfo.cs
new file mode 100644
index 00000000000000..f3919c9e6a4ab2
--- /dev/null
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Helpers/TimeInfo.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Xml.Schema.DateAndTime.Helpers
+{
+ internal struct TimeInfo
+ {
+ public int Fraction { get; }
+ public int Hour { get; }
+ public int Microsecond
+ {
+ get
+ {
+ return Convert.ToInt32((Fraction % TimeSpan.TicksPerMillisecond) / TimeSpan.TicksPerMicrosecond);
+ }
+ }
+
+ public int Millisecond
+ {
+ get
+ {
+ return Convert.ToInt32(Fraction / TimeSpan.TicksPerMillisecond);
+ }
+ }
+
+ public int Minute { get; }
+ public int Second { get; }
+
+ public TimeInfo(int fraction, int hour, int minute, int second)
+ {
+ Fraction = fraction;
+ Hour = hour;
+ Minute = minute;
+ Second = second;
+ }
+ }
+}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/DateTimeTypeCode.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/DateTimeTypeCode.cs
new file mode 100644
index 00000000000000..71750fbd57b07e
--- /dev/null
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/DateTimeTypeCode.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Xml.Schema.DateAndTime.Specifications
+{
+ ///
+ /// Subset of represented XML Schema types.
+ ///
+ internal enum DateTimeTypeCode
+ {
+ DateTime,
+ Time,
+ Date,
+ GYearMonth,
+ GYear,
+ GMonthDay,
+ GDay,
+ GMonth,
+ XdrDateTime,
+ }
+}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/XsdDateTimeKind.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/XsdDateTimeKind.cs
new file mode 100644
index 00000000000000..b168b257ab284a
--- /dev/null
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/Specifications/XsdDateTimeKind.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Xml.Schema.DateAndTime.Specifications
+{
+ ///
+ /// Internal representation of .
+ ///
+ internal enum XsdDateTimeKind
+ {
+ Unspecified,
+ Zulu,
+ LocalWestOfZulu, // GMT-1..14, N..Y
+ LocalEastOfZulu // GMT+1..14, A..M
+ }
+}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdDate.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdDate.cs
new file mode 100644
index 00000000000000..aa0daada57a811
--- /dev/null
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdDate.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Xml.Schema.DateAndTime.Converters;
+using System.Xml.Schema.DateAndTime.Helpers;
+
+namespace System.Xml.Schema.DateAndTime
+{
+ internal struct XsdDate : IFormattable
+ {
+ private DateOnly Date { get; set; }
+
+ private XsdDate(DateInfo parsedValue)
+ : this()
+ {
+ Date = new DateOnly(parsedValue.Year, parsedValue.Month, parsedValue.Day);
+ }
+
+ ///
+ public string ToString(string? format, IFormatProvider? formatProvider)
+ {
+ return Date.ToString(format, formatProvider);
+ }
+
+ internal static bool TryParse(string text, out XsdDate result)
+ {
+ if (!DateAndTimeConverter.TryParse(text, out DateInfo parsedValue))
+ {
+ result = default;
+ return false;
+ }
+
+ result = new XsdDate(parsedValue);
+ return true;
+ }
+ }
+}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdTime.cs
new file mode 100644
index 00000000000000..aaa5d484fe6854
--- /dev/null
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/DateAndTime/XsdTime.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Xml.Schema.DateAndTime.Converters;
+using System.Xml.Schema.DateAndTime.Helpers;
+
+namespace System.Xml.Schema.DateAndTime
+{
+ internal struct XsdTime : IFormattable
+ {
+ private TimeOnly Time { get; set; }
+
+ private XsdTime(TimeInfo parsedValue)
+ : this()
+ {
+ Time = new TimeOnly(
+ parsedValue.Hour,
+ parsedValue.Minute,
+ parsedValue.Second,
+ parsedValue.Millisecond,
+ parsedValue.Microsecond);
+ }
+
+ ///
+ public string ToString(string? format, IFormatProvider? formatProvider)
+ {
+ return Time.ToString(format, formatProvider);
+ }
+
+ internal static bool TryParse(string text, out XsdTime result)
+ {
+ if (!DateAndTimeConverter.TryParse(text, out TimeInfo parsedValue))
+ {
+ result = default;
+ return false;
+ }
+
+ result = new XsdTime(parsedValue);
+ return true;
+ }
+ }
+}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs
index c8c301cf323830..fb1b606cd2c589 100644
--- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs
@@ -1,11 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Diagnostics;
using System.Numerics;
using System.Text;
-using System.Xml;
+using System.Xml.Schema.DateAndTime.Converters;
+using System.Xml.Schema.DateAndTime.Helpers;
+using System.Xml.Schema.DateAndTime.Specifications;
namespace System.Xml.Schema
{
@@ -48,30 +49,6 @@ internal struct XsdDateTime
// 7-0 Zone Minutes
private uint _extra;
-
- // Subset of XML Schema types XsdDateTime represents
- private enum DateTimeTypeCode
- {
- DateTime,
- Time,
- Date,
- GYearMonth,
- GYear,
- GMonthDay,
- GDay,
- GMonth,
- XdrDateTime,
- }
-
- // Internal representation of DateTimeKind
- private enum XsdDateTimeKind
- {
- Unspecified,
- Zulu,
- LocalWestOfZulu, // GMT-1..14, N..Y
- LocalEastOfZulu // GMT+1..14, A..M
- }
-
// Masks and shifts used for packing and unpacking extra
private const uint TypeMask = 0xFF000000;
private const uint KindMask = 0x00FF0000;
@@ -81,33 +58,8 @@ private enum XsdDateTimeKind
private const int KindShift = 16;
private const int ZoneHourShift = 8;
- // Maximum number of fraction digits;
- private const short MaxFractionDigits = 7;
private const int TicksToFractionDivisor = 10000000;
- private static readonly int s_lzyyyy = "yyyy".Length;
- private static readonly int s_lzyyyy_ = "yyyy-".Length;
- private static readonly int s_lzyyyy_MM = "yyyy-MM".Length;
- private static readonly int s_lzyyyy_MM_ = "yyyy-MM-".Length;
- private static readonly int s_lzyyyy_MM_dd = "yyyy-MM-dd".Length;
- private static readonly int s_lzyyyy_MM_ddT = "yyyy-MM-ddT".Length;
- private static readonly int s_lzHH = "HH".Length;
- private static readonly int s_lzHH_ = "HH:".Length;
- private static readonly int s_lzHH_mm = "HH:mm".Length;
- private static readonly int s_lzHH_mm_ = "HH:mm:".Length;
- private static readonly int s_lzHH_mm_ss = "HH:mm:ss".Length;
- private static readonly int s_Lz_ = "-".Length;
- private static readonly int s_lz_zz = "-zz".Length;
- private static readonly int s_lz_zz_ = "-zz:".Length;
- private static readonly int s_lz_zz_zz = "-zz:zz".Length;
- private static readonly int s_Lz__ = "--".Length;
- private static readonly int s_lz__mm = "--MM".Length;
- private static readonly int s_lz__mm_ = "--MM-".Length;
- private static readonly int s_lz__mm__ = "--MM--".Length;
- private static readonly int s_lz__mm_dd = "--MM-dd".Length;
- private static readonly int s_Lz___ = "---".Length;
- private static readonly int s_lz___dd = "---dd".Length;
-
// Number of days in a non-leap year
private const int DaysPerYear = 365;
// Number of days in 4 years
@@ -127,38 +79,38 @@ private enum XsdDateTimeKind
///
public XsdDateTime(string text, XsdDateTimeFlags kinds) : this()
{
- Parser parser = default;
- if (!parser.Parse(text, kinds))
+ if (!DateAndTimeConverter.TryParse(text, kinds, out DateAndTimeInfo parsedValue))
{
throw new FormatException(SR.Format(SR.XmlConvert_BadFormat, text, kinds));
}
- InitiateXsdDateTime(parser);
+
+ InitiateXsdDateTime(parsedValue);
}
- private XsdDateTime(Parser parser) : this()
+ private XsdDateTime(DateAndTimeInfo parsedValue) : this()
{
- InitiateXsdDateTime(parser);
+ InitiateXsdDateTime(parsedValue);
}
- private void InitiateXsdDateTime(Parser parser)
+ private void InitiateXsdDateTime(DateAndTimeInfo parsedValue)
{
- _dt = new DateTime(parser.year, parser.month, parser.day, parser.hour, parser.minute, parser.second);
- if (parser.fraction != 0)
+ _dt = new DateTime(parsedValue.Date.Year, parsedValue.Date.Month, parsedValue.Date.Day, parsedValue.Time.Hour, parsedValue.Time.Minute, parsedValue.Time.Second);
+ if (parsedValue.Time.Fraction != 0)
{
- _dt = _dt.AddTicks(parser.fraction);
+ _dt = _dt.AddTicks(parsedValue.Time.Fraction);
}
- _extra = (uint)(((int)parser.typeCode << TypeShift) | ((int)parser.kind << KindShift) | (parser.zoneHour << ZoneHourShift) | parser.zoneMinute);
+ _extra = (uint)(((int)parsedValue.TypeCode << TypeShift) | ((int)parsedValue.Kind << KindShift) | (parsedValue.ZoneHour << ZoneHourShift) | parsedValue.ZoneMinute);
}
internal static bool TryParse(string text, XsdDateTimeFlags kinds, out XsdDateTime result)
{
- Parser parser = default;
- if (!parser.Parse(text, kinds))
+ if (!DateAndTimeConverter.TryParse(text, kinds, out DateAndTimeInfo parsedValue))
{
result = default;
return false;
}
- result = new XsdDateTime(parser);
+
+ result = new XsdDateTime(parsedValue);
return true;
}
@@ -543,14 +495,14 @@ public bool TryFormat(Span destination, out int charsWritten)
// Serialize year, month and day
private void PrintDate(ref ValueStringBuilder vsb)
{
- Span text = vsb.AppendSpan(s_lzyyyy_MM_dd);
+ Span text = vsb.AppendSpan(DateAndTimeConverter.s_lzyyyy_MM_dd);
int year, month, day;
GetYearMonthDay(out year, out month, out day);
WriteXDigits(text, 0, year, 4);
- text[s_lzyyyy] = '-';
- Write2Digits(text, s_lzyyyy_, month);
- text[s_lzyyyy_MM] = '-';
- Write2Digits(text, s_lzyyyy_MM_, day);
+ text[DateAndTimeConverter.s_lzyyyy] = '-';
+ Write2Digits(text, DateAndTimeConverter.s_lzyyyy_, month);
+ text[DateAndTimeConverter.s_lzyyyy_MM] = '-';
+ Write2Digits(text, DateAndTimeConverter.s_lzyyyy_MM_, day);
}
// When printing the date, we need the year, month and the day. When
@@ -609,16 +561,16 @@ private void GetYearMonthDay(out int year, out int month, out int day)
// Serialize hour, minute, second and fraction
private void PrintTime(ref ValueStringBuilder vsb)
{
- Span text = vsb.AppendSpan(s_lzHH_mm_ss);
+ Span text = vsb.AppendSpan(DateAndTimeConverter.s_lzHH_mm_ss);
Write2Digits(text, 0, Hour);
- text[s_lzHH] = ':';
- Write2Digits(text, s_lzHH_, Minute);
- text[s_lzHH_mm] = ':';
- Write2Digits(text, s_lzHH_mm_, Second);
+ text[DateAndTimeConverter.s_lzHH] = ':';
+ Write2Digits(text, DateAndTimeConverter.s_lzHH_, Minute);
+ text[DateAndTimeConverter.s_lzHH_mm] = ':';
+ Write2Digits(text, DateAndTimeConverter.s_lzHH_mm_, Second);
int fraction = Fraction;
if (fraction != 0)
{
- int fractionDigits = MaxFractionDigits;
+ int fractionDigits = DateAndTimeConverter.MaxFractionDigits;
while (fraction % 10 == 0)
{
fractionDigits--;
@@ -641,18 +593,18 @@ private void PrintZone(ref ValueStringBuilder vsb)
vsb.Append('Z');
break;
case XsdDateTimeKind.LocalWestOfZulu:
- text = vsb.AppendSpan(s_lz_zz_zz);
+ text = vsb.AppendSpan(DateAndTimeConverter.s_lz_zz_zz);
text[0] = '-';
- Write2Digits(text, s_Lz_, ZoneHour);
- text[s_lz_zz] = ':';
- Write2Digits(text, s_lz_zz_, ZoneMinute);
+ Write2Digits(text, DateAndTimeConverter.s_Lz_, ZoneHour);
+ text[DateAndTimeConverter.s_lz_zz] = ':';
+ Write2Digits(text, DateAndTimeConverter.s_lz_zz_, ZoneMinute);
break;
case XsdDateTimeKind.LocalEastOfZulu:
- text = vsb.AppendSpan(s_lz_zz_zz);
+ text = vsb.AppendSpan(DateAndTimeConverter.s_lz_zz_zz);
text[0] = '+';
- Write2Digits(text, s_Lz_, ZoneHour);
- text[s_lz_zz] = ':';
- Write2Digits(text, s_lz_zz_, ZoneMinute);
+ Write2Digits(text, DateAndTimeConverter.s_Lz_, ZoneHour);
+ text[DateAndTimeConverter.s_lz_zz] = ':';
+ Write2Digits(text, DateAndTimeConverter.s_lz_zz_, ZoneMinute);
break;
default:
// do nothing
@@ -688,388 +640,5 @@ private static void Write2Digits(Span text, int start, int value)
XmlTypeCode.GDay,
XmlTypeCode.GMonth
};
-
-
- // Parsing string according to XML schema spec
- private struct Parser
- {
- private const int leapYear = 1904;
- private const int firstMonth = 1;
- private const int firstDay = 1;
-
- public DateTimeTypeCode typeCode;
- public int year;
- public int month;
- public int day;
- public int hour;
- public int minute;
- public int second;
- public int fraction;
- public XsdDateTimeKind kind;
- public int zoneHour;
- public int zoneMinute;
-
- private string _text;
- private int _length;
-
- public bool Parse(string text, XsdDateTimeFlags kinds)
- {
- _text = text;
- _length = text.Length;
-
- // Skip leading whitespace
- int start = 0;
- while (start < _length && char.IsWhiteSpace(text[start]))
- {
- start++;
- }
- // Choose format starting from the most common and trying not to reparse the same thing too many times
- if (Test(kinds, XsdDateTimeFlags.DateTime | XsdDateTimeFlags.Date | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrDateTimeNoTz))
- {
- if (ParseDate(start))
- {
- if (Test(kinds, XsdDateTimeFlags.DateTime))
- {
- if (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT))
- {
- typeCode = DateTimeTypeCode.DateTime;
- return true;
- }
- }
- if (Test(kinds, XsdDateTimeFlags.Date))
- {
- if (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd))
- {
- typeCode = DateTimeTypeCode.Date;
- return true;
- }
- }
-
- if (Test(kinds, XsdDateTimeFlags.XdrDateTime))
- {
- if (ParseZoneAndWhitespace(start + s_lzyyyy_MM_dd) || (ParseChar(start + s_lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(start + s_lzyyyy_MM_ddT)))
- {
- typeCode = DateTimeTypeCode.XdrDateTime;
- return true;
- }
- }
- if (Test(kinds, XsdDateTimeFlags.XdrDateTimeNoTz))
- {
- if (ParseChar(start + s_lzyyyy_MM_dd, 'T'))
- {
- if (ParseTimeAndWhitespace(start + s_lzyyyy_MM_ddT))
- {
- typeCode = DateTimeTypeCode.XdrDateTime;
- return true;
- }
- }
- else
- {
- typeCode = DateTimeTypeCode.XdrDateTime;
- return true;
- }
- }
- }
- }
-
- if (Test(kinds, XsdDateTimeFlags.Time))
- {
- if (ParseTimeAndZoneAndWhitespace(start))
- { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time
- year = leapYear;
- month = firstMonth;
- day = firstDay;
- typeCode = DateTimeTypeCode.Time;
- return true;
- }
- }
-
- if (Test(kinds, XsdDateTimeFlags.XdrTimeNoTz))
- {
- if (ParseTimeAndWhitespace(start))
- { //Equivalent to NoCurrentDateDefault on DateTimeStyles while parsing xs:time
- year = leapYear;
- month = firstMonth;
- day = firstDay;
- typeCode = DateTimeTypeCode.Time;
- return true;
- }
- }
-
- if (Test(kinds, XsdDateTimeFlags.GYearMonth | XsdDateTimeFlags.GYear))
- {
- if (Parse4Dig(start, ref year) && 1 <= year)
- {
- if (Test(kinds, XsdDateTimeFlags.GYearMonth))
- {
- if (
- ParseChar(start + s_lzyyyy, '-') &&
- Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 &&
- ParseZoneAndWhitespace(start + s_lzyyyy_MM)
- )
- {
- day = firstDay;
- typeCode = DateTimeTypeCode.GYearMonth;
- return true;
- }
- }
- if (Test(kinds, XsdDateTimeFlags.GYear))
- {
- if (ParseZoneAndWhitespace(start + s_lzyyyy))
- {
- month = firstMonth;
- day = firstDay;
- typeCode = DateTimeTypeCode.GYear;
- return true;
- }
- }
- }
- }
- if (Test(kinds, XsdDateTimeFlags.GMonthDay | XsdDateTimeFlags.GMonth))
- {
- if (
- ParseChar(start, '-') &&
- ParseChar(start + s_Lz_, '-') &&
- Parse2Dig(start + s_Lz__, ref month) && 1 <= month && month <= 12
- )
- {
- if (Test(kinds, XsdDateTimeFlags.GMonthDay) && ParseChar(start + s_lz__mm, '-'))
- {
- if (
- Parse2Dig(start + s_lz__mm_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, month) &&
- ParseZoneAndWhitespace(start + s_lz__mm_dd)
- )
- {
- year = leapYear;
- typeCode = DateTimeTypeCode.GMonthDay;
- return true;
- }
- }
- if (Test(kinds, XsdDateTimeFlags.GMonth))
- {
- if (ParseZoneAndWhitespace(start + s_lz__mm) || (ParseChar(start + s_lz__mm, '-') && ParseChar(start + s_lz__mm_, '-') && ParseZoneAndWhitespace(start + s_lz__mm__)))
- {
- year = leapYear;
- day = firstDay;
- typeCode = DateTimeTypeCode.GMonth;
- return true;
- }
- }
- }
- }
- if (Test(kinds, XsdDateTimeFlags.GDay))
- {
- if (
- ParseChar(start, '-') &&
- ParseChar(start + s_Lz_, '-') &&
- ParseChar(start + s_Lz__, '-') &&
- Parse2Dig(start + s_Lz___, ref day) && 1 <= day && day <= DateTime.DaysInMonth(leapYear, firstMonth) &&
- ParseZoneAndWhitespace(start + s_lz___dd)
-
- )
- {
- year = leapYear;
- month = firstMonth;
- typeCode = DateTimeTypeCode.GDay;
- return true;
- }
- }
- return false;
- }
-
-
- private bool ParseDate(int start)
- {
- return
- Parse4Dig(start, ref year) && 1 <= year &&
- ParseChar(start + s_lzyyyy, '-') &&
- Parse2Dig(start + s_lzyyyy_, ref month) && 1 <= month && month <= 12 &&
- ParseChar(start + s_lzyyyy_MM, '-') &&
- Parse2Dig(start + s_lzyyyy_MM_, ref day) && 1 <= day && day <= DateTime.DaysInMonth(year, month);
- }
-
- private bool ParseTimeAndZoneAndWhitespace(int start)
- {
- if (ParseTime(ref start))
- {
- if (ParseZoneAndWhitespace(start))
- {
- return true;
- }
- }
- return false;
- }
-
- private bool ParseTimeAndWhitespace(int start)
- {
- if (ParseTime(ref start))
- {
- while (start < _length)
- {//&& char.IsWhiteSpace(text[start])) {
- start++;
- }
- return start == _length;
- }
- return false;
- }
-
- private static ReadOnlySpan Power10 => [-1, 10, 100, 1000, 10000, 100000, 1000000];
- private bool ParseTime(ref int start)
- {
- if (
- Parse2Dig(start, ref hour) && hour < 24 &&
- ParseChar(start + s_lzHH, ':') &&
- Parse2Dig(start + s_lzHH_, ref minute) && minute < 60 &&
- ParseChar(start + s_lzHH_mm, ':') &&
- Parse2Dig(start + s_lzHH_mm_, ref second) && second < 60
- )
- {
- start += s_lzHH_mm_ss;
- if (ParseChar(start, '.'))
- {
- // Parse factional part of seconds
- // We allow any number of digits, but keep only first 7
- this.fraction = 0;
- int fractionDigits = 0;
- int round = 0;
- while (++start < _length)
- {
- int d = _text[start] - '0';
- if (9u < unchecked((uint)d))
- { // d < 0 || 9 < d
- break;
- }
- if (fractionDigits < MaxFractionDigits)
- {
- this.fraction = (this.fraction * 10) + d;
- }
- else if (fractionDigits == MaxFractionDigits)
- {
- if (5 < d)
- {
- round = 1;
- }
- else if (d == 5)
- {
- round = -1;
- }
- }
- else if (round < 0 && d != 0)
- {
- round = 1;
- }
- fractionDigits++;
- }
- if (fractionDigits < MaxFractionDigits)
- {
- if (fractionDigits == 0)
- {
- return false; // cannot end with .
- }
- fraction *= Power10[MaxFractionDigits - fractionDigits];
- }
- else
- {
- if (round < 0)
- {
- round = fraction & 1;
- }
- fraction += round;
- }
- }
- return true;
- }
- // cleanup - conflict with gYear
- hour = 0;
- return false;
- }
-
- private bool ParseZoneAndWhitespace(int start)
- {
- if (start < _length)
- {
- char ch = _text[start];
- if (ch == 'Z' || ch == 'z')
- {
- kind = XsdDateTimeKind.Zulu;
- start++;
- }
- else if (start + 5 < _length)
- {
- if (
- Parse2Dig(start + s_Lz_, ref zoneHour) && zoneHour <= 99 &&
- ParseChar(start + s_lz_zz, ':') &&
- Parse2Dig(start + s_lz_zz_, ref zoneMinute) && zoneMinute <= 99
- )
- {
- if (ch == '-')
- {
- kind = XsdDateTimeKind.LocalWestOfZulu;
- start += s_lz_zz_zz;
- }
- else if (ch == '+')
- {
- kind = XsdDateTimeKind.LocalEastOfZulu;
- start += s_lz_zz_zz;
- }
- }
- }
- }
- while (start < _length && char.IsWhiteSpace(_text[start]))
- {
- start++;
- }
- return start == _length;
- }
-
-
- private bool Parse4Dig(int start, ref int num)
- {
- if (start + 3 < _length)
- {
- int d4 = _text[start] - '0';
- int d3 = _text[start + 1] - '0';
- int d2 = _text[start + 2] - '0';
- int d1 = _text[start + 3] - '0';
- if (0 <= d4 && d4 < 10 &&
- 0 <= d3 && d3 < 10 &&
- 0 <= d2 && d2 < 10 &&
- 0 <= d1 && d1 < 10
- )
- {
- num = ((d4 * 10 + d3) * 10 + d2) * 10 + d1;
- return true;
- }
- }
- return false;
- }
-
- private bool Parse2Dig(int start, ref int num)
- {
- if (start + 1 < _length)
- {
- int d2 = _text[start] - '0';
- int d1 = _text[start + 1] - '0';
- if (0 <= d2 && d2 < 10 &&
- 0 <= d1 && d1 < 10
- )
- {
- num = d2 * 10 + d1;
- return true;
- }
- }
- return false;
- }
-
- private bool ParseChar(int start, char ch)
- {
- return start < _length && _text[start] == ch;
- }
-
- private static bool Test(XsdDateTimeFlags left, XsdDateTimeFlags right)
- {
- return (left & right) != 0;
- }
- }
}
}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs
index 7d8613a4d7d3cd..476b81d5d96393 100644
--- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs
@@ -13,6 +13,7 @@
using System.Runtime.Versioning;
using System.Text;
using System.Xml.Schema;
+using System.Xml.Schema.DateAndTime;
using System.Xml.XPath;
using System.Xml.Xsl.Xslt;
@@ -309,6 +310,54 @@ public static string EXslObjectType(IList value)
// Msxml Extension Functions
//------------------------------------------------
+ ///
+ /// Format xsd:date as a date string for a given language using a given format string.
+ ///
+ /// Lexical representation of xsd:date.
+ /// Format string.
+ /// Specifies a culture used for formatting.
+ /// formatted as a date string.
+ public static string MSFormatDate(string date, string format, string lang)
+ {
+ try
+ {
+ if (!XsdDate.TryParse(date, out XsdDate xsdDate))
+ {
+ return string.Empty;
+ }
+
+ return xsdDate.ToString(format.Length != 0 ? format : null, GetCultureInfo(lang));
+ }
+ catch (ArgumentException)
+ {
+ return string.Empty;
+ }
+ }
+
+ ///
+ /// Format xsd:time as a time string for a given language using a given format string.
+ ///
+ /// Lexical representation of xsd:time.
+ /// Format string.
+ /// Specifies a culture used for formatting.
+ /// formatted as a time string.
+ public static string MSFormatTime(string time, string format, string lang)
+ {
+ try
+ {
+ if (!XsdTime.TryParse(time, out XsdTime xsdTime))
+ {
+ return string.Empty;
+ }
+
+ return xsdTime.ToString(format.Length != 0 ? format : null, GetCultureInfo(lang));
+ }
+ catch (ArgumentException)
+ {
+ return string.Empty;
+ }
+ }
+
public static double MSNumber(IList value)
{
XsltLibrary.CheckXsltValue(value);
@@ -351,7 +400,6 @@ public static double MSNumber(IList value)
return d;
}
- // string ms:format-date(string datetime[, string format[, string language]])
// string ms:format-time(string datetime[, string format[, string language]])
//
// Format xsd:dateTime as a date/time string for a given language using a given format string.
@@ -379,7 +427,7 @@ public static string MSFormatDateTime(string dateTime, string format, string lan
return dt.ToString(format.Length != 0 ? format : null, new CultureInfo(locale));
}
catch (ArgumentException)
- { // Operations with DateTime can throw this exception eventualy
+ { // Operations with DateTime can throw this exception eventually
return string.Empty;
}
}
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs
index 07e6f7585ff94b..2ab804e892a652 100644
--- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltLibrary.cs
@@ -47,7 +47,11 @@ internal static class XsltMethods
public static readonly MethodInfo OnCurrentNodeChanged = typeof(XmlQueryRuntime).GetMethod("OnCurrentNodeChanged")!;
// MSXML extension functions
+ public static readonly MethodInfo MSFormatDate = typeof(XsltFunctions)
+ .GetMethod(nameof(XsltFunctions.MSFormatDate))!;
public static readonly MethodInfo MSFormatDateTime = typeof(XsltFunctions).GetMethod("MSFormatDateTime")!;
+ public static readonly MethodInfo MSFormatTime = typeof(XsltFunctions)
+ .GetMethod(nameof(XsltFunctions.MSFormatTime))!;
public static readonly MethodInfo MSStringCompare = typeof(XsltFunctions).GetMethod("MSStringCompare")!;
public static readonly MethodInfo MSUtc = typeof(XsltFunctions).GetMethod("MSUtc")!;
public static readonly MethodInfo MSNumber = typeof(XsltFunctions).GetMethod("MSNumber")!;
diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs
index f0410142b8ca5a..765459cf0acb2f 100644
--- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs
+++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Xslt/QilGeneratorEnv.cs
@@ -166,14 +166,22 @@ QilNode IXPathEnvironment.ResolveFunction(string prefix, string name, IList
-
- Monday, June 15, 2009
- 6/15/2009
- 6/15/2009 1:45:30 PM
- 6/15/2009
- lundi 15 juin 2009
- 15/06/2009
-
+
+ Monday, 15 June 2009
+ 06/15/2009
+ 06/15/2009
+ 6/15/2009
+ lundi 15 juin 2009
+ 15/06/2009
+
- 1:45:30 PM
- 1:45 PM
- 6/15/2009 1:45:30 PM
- 1:45 PM
- 13:45:30
- 13:45
-
+ 13:45:30
+ 13:45
+ 13:45
+ 01:45:30.876PM
+ 1:45 PM
+ 13:45:30
+ 13:45
+
diff --git a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml
index 19f20d615e9684..cbbd67b4b81021 100644
--- a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml
+++ b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xml
@@ -1,3 +1,3 @@
- 2009-06-15T13:45:30
+ 2009-06-15T13:45:30.87654321
diff --git a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl
index 88932a64469ab6..14b033f9bcf2d7 100644
--- a/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl
+++ b/src/libraries/System.Private.Xml/tests/Xslt/TestFiles/TestData/XsltApiV2/bug93189.xsl
@@ -1,48 +1,51 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/Errata4.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/Errata4.cs
index 0b5c13f8d95c0a..d065091d04685f 100644
--- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/Errata4.cs
+++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/Errata4.cs
@@ -373,10 +373,5 @@ private string GenerateString(char c, CharType charType)
throw new CTestFailedException("TEST ISSUE: CharType FAILURE!");
}
}
-
- public new int Init(object objParam)
- {
- return 1;
- }
}
}
diff --git a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs
index 00511a00ec6f19..d586c2dab8c5b6 100644
--- a/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs
+++ b/src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompiledTransform.cs
@@ -4,10 +4,12 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Loader;
+using System.Tests;
using System.Text;
using System.Xml.Tests;
using System.Xml.XPath;
@@ -3106,6 +3108,252 @@ public void TransformStrStrResolver3(object param, XslInputType xslInputType, Re
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))]
public class CTransformConstructorWithFourParametersTest : XsltApiTestCaseBase2
{
+ public static IEnumerable