Skip to content
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

Add DateOnly and TimeOnly literal converters #282

Merged
merged 1 commit into from
Feb 1, 2025
Merged
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 @@ -119,6 +119,20 @@ public void Serialize_Date_ReturnsString(string format)
result.Should().Be("2020-01-02");
}

[Theory]
[InlineData("date")]
[InlineData("full-date")]
public void Serialize_DateOnly_ReturnsString(string format)
{
// Act

string result = LiteralSerializer.Serialize(new DateOnly(2020, 1, 2), format);

// Assert

result.Should().Be("2020-01-02");
}

[Fact]
public void Serialize_DateTimeOffset_ReturnsString()
{
Expand Down Expand Up @@ -162,6 +176,34 @@ public void Serialize_TimeSpanMillis_ReturnsString(string format)
result.Should().Be("03:04:05.1230000");
}

[Theory]
[InlineData("partial-time")]
public void Serialize_TimeOnly_ReturnsString(string format)
{
// Act

string result = LiteralSerializer.Serialize(
new TimeOnly(3, 4, 5), format);

// Assert

result.Should().Be("03:04:05");
}

[Theory]
[InlineData("partial-time")]
public void Serialize_TimeOnlyMillis_ReturnsString(string format)
{
// Act

string result = LiteralSerializer.Serialize(
new TimeOnly(3, 4, 5, 123), format);

// Assert

result.Should().Be("03:04:05.1230000");
}

[Fact]
public void Serialize_Guid_ReturnsString()
{
Expand Down Expand Up @@ -351,6 +393,26 @@ public void TrySerialize_Date_ReturnsString(string format)
buffer[..charsWritten].ToString().Should().Be("2020-01-02");
}

[Theory]
[InlineData("date")]
[InlineData("full-date")]
public void TrySerialize_DateOnly_ReturnsString(string format)
{
// Arrange

Span<char> buffer = stackalloc char[256];

// Act

bool result = LiteralSerializer.TrySerialize(new DateOnly(2020, 1, 2),
format, buffer, out var charsWritten);

// Assert

result.Should().BeTrue();
buffer[..charsWritten].ToString().Should().Be("2020-01-02");
}

[Fact]
public void TrySerialize_DateTimeOffset_ReturnsString()
{
Expand Down Expand Up @@ -410,6 +472,44 @@ public void TrySerialize_TimeSpanMillis_ReturnsString(string format)
buffer[..charsWritten].ToString().Should().Be("03:04:05.1230000");
}

[Theory]
[InlineData("partial-time")]
public void TrySerialize_TimeOnly_ReturnsString(string format)
{
// Arrange

Span<char> buffer = stackalloc char[256];

// Act

bool result = LiteralSerializer.TrySerialize(
new TimeOnly(3, 4, 5), format, buffer, out var charsWritten);

// Assert

result.Should().BeTrue();
buffer[..charsWritten].ToString().Should().Be("03:04:05");
}

[Theory]
[InlineData("partial-time")]
public void TrySerialize_TimeOnlyMillis_ReturnsString(string format)
{
// Arrange

Span<char> buffer = stackalloc char[256];

// Act

bool result = LiteralSerializer.TrySerialize(
new TimeOnly(3, 4, 5, 123), format, buffer, out var charsWritten);

// Assert

result.Should().BeTrue();
buffer[..charsWritten].ToString().Should().Be("03:04:05.1230000");
}

[Fact]
public void TrySerialize_Guid_ReturnsString()
{
Expand Down Expand Up @@ -808,6 +908,20 @@ public void Deserialize_Date_ReturnsString(string format)
result.Should().Be(new DateTime(2020, 01, 02));
}

[Theory]
[InlineData("date")]
[InlineData("full-date")]
public void Deserialize_DateOnly_ReturnsString(string format)
{
// Act

var result = LiteralSerializer.Deserialize<DateOnly>("2020-01-02", format);

// Assert

result.Should().Be(new DateOnly(year: 2020, 01, 02));
}

