diff --git a/AGENT.md b/AGENT.md index 0e70c7a..c114f47 100644 --- a/AGENT.md +++ b/AGENT.md @@ -11,7 +11,7 @@ This document provides guidance for AI agents and developers working on the **Sc - Custom date/time format attributes (`JsonDateTimeConverterAttribute` for reflection, `JsonDateTimeFormatAttribute` for source generators) - Source generator-compatible format converters (`JsonDateTimeFormatConverter`) - .NET 9+ contract customization resolver (`DateTimeConverterResolver`) -- Support for `DateTime`, `DateTimeOffset`, and nullable variants +- Support for `DateTime`, `DateTimeOffset`, `DateOnly`, `TimeOnly`, and nullable variants - Multi-target framework support (.NET 6, 7, 8, 9, 10) ## Repository Structure @@ -31,7 +31,11 @@ Scarlet.System.Text.Json.DateTimeConverter/ │ │ │ ├── DateTimeConverter.cs │ │ │ ├── DateTimeNullableConverter.cs │ │ │ ├── DateTimeOffsetConverter.cs -│ │ │ └── DateTimeOffsetNullableConverter.cs +│ │ │ ├── DateTimeOffsetNullableConverter.cs +│ │ │ ├── DateOnlyConverter.cs +│ │ │ ├── DateOnlyNullableConverter.cs +│ │ │ ├── TimeOnlyConverter.cs +│ │ │ └── TimeOnlyNullableConverter.cs │ │ ├── DateTimeConverterFactoryHelper.cs │ │ ├── DateTimeConverterResolver.cs # .NET 9+ contract customization │ │ ├── IJsonDateTimeFormat.cs @@ -187,11 +191,15 @@ The project uses a standard GitHub workflow: #### 1. Converters (Internal) -Four internal converter classes handle actual JSON serialization/deserialization: +Eight internal converter classes handle actual JSON serialization/deserialization: - `DateTimeConverter` - for `DateTime` - `DateTimeNullableConverter` - for `DateTime?` - `DateTimeOffsetConverter` - for `DateTimeOffset` - `DateTimeOffsetNullableConverter` - for `DateTimeOffset?` +- `DateOnlyConverter` - for `DateOnly` +- `DateOnlyNullableConverter` - for `DateOnly?` +- `TimeOnlyConverter` - for `TimeOnly` +- `TimeOnlyNullableConverter` - for `TimeOnly?` All use `CultureInfo.InvariantCulture` for consistent formatting. @@ -269,7 +277,7 @@ Tests verify four distinct usage patterns: 4. **Source generator with resolver (old attribute)** - Uses `JsonDateTimeConverterAttribute` + `DateTimeConverterResolver` (.NET 9+, with SYSLIB1223 warnings for backward compatibility) Each pattern is tested with: -- Individual types (`DateTime`, `DateTime?`, `DateTimeOffset`, `DateTimeOffset?`) +- Individual types (`DateTime`, `DateTime?`, `DateTimeOffset`, `DateTimeOffset?`, `DateOnly`, `DateOnly?`, `TimeOnly`, `TimeOnly?`) - Complete models - Null value handling diff --git a/README.md b/README.md index 4630b2c..c0e4cd0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Nuget](https://img.shields.io/nuget/dt/Scarlet.System.Text.Json.DateTimeConverter?color=ff4081&label=nuget%20downloads&logo=nuget)](https://www.nuget.org/packages/Scarlet.System.Text.Json.DateTimeConverter) [![GitHub](https://img.shields.io/github/license/ScarletKuro/Scarlet.System.Text.Json.DateTimeConverter?color=594ae2&logo=github)](https://github.com/ScarletKuro/Scarlet.System.Text.Json.DateTimeConverter/blob/master/LICENSE) -A flexible and powerful library for customizing `DateTime` and `DateTimeOffset` serialization in System.Text.Json, with full support for both reflection-based and source generator approaches. +A flexible and powerful library for customizing `DateTime`, `DateTimeOffset`, `DateOnly`, and `TimeOnly` serialization in System.Text.Json, with full support for both reflection-based and source generator approaches. ## Table of Contents @@ -24,7 +24,7 @@ A flexible and powerful library for customizing `DateTime` and `DateTimeOffset` ## Overview -This package provides four ways to specify custom date formats for `DateTime`, `DateTimeOffset`, and their nullable counterparts when serializing and deserializing JSON using `System.Text.Json`: +This package provides four ways to specify custom date formats for `DateTime`, `DateTimeOffset`, `DateOnly`, `TimeOnly`, and their nullable counterparts when serializing and deserializing JSON using `System.Text.Json`: 1. **`JsonDateTimeConverterAttribute`** - Simple attribute-based approach (reflection only, or .NET 9+ with resolver but produces warnings) 2. **`JsonDateTimeFormatAttribute`** - Clean attribute for source generators with .NET 9+ resolver (no warnings) @@ -102,6 +102,12 @@ public class Order [JsonDateTimeConverter("yyyy-MM-ddTHH:mm:ss.fffZ")] public DateTimeOffset ShippedAt { get; set; } + + [JsonDateTimeConverter("MM/dd/yyyy")] + public DateOnly DeliveryDate { get; set; } + + [JsonDateTimeConverter("HH:mm")] + public TimeOnly DeliveryTime { get; set; } } // Usage @@ -109,12 +115,14 @@ var order = new Order { OrderDate = new DateTime(2026, 1, 15), ProcessedDate = new DateTime(2026, 1, 15, 14, 30, 0), - ShippedAt = DateTimeOffset.UtcNow + ShippedAt = DateTimeOffset.UtcNow, + DeliveryDate = new DateOnly(2026, 1, 20), + DeliveryTime = new TimeOnly(10, 30) }; string json = JsonSerializer.Serialize(order); Console.WriteLine(json); -// Output: {"OrderDate":"2026-01-15","ProcessedDate":"2026-01-15T14:30:00","ShippedAt":"2026-01-15T14:30:00.123Z"} +// Output: {"OrderDate":"2026-01-15","ProcessedDate":"2026-01-15T14:30:00","ShippedAt":"2026-01-15T14:30:00.123Z","DeliveryDate":"01/20/2026","DeliveryTime":"10:30"} var deserializedOrder = JsonSerializer.Deserialize(json); ``` @@ -150,6 +158,12 @@ public class Order [JsonConverter(typeof(JsonDateTimeFormatConverter))] public DateTimeOffset ShippedAt { get; set; } + + [JsonConverter(typeof(JsonDateTimeFormatConverter))] + public DateOnly DeliveryDate { get; set; } + + [JsonConverter(typeof(JsonDateTimeFormatConverter))] + public TimeOnly DeliveryTime { get; set; } } // Define your custom date formats @@ -169,6 +183,16 @@ public static class DateFormats { public static string Format => "yyyy-MM-ddTHH:mm:ss.fffZ"; } + + public class DateOnlySlash : IJsonDateTimeFormat + { + public static string Format => "MM/dd/yyyy"; + } + + public class TimeOnlyShort : IJsonDateTimeFormat + { + public static string Format => "HH:mm"; + } } // Create a JsonSerializerContext for source generation @@ -181,7 +205,9 @@ var order = new Order { OrderDate = new DateTime(2026, 1, 15), ProcessedDate = new DateTime(2026, 1, 15, 14, 30, 0), - ShippedAt = DateTimeOffset.UtcNow + ShippedAt = DateTimeOffset.UtcNow, + DeliveryDate = new DateOnly(2026, 1, 20), + DeliveryTime = new TimeOnly(10, 30) }; string json = JsonSerializer.Serialize(order, typeof(Order), OrderJsonContext.Default); @@ -225,6 +251,12 @@ public class Order [JsonDateTimeFormat("yyyy-MM-ddTHH:mm:ss.fffZ")] public DateTimeOffset ShippedAt { get; set; } + + [JsonDateTimeFormat("MM/dd/yyyy")] + public DateOnly DeliveryDate { get; set; } + + [JsonDateTimeFormat("HH:mm")] + public TimeOnly DeliveryTime { get; set; } } [JsonSerializable(typeof(Order))] @@ -427,6 +459,10 @@ This matches standard `System.Text.Json` behavior. - `DateTime?` - `DateTimeOffset` - `DateTimeOffset?` +- `DateOnly` +- `DateOnly?` +- `TimeOnly` +- `TimeOnly?` All types support any valid [.NET date and time format string](https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings). diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/JsonDateTimeConverterAttributeTests.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/JsonDateTimeConverterAttributeTests.cs index 3e389ca..6c19c39 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/JsonDateTimeConverterAttributeTests.cs +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/JsonDateTimeConverterAttributeTests.cs @@ -81,6 +81,82 @@ public void ReflectionBased_NullableDateTimeOffset_WithAttribute() Assert.Equal("\"2023-10-01T12:00:00.000Z\"", json); } + [Fact] + public void ReflectionBased_DateOnly_WithAttribute() + { + // Arrange + var options = new JsonSerializerOptions + { + Converters = { new JsonDateTimeConverterAttribute("MM/dd/yyyy").CreateConverter(typeof(DateOnly)) } + }; + var originalDate = new DateOnly(2023, 10, 1); + + // Act + var json = JsonSerializer.Serialize(originalDate, options); + var deserializedDate = JsonSerializer.Deserialize(json, options); + + // Assert + Assert.Equal(originalDate, deserializedDate); + Assert.Equal("\"10/01/2023\"", json); + } + + [Fact] + public void ReflectionBased_NullableDateOnly_WithAttribute() + { + // Arrange + var options = new JsonSerializerOptions + { + Converters = { new JsonDateTimeConverterAttribute("MM/dd/yyyy").CreateConverter(typeof(DateOnly?)) } + }; + DateOnly? originalDate = new DateOnly(2023, 10, 1); + + // Act + var json = JsonSerializer.Serialize(originalDate, options); + var deserializedDate = JsonSerializer.Deserialize(json, options); + + // Assert + Assert.Equal(originalDate, deserializedDate); + Assert.Equal("\"10/01/2023\"", json); + } + + [Fact] + public void ReflectionBased_TimeOnly_WithAttribute() + { + // Arrange + var options = new JsonSerializerOptions + { + Converters = { new JsonDateTimeConverterAttribute("HH.mm.ss").CreateConverter(typeof(TimeOnly)) } + }; + var originalTime = new TimeOnly(14, 30, 45); + + // Act + var json = JsonSerializer.Serialize(originalTime, options); + var deserializedTime = JsonSerializer.Deserialize(json, options); + + // Assert + Assert.Equal(originalTime, deserializedTime); + Assert.Equal("\"14.30.45\"", json); + } + + [Fact] + public void ReflectionBased_NullableTimeOnly_WithAttribute() + { + // Arrange + var options = new JsonSerializerOptions + { + Converters = { new JsonDateTimeConverterAttribute("HH.mm.ss").CreateConverter(typeof(TimeOnly?)) } + }; + TimeOnly? originalTime = new TimeOnly(14, 30, 45); + + // Act + var json = JsonSerializer.Serialize(originalTime, options); + var deserializedTime = JsonSerializer.Deserialize(json, options); + + // Assert + Assert.Equal(originalTime, deserializedTime); + Assert.Equal("\"14.30.45\"", json); + } + [Fact] public void ReflectionBased_CompleteModel_WithAttribute() { @@ -94,14 +170,22 @@ public void ReflectionBased_CompleteModel_WithAttribute() DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero) + NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = new DateOnly(2023, 10, 1), + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = new TimeOnly(14, 30, 45) }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": "2023-10-01T12:00:00", "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z" + "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": "10/01/2023", + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": "14.30.45" } """; @@ -115,6 +199,10 @@ public void ReflectionBased_CompleteModel_WithAttribute() Assert.Equal(originalModel.NullableDateTimeProperty, deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Equal(originalModel.NullableDateTimeOffsetProperty, deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Equal(originalModel.NullableDateOnlyProperty, deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Equal(originalModel.NullableTimeOnlyProperty, deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } @@ -131,14 +219,22 @@ public void ReflectionBased_CompleteModel_WithAttribute_WithNullValues() DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = null, DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = null + NullableDateTimeOffsetProperty = null, + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = null, + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = null }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": null, "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": null + "NullableDateTimeOffsetProperty": null, + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": null, + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": null } """; @@ -152,6 +248,10 @@ public void ReflectionBased_CompleteModel_WithAttribute_WithNullValues() Assert.Null(deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Null(deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Null(deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Null(deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } @@ -169,14 +269,22 @@ public void SourceGenerator_WithResolver_WithFormatAttribute_UsingOptions() DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero) + NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = new DateOnly(2023, 10, 1), + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = new TimeOnly(14, 30, 45) }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": "2023-10-01T12:00:00", "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z" + "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": "10/01/2023", + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": "14.30.45" } """; @@ -190,6 +298,10 @@ public void SourceGenerator_WithResolver_WithFormatAttribute_UsingOptions() Assert.Equal(originalModel.NullableDateTimeProperty, deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Equal(originalModel.NullableDateTimeOffsetProperty, deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Equal(originalModel.NullableDateOnlyProperty, deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Equal(originalModel.NullableTimeOnlyProperty, deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } @@ -207,14 +319,22 @@ public void SourceGenerator_WithResolver_WithFormatAttribute_WithNullValues_Usin DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = null, DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = null + NullableDateTimeOffsetProperty = null, + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = null, + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = null }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": null, "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": null + "NullableDateTimeOffsetProperty": null, + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": null, + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": null } """; @@ -228,6 +348,10 @@ public void SourceGenerator_WithResolver_WithFormatAttribute_WithNullValues_Usin Assert.Null(deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Null(deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Null(deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Null(deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } @@ -242,14 +366,22 @@ public void SourceGenerator_WithResolver_WithFormatAttribute_UsingContext() DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero) + NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = new DateOnly(2023, 10, 1), + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = new TimeOnly(14, 30, 45) }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": "2023-10-01T12:00:00", "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z" + "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": "10/01/2023", + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": "14.30.45" } """; @@ -263,6 +395,10 @@ public void SourceGenerator_WithResolver_WithFormatAttribute_UsingContext() Assert.Equal(originalModel.NullableDateTimeProperty, deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Equal(originalModel.NullableDateTimeOffsetProperty, deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Equal(originalModel.NullableDateOnlyProperty, deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Equal(originalModel.NullableTimeOnlyProperty, deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } @@ -277,14 +413,22 @@ public void SourceGenerator_WithResolver_WithFormatAttribute_WithNullValues_Usin DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = null, DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = null + NullableDateTimeOffsetProperty = null, + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = null, + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = null }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": null, "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": null + "NullableDateTimeOffsetProperty": null, + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": null, + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": null } """; @@ -298,6 +442,10 @@ public void SourceGenerator_WithResolver_WithFormatAttribute_WithNullValues_Usin Assert.Equal(originalModel.NullableDateTimeProperty, deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Equal(originalModel.NullableDateTimeOffsetProperty, deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Null(deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Null(deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } } \ No newline at end of file diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/JsonDateTimeFormatConverterTests.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/JsonDateTimeFormatConverterTests.cs index f785071..d1e9d7b 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/JsonDateTimeFormatConverterTests.cs +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/JsonDateTimeFormatConverterTests.cs @@ -18,14 +18,22 @@ public void ReflectionBased_CompleteModel_WithFormatConverter() DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero) + NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = new DateOnly(2023, 10, 1), + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = new TimeOnly(14, 30, 45) }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": "2023-10-01T12:00:00", "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z" + "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": "10/01/2023", + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": "14.30.45" } """; @@ -39,6 +47,10 @@ public void ReflectionBased_CompleteModel_WithFormatConverter() Assert.Equal(originalModel.NullableDateTimeProperty, deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Equal(originalModel.NullableDateTimeOffsetProperty, deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Equal(originalModel.NullableDateOnlyProperty, deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Equal(originalModel.NullableTimeOnlyProperty, deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } @@ -55,14 +67,22 @@ public void ReflectionBased_CompleteModel_WithFormatConverter_WithNullValues() DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = null, DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = null + NullableDateTimeOffsetProperty = null, + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = null, + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = null }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": null, "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": null + "NullableDateTimeOffsetProperty": null, + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": null, + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": null } """; @@ -76,6 +96,10 @@ public void ReflectionBased_CompleteModel_WithFormatConverter_WithNullValues() Assert.Null(deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Null(deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Null(deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Null(deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } @@ -90,14 +114,22 @@ public void SourceGenerator_CompleteModel_WithFormatConverter() DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero) + NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = new DateOnly(2023, 10, 1), + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = new TimeOnly(14, 30, 45) }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": "2023-10-01T12:00:00", "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z" + "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": "10/01/2023", + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": "14.30.45" } """; @@ -111,6 +143,10 @@ public void SourceGenerator_CompleteModel_WithFormatConverter() Assert.Equal(originalModel.NullableDateTimeProperty, deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Equal(originalModel.NullableDateTimeOffsetProperty, deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Equal(originalModel.NullableDateOnlyProperty, deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Equal(originalModel.NullableTimeOnlyProperty, deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } @@ -125,14 +161,22 @@ public void SourceGenerator_CompleteModel_WithFormatConverter_WithNullValues() DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), NullableDateTimeProperty = null, DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), - NullableDateTimeOffsetProperty = null + NullableDateTimeOffsetProperty = null, + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = null, + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = null }; const string expectedJson = """ { "DateTimeProperty": "2023-10-01T12:00:00", "NullableDateTimeProperty": null, "DateTimeOffsetProperty": "2023-10-01T12:00:00.000Z", - "NullableDateTimeOffsetProperty": null + "NullableDateTimeOffsetProperty": null, + "DateOnlyProperty": "10/01/2023", + "NullableDateOnlyProperty": null, + "TimeOnlyProperty": "14.30.45", + "NullableTimeOnlyProperty": null } """; @@ -146,6 +190,10 @@ public void SourceGenerator_CompleteModel_WithFormatConverter_WithNullValues() Assert.Null(deserializedModel.NullableDateTimeProperty); Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); Assert.Null(deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Null(deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Null(deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } } \ No newline at end of file diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/ReflectionBasedModel.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/ReflectionBasedModel.cs index c11a88d..37cc9c9 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/ReflectionBasedModel.cs +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/ReflectionBasedModel.cs @@ -18,4 +18,16 @@ public class ReflectionBasedModel [JsonDateTimeConverter("yyyy-MM-ddTHH:mm:ss.fffZ")] public DateTimeOffset? NullableDateTimeOffsetProperty { get; set; } + + [JsonDateTimeConverter("MM/dd/yyyy")] + public DateOnly DateOnlyProperty { get; set; } + + [JsonDateTimeConverter("MM/dd/yyyy")] + public DateOnly? NullableDateOnlyProperty { get; set; } + + [JsonDateTimeConverter("HH.mm.ss")] + public TimeOnly TimeOnlyProperty { get; set; } + + [JsonDateTimeConverter("HH.mm.ss")] + public TimeOnly? NullableTimeOnlyProperty { get; set; } } \ No newline at end of file diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/SourceGeneratorWithConverterModel.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/SourceGeneratorWithConverterModel.cs index c5164d7..77aa0f9 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/SourceGeneratorWithConverterModel.cs +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/SourceGeneratorWithConverterModel.cs @@ -20,6 +20,18 @@ public class SourceGeneratorWithConverterModel [JsonConverter(typeof(JsonDateTimeFormatConverter))] public DateTimeOffset? NullableDateTimeOffsetProperty { get; set; } + + [JsonConverter(typeof(JsonDateTimeFormatConverter))] + public DateOnly DateOnlyProperty { get; set; } + + [JsonConverter(typeof(JsonDateTimeFormatConverter))] + public DateOnly? NullableDateOnlyProperty { get; set; } + + [JsonConverter(typeof(JsonDateTimeFormatConverter))] + public TimeOnly TimeOnlyProperty { get; set; } + + [JsonConverter(typeof(JsonDateTimeFormatConverter))] + public TimeOnly? NullableTimeOnlyProperty { get; set; } } internal class JsonDateTimeFormat @@ -32,4 +44,12 @@ internal class DateTimeFormat : IJsonDateTimeFormat { public static string Format => "yyyy-MM-ddTHH:mm:ss"; } + internal class DateOnlyFormat : IJsonDateTimeFormat + { + public static string Format => "MM/dd/yyyy"; + } + internal class TimeOnlyFormat : IJsonDateTimeFormat + { + public static string Format => "HH.mm.ss"; + } } \ No newline at end of file diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/SourceGeneratorWithResolverFormatModel.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/SourceGeneratorWithResolverFormatModel.cs index 7879782..671b228 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/SourceGeneratorWithResolverFormatModel.cs +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Model/SourceGeneratorWithResolverFormatModel.cs @@ -18,4 +18,16 @@ public class SourceGeneratorWithResolverFormatModel [JsonDateTimeFormat("yyyy-MM-ddTHH:mm:ss.fffZ")] public DateTimeOffset? NullableDateTimeOffsetProperty { get; set; } + + [JsonDateTimeFormat("MM/dd/yyyy")] + public DateOnly DateOnlyProperty { get; set; } + + [JsonDateTimeFormat("MM/dd/yyyy")] + public DateOnly? NullableDateOnlyProperty { get; set; } + + [JsonDateTimeFormat("HH.mm.ss")] + public TimeOnly TimeOnlyProperty { get; set; } + + [JsonDateTimeFormat("HH.mm.ss")] + public TimeOnly? NullableTimeOnlyProperty { get; set; } } diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/DateOnlyConverter.cs b/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/DateOnlyConverter.cs new file mode 100644 index 0000000..f6bf076 --- /dev/null +++ b/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/DateOnlyConverter.cs @@ -0,0 +1,64 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Scarlet.System.Text.Json.DateTimeConverter.Converters; + +/// +/// Converts objects to and from JSON using a specified date format. +/// +internal sealed class DateOnlyConverter : JsonConverter +{ + private readonly string _format; + + /// + /// Initializes a new instance of the class with the specified date format. + /// + /// The date format string. + private DateOnlyConverter(string format) => _format = format; + + /// + /// Reads and converts the JSON to a object. + /// + /// The to read from. + /// The type to convert. + /// Options to control the conversion behavior. + /// The converted object. + public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + if (DateOnly.TryParseExact(reader.GetString(), _format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly date)) + { + return date; + } + } + + // Fallback to reading as DateTime and converting to DateOnly + try + { + var dateTime = reader.GetDateTime(); + return DateOnly.FromDateTime(dateTime); + } + catch (Exception ex) when (ex is FormatException || ex is InvalidOperationException) + { + throw new JsonException($"Unable to convert token to DateOnly using format '{_format}'.", ex); + } + } + + /// + /// Writes a object as a JSON string using the specified date format. + /// + /// The to write to. + /// The value to write. + /// Options to control the conversion behavior. + public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + + var date = value.ToString(_format, CultureInfo.InvariantCulture); + writer.WriteStringValue(date); + } + + public static DateOnlyConverter FromFormat(string format) => new(format); +} diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/DateOnlyNullableConverter.cs b/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/DateOnlyNullableConverter.cs new file mode 100644 index 0000000..5294186 --- /dev/null +++ b/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/DateOnlyNullableConverter.cs @@ -0,0 +1,67 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Scarlet.System.Text.Json.DateTimeConverter.Converters; + +/// +/// Converts nullable objects to and from JSON using a specified date format. +/// +internal sealed class DateOnlyNullableConverter : JsonConverter +{ + private readonly string _format; + + /// + /// Initializes a new instance of the class with the specified date format. + /// + /// The date format string. + private DateOnlyNullableConverter(string format) => _format = format; + + /// + /// Reads and converts the JSON to a nullable object. + /// + /// The to read from. + /// The type to convert. + /// Options to control the conversion behavior. + /// The converted nullable object, or null if the JSON token is null. + public override DateOnly? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType == JsonTokenType.String) + { + if (DateOnly.TryParseExact(reader.GetString(), _format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly date)) + { + return date; + } + } + + return null; + } + + /// + /// Writes a nullable object as a JSON string using the specified date format. + /// + /// The to write to. + /// The nullable value to write. + /// Options to control the conversion behavior. + public override void Write(Utf8JsonWriter writer, DateOnly? value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + + if (value.HasValue) + { + var date = value.Value.ToString(_format, CultureInfo.InvariantCulture); + writer.WriteStringValue(date); + } + else + { + writer.WriteNullValue(); + } + } + + public static DateOnlyNullableConverter FromFormat(string format) => new(format); +} diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/TimeOnlyConverter.cs b/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/TimeOnlyConverter.cs new file mode 100644 index 0000000..a58aa0b --- /dev/null +++ b/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/TimeOnlyConverter.cs @@ -0,0 +1,64 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Scarlet.System.Text.Json.DateTimeConverter.Converters; + +/// +/// Converts objects to and from JSON using a specified time format. +/// +internal sealed class TimeOnlyConverter : JsonConverter +{ + private readonly string _format; + + /// + /// Initializes a new instance of the class with the specified time format. + /// + /// The time format string. + private TimeOnlyConverter(string format) => _format = format; + + /// + /// Reads and converts the JSON to a object. + /// + /// The to read from. + /// The type to convert. + /// Options to control the conversion behavior. + /// The converted object. + public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + if (TimeOnly.TryParseExact(reader.GetString(), _format, CultureInfo.InvariantCulture, DateTimeStyles.None, out TimeOnly time)) + { + return time; + } + } + + // Fallback to reading as DateTime and converting to TimeOnly + try + { + var dateTime = reader.GetDateTime(); + return TimeOnly.FromDateTime(dateTime); + } + catch (Exception ex) when (ex is FormatException || ex is InvalidOperationException) + { + throw new JsonException($"Unable to convert token to TimeOnly using format '{_format}'.", ex); + } + } + + /// + /// Writes a object as a JSON string using the specified time format. + /// + /// The to write to. + /// The value to write. + /// Options to control the conversion behavior. + public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + + var time = value.ToString(_format, CultureInfo.InvariantCulture); + writer.WriteStringValue(time); + } + + public static TimeOnlyConverter FromFormat(string format) => new(format); +} diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/TimeOnlyNullableConverter.cs b/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/TimeOnlyNullableConverter.cs new file mode 100644 index 0000000..8d97d60 --- /dev/null +++ b/src/Scarlet.System.Text.Json.DateTimeConverter/Converters/TimeOnlyNullableConverter.cs @@ -0,0 +1,67 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Scarlet.System.Text.Json.DateTimeConverter.Converters; + +/// +/// Converts nullable objects to and from JSON using a specified time format. +/// +internal sealed class TimeOnlyNullableConverter : JsonConverter +{ + private readonly string _format; + + /// + /// Initializes a new instance of the class with the specified time format. + /// + /// The time format string. + private TimeOnlyNullableConverter(string format) => _format = format; + + /// + /// Reads and converts the JSON to a nullable object. + /// + /// The to read from. + /// The type to convert. + /// Options to control the conversion behavior. + /// The converted nullable object, or null if the JSON token is null. + public override TimeOnly? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType == JsonTokenType.String) + { + if (TimeOnly.TryParseExact(reader.GetString(), _format, CultureInfo.InvariantCulture, DateTimeStyles.None, out TimeOnly time)) + { + return time; + } + } + + return null; + } + + /// + /// Writes a nullable object as a JSON string using the specified time format. + /// + /// The to write to. + /// The nullable value to write. + /// Options to control the conversion behavior. + public override void Write(Utf8JsonWriter writer, TimeOnly? value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + + if (value.HasValue) + { + var time = value.Value.ToString(_format, CultureInfo.InvariantCulture); + writer.WriteStringValue(time); + } + else + { + writer.WriteNullValue(); + } + } + + public static TimeOnlyNullableConverter FromFormat(string format) => new(format); +} diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter/DateTimeConverterFactoryHelper.cs b/src/Scarlet.System.Text.Json.DateTimeConverter/DateTimeConverterFactoryHelper.cs index 73fad8e..30c4da5 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter/DateTimeConverterFactoryHelper.cs +++ b/src/Scarlet.System.Text.Json.DateTimeConverter/DateTimeConverterFactoryHelper.cs @@ -28,6 +28,26 @@ public static JsonConverter CreateConverter(Type typeToConvert, string format) return Converters.DateTimeOffsetNullableConverter.FromFormat(format); } + if (typeToConvert == typeof(DateOnly)) + { + return Converters.DateOnlyConverter.FromFormat(format); + } + + if (typeToConvert == typeof(DateOnly?)) + { + return Converters.DateOnlyNullableConverter.FromFormat(format); + } + + if (typeToConvert == typeof(TimeOnly)) + { + return Converters.TimeOnlyConverter.FromFormat(format); + } + + if (typeToConvert == typeof(TimeOnly?)) + { + return Converters.TimeOnlyNullableConverter.FromFormat(format); + } + throw new NotSupportedException($"{typeToConvert.FullName} is not supported by the {nameof(DateTimeConverterFactoryHelper)}."); } } \ No newline at end of file diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter/JsonDateTimeFormatConverter.cs b/src/Scarlet.System.Text.Json.DateTimeConverter/JsonDateTimeFormatConverter.cs index 06ef9a0..bd5d3b8 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter/JsonDateTimeFormatConverter.cs +++ b/src/Scarlet.System.Text.Json.DateTimeConverter/JsonDateTimeFormatConverter.cs @@ -4,7 +4,7 @@ namespace Scarlet.System.Text.Json.DateTimeConverter; /// -/// A JSON converter factory that creates converters for and types using a specified date format. +/// A JSON converter factory that creates converters for , , , and types using a specified date format. /// /// The type that implements to provide the date format. /// @@ -39,7 +39,11 @@ public override bool CanConvert(Type typeToConvert) return typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?) || typeToConvert == typeof(DateTimeOffset) || - typeToConvert == typeof(DateTimeOffset?); + typeToConvert == typeof(DateTimeOffset?) || + typeToConvert == typeof(DateOnly) || + typeToConvert == typeof(DateOnly?) || + typeToConvert == typeof(TimeOnly) || + typeToConvert == typeof(TimeOnly?); } ///