Skip to content

Add DateOnly/TimeOnly support to XmlSerializer and DataContractSerializer #118549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,8 @@ private static RuntimeTypeHandle GetDataContractAdapterTypeHandle(RuntimeTypeHan
"Byte[]" => typeof(byte[]),
"Object" => typeof(object),
"TimeSpan" => typeof(TimeSpan),
"DateOnly" => typeof(DateOnly),
"TimeOnly" => typeof(TimeOnly),
"Guid" => typeof(Guid),
"Uri" => typeof(Uri),
"Xml.XmlQualifiedName" => typeof(XmlQualifiedName),
Expand Down Expand Up @@ -745,6 +747,10 @@ internal static bool TryCreateBuiltInDataContract(Type type, [NotNullWhen(true)]
dataContract = new QNameDataContract();
else if (type == typeof(TimeSpan))
dataContract = new TimeSpanDataContract();
else if (type == typeof(DateOnly))
dataContract = new DateOnlyDataContract();
else if (type == typeof(TimeOnly))
dataContract = new TimeOnlyDataContract();
else if (type == typeof(Guid))
dataContract = new GuidDataContract();
else if (type == typeof(Enum) || type == typeof(ValueType))
Expand Down Expand Up @@ -864,6 +870,10 @@ internal static bool TryCreateBuiltInDataContract(string name, string ns, [NotNu
dataContract = new GuidDataContract();
else if (DictionaryGlobals.CharLocalName.Value == name)
dataContract = new CharDataContract();
else if (DictionaryGlobals.DateOnlyLocalName.Value == name)
dataContract = new DateOnlyDataContract();
else if (DictionaryGlobals.TimeOnlyLocalName.Value == name)
dataContract = new TimeOnlyDataContract();
else if ("ArrayOfanyType" == name)
dataContract = new CollectionDataContract(typeof(Array));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Runtime.Serialization
internal static class DictionaryGlobals
{
// Update array size when adding new strings or templates
private static readonly XmlDictionary s_dictionary = new XmlDictionary(61);
private static readonly XmlDictionary s_dictionary = new XmlDictionary(63);

// 0
public static readonly XmlDictionaryString SchemaInstanceNamespace = s_dictionary.Add(Globals.SchemaInstanceNamespace);
Expand Down Expand Up @@ -96,5 +96,7 @@ internal static class DictionaryGlobals

// 60
public static readonly XmlDictionaryString AsmxTypesNamespace = s_dictionary.Add("http://microsoft.com/wsdl/types/");
public static readonly XmlDictionaryString DateOnlyLocalName = s_dictionary.Add("DateOnly");
public static readonly XmlDictionaryString TimeOnlyLocalName = s_dictionary.Add("TimeOnly");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ internal static partial class Globals
internal static Type TypeOfVoid => field ??= typeof(void);
internal static Type TypeOfByteArray => field ??= typeof(byte[]);
internal static Type TypeOfTimeSpan => field ??= typeof(TimeSpan);
internal static Type TypeOfDateOnly => field ??= typeof(DateOnly);
internal static Type TypeOfTimeOnly => field ??= typeof(TimeOnly);
internal static Type TypeOfGuid => field ??= typeof(Guid);
internal static Type TypeOfDateTimeOffset => field ??= typeof(DateTimeOffset);
internal static Type TypeOfDateTimeOffsetAdapter => field ??= typeof(DateTimeOffsetAdapter);
Comment on lines 34 to 41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's highly doubtful that caching the result of typeof() is helpful; we can remove it either now or in a separate PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, caching typeof like this is a de-optimization in current .NET. It would be best to delete these fields and use typeof directly in all places they are used.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,70 @@ internal sealed class AsmxGuidDataContract : GuidDataContract
internal AsmxGuidDataContract() : base(DictionaryGlobals.GuidLocalName, DictionaryGlobals.AsmxTypesNamespace) { }
}

internal sealed class DateOnlyDataContract : PrimitiveDataContract
{
public DateOnlyDataContract() : base(typeof(DateOnly), DictionaryGlobals.DateOnlyLocalName, DictionaryGlobals.SerializationNamespace)
{
}

internal override string WriteMethodName => "WriteDateOnly";
internal override string ReadMethodName => "ReadElementContentAsDateOnly";

[RequiresDynamicCode(DataContract.SerializerAOTWarning)]
[RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
internal override void WriteXmlValue(XmlWriterDelegator writer, object obj, XmlObjectSerializerWriteContext? context)
{
writer.WriteDateOnly((DateOnly)obj);
}

[RequiresDynamicCode(DataContract.SerializerAOTWarning)]
[RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
internal override object? ReadXmlValue(XmlReaderDelegator reader, XmlObjectSerializerReadContext? context)
{
return (context == null) ? reader.ReadElementContentAsDateOnly()
: HandleReadValue(reader.ReadElementContentAsDateOnly(), context);
}

[RequiresDynamicCode(DataContract.SerializerAOTWarning)]
[RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
internal override void WriteXmlElement(XmlWriterDelegator xmlWriter, object? obj, XmlObjectSerializerWriteContext context, XmlDictionaryString name, XmlDictionaryString? ns)
{
xmlWriter.WriteDateOnly((DateOnly)obj!, name, ns);
}
}

internal sealed class TimeOnlyDataContract : PrimitiveDataContract
{
public TimeOnlyDataContract() : base(typeof(TimeOnly), DictionaryGlobals.TimeOnlyLocalName, DictionaryGlobals.SerializationNamespace)
{
}

internal override string WriteMethodName => "WriteTimeOnly";
internal override string ReadMethodName => "ReadElementContentAsTimeOnly";

[RequiresDynamicCode(DataContract.SerializerAOTWarning)]
[RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
internal override void WriteXmlValue(XmlWriterDelegator writer, object obj, XmlObjectSerializerWriteContext? context)
{
writer.WriteTimeOnly((TimeOnly)obj);
}

[RequiresDynamicCode(DataContract.SerializerAOTWarning)]
[RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
internal override object? ReadXmlValue(XmlReaderDelegator reader, XmlObjectSerializerReadContext? context)
{
return (context == null) ? reader.ReadElementContentAsTimeOnly()
: HandleReadValue(reader.ReadElementContentAsTimeOnly(), context);
}

[RequiresDynamicCode(DataContract.SerializerAOTWarning)]
[RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
internal override void WriteXmlElement(XmlWriterDelegator xmlWriter, object? obj, XmlObjectSerializerWriteContext context, XmlDictionaryString name, XmlDictionaryString? ns)
{
xmlWriter.WriteTimeOnly((TimeOnly)obj!, name, ns);
}
}

internal sealed class UriDataContract : PrimitiveDataContract
{
public UriDataContract() : base(typeof(Uri), DictionaryGlobals.UriLocalName, DictionaryGlobals.SchemaNamespace)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,54 @@ internal Guid ReadElementContentAsGuid()
}
}

internal DateOnly ReadElementContentAsDateOnly()
{
if (isEndOfEmptyElement)
ThrowNotAtElement();

string str = reader.ReadElementContentAsString();
try
{
return XmlConvert.ToDateOnly(str);
}
catch (ArgumentException exception)
{
throw XmlExceptionHelper.CreateConversionException(str, "DateOnly", exception);
}
catch (FormatException exception)
{
throw XmlExceptionHelper.CreateConversionException(str, "DateOnly", exception);
}
catch (OverflowException exception)
{
throw XmlExceptionHelper.CreateConversionException(str, "DateOnly", exception);
}
}

internal TimeOnly ReadElementContentAsTimeOnly()
{
if (isEndOfEmptyElement)
ThrowNotAtElement();

string str = reader.ReadElementContentAsString();
try
{
return XmlConvert.ToTimeOnly(str);
}
catch (ArgumentException exception)
{
throw XmlExceptionHelper.CreateConversionException(str, "TimeOnly", exception);
}
catch (FormatException exception)
{
throw XmlExceptionHelper.CreateConversionException(str, "TimeOnly", exception);
}
catch (OverflowException exception)
{
throw XmlExceptionHelper.CreateConversionException(str, "TimeOnly", exception);
}
}

internal Guid ReadContentAsGuid()
{
string str = reader.ReadContentAsString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,10 @@ internal void WriteAnyType(object value, Type valueType)
}
else if (valueType == Globals.TypeOfTimeSpan)
WriteTimeSpan((TimeSpan)value);
else if (valueType == Globals.TypeOfDateOnly)
WriteDateOnly((DateOnly)value);
else if (valueType == Globals.TypeOfTimeOnly)
WriteTimeOnly((TimeOnly)value);
else if (valueType == Globals.TypeOfGuid)
WriteGuid((Guid)value);
else if (valueType == Globals.TypeOfUri)
Expand Down Expand Up @@ -420,6 +424,10 @@ internal void WriteExtensionData(IDataNode dataNode)
}
else if (valueType == Globals.TypeOfTimeSpan)
WriteTimeSpan(((DataNode<TimeSpan>)dataNode).GetValue());
else if (valueType == Globals.TypeOfDateOnly)
WriteDateOnly(((DataNode<DateOnly>)dataNode).GetValue());
else if (valueType == Globals.TypeOfTimeOnly)
WriteTimeOnly(((DataNode<TimeOnly>)dataNode).GetValue());
else if (valueType == Globals.TypeOfGuid)
WriteGuid(((DataNode<Guid>)dataNode).GetValue());
else if (valueType == Globals.TypeOfUri)
Expand Down Expand Up @@ -639,6 +647,30 @@ internal void WriteGuid(Guid value, XmlDictionaryString name, XmlDictionaryStrin
WriteEndElementPrimitive();
}

internal void WriteDateOnly(DateOnly value)
{
writer.WriteRaw(XmlConvert.ToString(value));
}

internal void WriteDateOnly(DateOnly value, XmlDictionaryString name, XmlDictionaryString? ns)
{
WriteStartElementPrimitive(name, ns);
WriteDateOnly(value);
WriteEndElementPrimitive();
}

internal void WriteTimeOnly(TimeOnly value)
{
writer.WriteRaw(XmlConvert.ToString(value));
}

internal void WriteTimeOnly(TimeOnly value, XmlDictionaryString name, XmlDictionaryString? ns)
{
WriteStartElementPrimitive(name, ns);
WriteTimeOnly(value);
WriteEndElementPrimitive();
}

internal void WriteUri(Uri? value)
{
writer.WriteString(value?.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,56 @@ public static TimeSpan ToTimeSpan(byte[] buffer, int offset, int count)
return ToTimeSpan(ToString(buffer, offset, count));
}

public static DateOnly ToDateOnly(string value)
{
try
{
return XmlConvert.ToDateOnly(value);
}
catch (ArgumentException exception)
{
throw XmlExceptionHelper.CreateConversionException(value, "DateOnly", exception);
}
catch (FormatException exception)
{
throw XmlExceptionHelper.CreateConversionException(value, "DateOnly", exception);
}
catch (OverflowException exception)
{
throw XmlExceptionHelper.CreateConversionException(value, "DateOnly", exception);
}
}

public static DateOnly ToDateOnly(byte[] buffer, int offset, int count)
{
return ToDateOnly(ToString(buffer, offset, count));
}

public static TimeOnly ToTimeOnly(string value)
{
try
{
return XmlConvert.ToTimeOnly(value);
}
catch (ArgumentException exception)
{
throw XmlExceptionHelper.CreateConversionException(value, "TimeOnly", exception);
}
catch (FormatException exception)
{
throw XmlExceptionHelper.CreateConversionException(value, "TimeOnly", exception);
}
catch (OverflowException exception)
{
throw XmlExceptionHelper.CreateConversionException(value, "TimeOnly", exception);
}
}

public static TimeOnly ToTimeOnly(byte[] buffer, int offset, int count)
{
return ToTimeOnly(ToString(buffer, offset, count));
}

public static Guid ToGuid(string value)
{
try
Expand Down
Loading
Loading