[Theory]
[InlineData("partial-time")]
[InlineData("date-span")]
Expand Down Expand Up @@ -836,6 +950,54 @@ public void Deserialize_TimeSpanWithMillis_ReturnsString(string format)
result.Should().Be(new TimeSpan(0, 13, 1, 2, 234));
}

[Theory]
[InlineData("partial-time")]
public void Deserialize_TimeOnly_ReturnsString(string format)
{
// Act

var result = LiteralSerializer.Deserialize<TimeOnly>("13:01:02", format);

// Assert

result.Should().Be(new TimeOnly(13, 1, 2));
}

[Theory]
[InlineData("partial-time")]
public void Deserialize_TimeOnlyWithMillis_ReturnsString(string format)
{
// Act

var result = LiteralSerializer.Deserialize<TimeOnly>("13:01:02.234000", format);

// Assert

result.Should().Be(new TimeOnly(13, 1, 2, 234));
}

[Theory]
[InlineData("partial-time")]
public void Deserialize_TimeOnlyWithDays_ThrowsFormatException(string format)
{
// Act/Assert

Action act = () => LiteralSerializer.Deserialize<TimeOnly>("2.13:01:02.234000", format);

act.Should().Throw<FormatException>();
}

[Theory]
[InlineData("partial-time")]
public void Deserialize_TimeOnlyWithNegative_ThrowsFormatException(string format)
{
// Act/Assert

Action act = () => LiteralSerializer.Deserialize<TimeOnly>("-13:01:02.234000", format);

act.Should().Throw<FormatException>();
}

[Fact]
public void Deserialize_DateWithUnexpectedTime_FormatException()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace RootNamespace.Serialization.Literals.Converters;

internal sealed class DateOnlyLiteralConverter : ValueTypeLiteralConverter<DateOnly>
{
protected override DateOnly ReadCore(string value, string? format) =>
DateOnly.ParseExact(value, "o");

public override string Write(DateOnly value, string? format) =>
value.ToString("o");

public override bool TryWrite(DateOnly value, ReadOnlySpan<char> format, Span<char> destination, out int charsWritten) =>
value.TryFormat(destination, out charsWritten, "o");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using Yardarm.Client.Internal;

namespace RootNamespace.Serialization.Literals.Converters;

internal sealed class TimeOnlyLiteralConverter : ValueTypeLiteralConverter<TimeOnly>
{
protected override TimeOnly ReadCore(string value, string? format)
{
ThrowHelper.ThrowIfNull(value);

char firstChar = value[0];
int firstSeparator = value.AsSpan().IndexOfAny('.', ':');
if (!char.IsDigit(firstChar) || firstSeparator < 0 || value[firstSeparator] == '.')
{
// Note: TimeSpan.ParseExact permits leading whitespace, negative values
// and numbers of days so we need to exclude these cases here.
ThrowHelper.ThrowFormatException("The value is not in a supported TimeOnly format.");
}

return TimeOnly.FromTimeSpan(TimeSpan.ParseExact(value, "c", null));
}

public override string Write(TimeOnly value, string? format) =>
value.ToTimeSpan().ToString("c");

public override bool TryWrite(TimeOnly value, ReadOnlySpan<char> format, Span<char> destination, out int charsWritten) =>
value.ToTimeSpan().TryFormat(destination, out charsWritten, "c");
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,10 @@ public static LiteralConverterRegistry CreateDefaultRegistry() =>
.Add(new DoubleLiteralConverter())
.Add(new DecimalLiteralConverter())
.Add(new StringLiteralConverter())
.Add(new UriLiteralConverter());
.Add(new UriLiteralConverter())
#if NET6_0_OR_GREATER
.Add(new DateOnlyLiteralConverter())
.Add(new TimeOnlyLiteralConverter())
#endif
;
}