Skip to content
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
16 changes: 12 additions & 4 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`)
- .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
Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand Down
46 changes: 41 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -102,19 +102,27 @@ 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
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<Order>(json);
```
Expand Down Expand Up @@ -150,6 +158,12 @@ public class Order

[JsonConverter(typeof(JsonDateTimeFormatConverter<DateFormats.ISO8601>))]
public DateTimeOffset ShippedAt { get; set; }

[JsonConverter(typeof(JsonDateTimeFormatConverter<DateFormats.DateOnlySlash>))]
public DateOnly DeliveryDate { get; set; }

[JsonConverter(typeof(JsonDateTimeFormatConverter<DateFormats.TimeOnlyShort>))]
public TimeOnly DeliveryTime { get; set; }
}

// Define your custom date formats
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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))]
Expand Down Expand Up @@ -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).

Expand Down
Loading