diff --git a/Directory.Build.props b/Directory.Build.props
index acf73ef..24eb9cb 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,7 +9,7 @@
Domain Primitives
ALTA Software llc.
Copyright © 2024 ALTA Software llc.
- 6.0.3
+ 6.0.4
diff --git a/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs b/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs
index 75e94cc..a6256d0 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs
+++ b/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs
@@ -992,6 +992,7 @@ public static void GenerateIXmlSerializableMethods(GeneratorData data, SourceCod
{
"string" => "ReadElementContentAsString",
"bool" => "ReadElementContentAsBoolean",
+ "DateOnly" => "ReadElementContentAsDateOnly",
_ => $"ReadElementContentAs<{data.PrimitiveTypeFriendlyName}>"
};
diff --git a/src/AltaSoft.DomainPrimitives/XmlReaderExt.cs b/src/AltaSoft.DomainPrimitives/XmlReaderExt.cs
index ccf0981..a396f95 100644
--- a/src/AltaSoft.DomainPrimitives/XmlReaderExt.cs
+++ b/src/AltaSoft.DomainPrimitives/XmlReaderExt.cs
@@ -13,13 +13,33 @@ namespace AltaSoft.DomainPrimitives;
public static class XmlReaderExt
{
///
- /// Reads the content of the current element as a object.
+ /// Reads the content of the current XML element as a value.
///
- /// The XmlReader instance.
- /// A byte object representing the value read from the element.
+ /// The type of value to parse, which must implement .
+ /// The instance.
+ ///
+ /// A value parsed from the current element's content.
+ ///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ReadElementContentAs(this XmlReader reader) where T : IParsable
{
return T.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
}
+
+ ///
+ /// Reads the content of the current XML element as a value.
+ ///
+ /// The instance.
+ ///
+ /// A value parsed from the current element's content.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static DateOnly ReadElementContentAsDateOnly(this XmlReader reader)
+ {
+ var str = reader.ReadElementContentAsString();
+ if (DateOnly.TryParse(str, CultureInfo.InvariantCulture, out var result))
+ return result;
+
+ return DateOnly.FromDateTime(DateTime.Parse(str, CultureInfo.InvariantCulture));
+ }
}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs
index 87d634b..6e0e4dd 100644
--- a/tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs
@@ -829,6 +829,44 @@ public static PrimitiveValidationResult Validate(uint value)
GenerateTypeConverters = false
});
}
+
+ [Fact]
+ public Task DateOnlyValue_GeneratesAllInterfacesAndConvertersWithXml()
+ {
+ const string source = """
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading.Tasks;
+ using AltaSoft.DomainPrimitives;
+
+ namespace AltaSoft.DomainPrimitives;
+
+ public readonly partial struct DateOnlyXmlValue : IDomainValue
+ {
+ ///
+ public static PrimitiveValidationResult Validate(DateOnly value)
+ {
+ if (value == default)
+ return "Invalid Value";
+
+ return PrimitiveValidationResult.Ok;
+ }
+ }
+
+ """;
+
+ return TestHelper.Verify(source, (_, x, _) => Assert.Equal(1, x.Count), new DomainPrimitiveGlobalOptions()
+ {
+ GenerateEntityFrameworkCoreValueConverters = false,
+ GenerateJsonConverters = false,
+ GenerateSwaggerConverters = false,
+ GenerateTypeConverters = false,
+ GenerateXmlSerialization = true
+ });
+ }
+
public static class TestHelper
{
internal static Task Verify(string source, Action, List, GeneratorDriver>? additionalChecks = null, DomainPrimitiveGlobalOptions? options = null)
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/ModuleInitializer.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/ModuleInitializer.cs
index 54d40a5..185b002 100644
--- a/tests/AltaSoft.DomainPrimitives.Generator.Tests/ModuleInitializer.cs
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/ModuleInitializer.cs
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
+using DiffEngine;
namespace AltaSoft.DomainPrimitives.Generator.Tests;
@@ -8,6 +9,6 @@ public static class ModuleInitializer
public static void Init()
{
VerifySourceGenerators.Initialize();
-
+ DiffTools.UseOrder(DiffTool.VisualStudioCode);
}
}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.DateOnlyValue_GeneratesAllInterfacesAndConvertersWithXml#DateOnlyXmlValue.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.DateOnlyValue_GeneratesAllInterfacesAndConvertersWithXml#DateOnlyXmlValue.g.verified.cs
new file mode 100644
index 0000000..f1697c7
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.DateOnlyValue_GeneratesAllInterfacesAndConvertersWithXml#DateOnlyXmlValue.g.verified.cs
@@ -0,0 +1,331 @@
+//HintName: DateOnlyXmlValue.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using System;
+using System.Numerics;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using AltaSoft.DomainPrimitives;
+using System.Diagnostics.CodeAnalysis;
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.Serialization;
+
+namespace AltaSoft.DomainPrimitives;
+
+[UnderlyingPrimitiveType(typeof(DateOnly))]
+[DebuggerDisplay("{_value}")]
+public readonly partial struct DateOnlyXmlValue : IEquatable
+ , IComparable
+ , IComparable
+ , IComparisonOperators
+ , ISpanFormattable
+ , IParsable
+ , IConvertible
+ , IXmlSerializable
+#if NET8_0_OR_GREATER
+ , IUtf8SpanFormattable
+#endif
+{
+ ///
+ public Type GetUnderlyingPrimitiveType() => typeof(DateOnly);
+ ///
+ public object GetUnderlyingPrimitiveValue() => (DateOnly)this;
+
+ private DateOnly _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException("The domain value has not been initialized", this);
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly DateOnly _value;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly bool _isInitialized;
+
+ ///
+ /// Initializes a new instance of the class by validating the specified value using static method.
+ ///
+ /// The value to be validated.
+ public DateOnlyXmlValue(DateOnly value) : this(value, true)
+ {
+ }
+
+ private DateOnlyXmlValue(DateOnly value, bool validate)
+ {
+ if (validate)
+ {
+ ValidateOrThrow(value);
+ }
+ _value = value;
+ _isInitialized = true;
+ }
+
+ ///
+ [Obsolete("Domain primitive cannot be created using empty Constructor", true)]
+ public DateOnlyXmlValue()
+ {
+ }
+
+ ///
+ /// Tries to create an instance of AsciiString from the specified value.
+ ///
+ /// The value to create DateOnlyXmlValue from
+ /// When this method returns, contains the created DateOnlyXmlValue if the conversion succeeded, or null if the conversion failed.
+ /// true if the conversion succeeded; otherwise, false.
+ public static bool TryCreate(DateOnly value, [NotNullWhen(true)] out DateOnlyXmlValue? result)
+ {
+ return TryCreate(value, out result, out _);
+ }
+
+ ///
+ /// Tries to create an instance of AsciiString from the specified value.
+ ///
+ /// The value to create DateOnlyXmlValue from
+ /// When this method returns, contains the created DateOnlyXmlValue if the conversion succeeded, or null if the conversion failed.
+ /// When this method returns, contains the error message if the conversion failed; otherwise, null.
+ /// true if the conversion succeeded; otherwise, false.
+ public static bool TryCreate(DateOnly value, [NotNullWhen(true)] out DateOnlyXmlValue? result, [NotNullWhen(false)] out string? errorMessage)
+ {
+ var validationResult = Validate(value);
+ if (!validationResult.IsValid)
+ {
+ result = null;
+ errorMessage = validationResult.ErrorMessage;
+ return false;
+ }
+
+ result = new (value, false);
+ errorMessage = null;
+ return true;
+ }
+
+ ///
+ /// Validates the specified value and throws an exception if it is not valid.
+ ///
+ /// The value to validate
+ /// Thrown when the value is not valid.
+ public void ValidateOrThrow(DateOnly value)
+ {
+ var result = Validate(value);
+ if (!result.IsValid)
+ throw new InvalidDomainValueException(result.ErrorMessage, this);
+ }
+
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object? obj) => obj is DateOnlyXmlValue other && Equals(other);
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(DateOnlyXmlValue other)
+ {
+ if (!_isInitialized || !other._isInitialized)
+ return false;
+ return _value.Equals(other._value);
+ }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(DateOnlyXmlValue left, DateOnlyXmlValue right) => left.Equals(right);
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(DateOnlyXmlValue left, DateOnlyXmlValue right) => !(left == right);
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (obj is null)
+ return 1;
+
+ if (obj is DateOnlyXmlValue c)
+ return CompareTo(c);
+
+ throw new ArgumentException("Object is not a DateOnlyXmlValue", nameof(obj));
+ }
+
+ ///
+ public int CompareTo(DateOnlyXmlValue other)
+ {
+ if (!other._isInitialized)
+ return 1;
+ if (!_isInitialized)
+ return -1;
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ /// Implicit conversion from to
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator DateOnlyXmlValue(DateOnly value) => new(value);
+
+ ///
+ /// Implicit conversion from (nullable) to (nullable)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(value))]
+ public static implicit operator DateOnlyXmlValue?(DateOnly? value) => value is null ? null : new(value.Value);
+
+ ///
+ /// Implicit conversion from to
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator DateOnly(DateOnlyXmlValue value) => (DateOnly)value._valueOrThrow;
+
+ ///
+ /// Implicit conversion from (nullable) to (nullable)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(value))]
+ public static implicit operator DateOnly?(DateOnlyXmlValue? value) => value is null ? null : (DateOnly?)value.Value._valueOrThrow;
+
+ ///
+ /// Implicit conversion from to
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator DateTime(DateOnlyXmlValue value) => ((DateOnly)value._valueOrThrow).ToDateTime();
+
+ ///
+ /// Implicit conversion from to
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator DateOnlyXmlValue(DateTime value) => DateOnly.FromDateTime(value);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator <(DateOnlyXmlValue left, DateOnlyXmlValue right) => left._valueOrThrow < right._valueOrThrow;
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator <=(DateOnlyXmlValue left, DateOnlyXmlValue right) => left._valueOrThrow <= right._valueOrThrow;
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator >(DateOnlyXmlValue left, DateOnlyXmlValue right) => left._valueOrThrow > right._valueOrThrow;
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator >=(DateOnlyXmlValue left, DateOnlyXmlValue right) => left._valueOrThrow >= right._valueOrThrow;
+
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static DateOnlyXmlValue Parse(string s, IFormatProvider? provider) => DateOnly.Parse(s, provider);
+
+ ///
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out DateOnlyXmlValue result)
+ {
+ if (!DateOnly.TryParse(s, provider, out var value))
+ {
+ result = default;
+ return false;
+ }
+
+ if (TryCreate(value, out var created))
+ {
+ result = created.Value;
+ return true;
+ }
+
+ result = default;
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string ToString(string? format, IFormatProvider? formatProvider) => _valueOrThrow.ToString(format, formatProvider);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider)
+ {
+ return ((ISpanFormattable)_valueOrThrow).TryFormat(destination, out charsWritten, format, provider);
+ }
+
+
+#if NET8_0_OR_GREATER
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider)
+ {
+ return ((IUtf8SpanFormattable)_valueOrThrow).TryFormat(utf8Destination, out bytesWritten, format, provider);
+ }
+#endif
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() => _valueOrThrow.GetHashCode();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ TypeCode IConvertible.GetTypeCode() => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).GetTypeCode();
+
+ ///
+ bool IConvertible.ToBoolean(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToBoolean(provider);
+
+ ///
+ byte IConvertible.ToByte(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToByte(provider);
+
+ ///
+ char IConvertible.ToChar(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToChar(provider);
+
+ ///
+ DateTime IConvertible.ToDateTime(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToDateTime(provider);
+
+ ///
+ decimal IConvertible.ToDecimal(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToDecimal(provider);
+
+ ///
+ double IConvertible.ToDouble(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToDouble(provider);
+
+ ///
+ short IConvertible.ToInt16(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToInt16(provider);
+
+ ///
+ int IConvertible.ToInt32(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToInt32(provider);
+
+ ///
+ long IConvertible.ToInt64(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToInt64(provider);
+
+ ///
+ sbyte IConvertible.ToSByte(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToSByte(provider);
+
+ ///
+ float IConvertible.ToSingle(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToSingle(provider);
+
+ ///
+ string IConvertible.ToString(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToString(provider);
+
+ ///
+ object IConvertible.ToType(Type conversionType, IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToType(conversionType, provider);
+
+ ///
+ ushort IConvertible.ToUInt16(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToUInt16(provider);
+
+ ///
+ uint IConvertible.ToUInt32(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToUInt32(provider);
+
+ ///
+ ulong IConvertible.ToUInt64(IFormatProvider? provider) => ((IConvertible)((DateOnly)_valueOrThrow).ToDateTime()).ToUInt64(provider);
+
+ ///
+ public XmlSchema? GetSchema() => null;
+
+ ///
+ public void ReadXml(XmlReader reader)
+ {
+ var value = reader.ReadElementContentAsDateOnly();
+ ValidateOrThrow(value);
+ System.Runtime.CompilerServices.Unsafe.AsRef(in _value) = value;
+ System.Runtime.CompilerServices.Unsafe.AsRef(in _isInitialized) = true;
+ }
+
+ ///
+ public void WriteXml(XmlWriter writer) => writer.WriteValue(((DateOnly)_valueOrThrow).ToXmlString());
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override string ToString() => _valueOrThrow.ToString();
+}
diff --git a/tests/AltaSoft.DomainPrimitives.UnitTests/DateOnlyConversionTests.cs b/tests/AltaSoft.DomainPrimitives.UnitTests/DateOnlyConversionTests.cs
new file mode 100644
index 0000000..bd01d39
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.UnitTests/DateOnlyConversionTests.cs
@@ -0,0 +1,31 @@
+using System.Xml;
+using Xunit;
+
+namespace AltaSoft.DomainPrimitives.UnitTests;
+
+public class DateOnlyConversionTests
+{
+ [Fact]
+ public void ReadElementContentAsDateOnly_WithDateOnlyString_ReturnsDateOnly()
+ {
+ var xml = "2024-04-01";
+ using var reader = XmlReader.Create(new StringReader(xml));
+ reader.ReadToFollowing("d");
+
+ var result = reader.ReadElementContentAsDateOnly();
+
+ Assert.Equal(new DateOnly(2024, 4, 1), result);
+ }
+
+ [Fact]
+ public void ReadElementContentAsDateOnly_WithTimezoneString_ReturnsDateOnly()
+ {
+ var xml = "2024-04-01T12:34:56+03:00";
+ using var reader = XmlReader.Create(new System.IO.StringReader(xml));
+ reader.ReadToFollowing("d");
+
+ var result = reader.ReadElementContentAsDateOnly();
+
+ Assert.Equal(new DateOnly(2024, 4, 1), result);
+ }
+}