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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand Down Expand Up @@ -31,5 +32,5 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToUniversalTime().ToString(format));
=> writer.WriteStringValue(value.ToUniversalTime().ToString(format, CultureInfo.InvariantCulture));
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand Down Expand Up @@ -31,5 +32,5 @@ public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConver

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToUniversalTime().UtcDateTime.ToString(format));
=> writer.WriteStringValue(value.ToUniversalTime().UtcDateTime.ToString(format, CultureInfo.InvariantCulture));
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand Down Expand Up @@ -31,5 +32,5 @@ public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, Jso

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToString(format));
=> writer.WriteStringValue(value.ToString(format, CultureInfo.InvariantCulture));
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ namespace CommunityToolkit.Datasync.Server.Abstractions.Json;
public class DateTimeConverter : JsonConverter<DateTime>
{
private const string format = "yyyy-MM-dd'T'HH:mm:ss.fffK";
private static readonly CultureInfo culture = new("en-US");

/// <inheritdoc />
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTime.Parse(reader.GetString() ?? string.Empty);

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToUniversalTime().ToString(format, culture));
=> writer.WriteStringValue(value.ToUniversalTime().ToString(format, CultureInfo.InvariantCulture));
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ namespace CommunityToolkit.Datasync.Server.Abstractions.Json;
public class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
private const string format = "yyyy-MM-dd'T'HH:mm:ss.fffK";
private static readonly CultureInfo culture = new("en-US");

/// <inheritdoc />
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTimeOffset.Parse(reader.GetString() ?? string.Empty);

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToUniversalTime().UtcDateTime.ToString(format, culture));
=> writer.WriteStringValue(value.ToUniversalTime().UtcDateTime.ToString(format, CultureInfo.InvariantCulture));
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal static class JsonExtensions
/// <param name="reader">The <see cref="Utf8JsonReader"/> to assert.</param>
/// <param name="expectedTokenType">The expected <see cref="JsonTokenType"/> of the current token.</param>
/// <exception cref="JsonException">The current token did not match the <paramref name="expectedTokenType"/>.</exception>
public static void Expect(in this Utf8JsonReader reader, JsonTokenType expectedTokenType)
public static void Expect(this Utf8JsonReader reader, JsonTokenType expectedTokenType)
{
if (reader.TokenType != expectedTokenType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ namespace CommunityToolkit.Datasync.Server.Abstractions.Json;
public class TimeOnlyConverter : JsonConverter<TimeOnly>
{
private const string format = "HH:mm:ss.fff";
private static readonly CultureInfo culture = new("en-US");

/// <inheritdoc />
public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> TimeOnly.Parse(reader.GetString() ?? string.Empty);

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToString(format, culture));
=> writer.WriteStringValue(value.ToString(format, CultureInfo.InvariantCulture));
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,84 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.Datasync.Client.Serialization;
using System.Text.Json;

namespace CommunityToolkit.Datasync.Client.Test.Serialization;

[ExcludeFromCodeCoverage]
public class DateTimeConverter_Tests
public class DateTimeConverter_Tests : SerializerTests
{
private readonly JsonSerializerOptions serializerOptions;
[Theory]
[MemberData(nameof(Locales))]
public void Converter_ReadsJson(string culture)
{
const string json = """{"updatedAt":"2021-08-21T12:30:15.123+00:00"}""";
DateTime value = new(2021, 8, 21, 12, 30, 15, 123, DateTimeKind.Utc);

TestWithCulture(culture, () =>
{
Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
// Use FileTime comparison because no FluentAssertions support for DateTime ignoring zone info.
entity.UpdatedAt.ToFileTime().Should().Be(value.ToFileTime());
});
}

public DateTimeConverter_Tests()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_WritesJson(string culture)
{
this.serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
this.serializerOptions.Converters.Add(new DateTimeConverter());
const string json = """{"updatedAt":"2021-08-21T12:30:15.123Z"}""";
DateTime value = new(2021, 8, 21, 12, 30, 15, 123, 456, DateTimeKind.Utc);

TestWithCulture(culture, () =>
{
Entity entity = new() { UpdatedAt = value };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
});
}

[Fact]
public void Read_Null_Works()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_WritesJson_WithTimeZone(string culture)
{
string json = """{"dt":null}""";
SUT actual = JsonSerializer.Deserialize<SUT>(json, this.serializerOptions);
actual.dt.Should().Be(DateTime.MinValue);
const string json = """{"updatedAt":"2021-08-21T12:30:15.123Z"}""";
DateTime value = DateTime.Parse("2021-08-21T20:30:15.1234567+08:00");

TestWithCulture(culture, () =>
{
Entity entity = new() { UpdatedAt = value };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
});
}

[Fact]
public void Read_Int_Throws()
public void Converter_ThrowsOnBadDateInInput()
{
string json = """{"dt":42}""";
Action act = () => _ = JsonSerializer.Deserialize<SUT>(json, this.serializerOptions);
act.Should().Throw<JsonException>();
const string json = """{"updatedAt":"foo"}""";
Action act = () => _ = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
act.Should().Throw<FormatException>();
}

[Theory]
[MemberData(nameof(Locales))]
public void Converter_HandlesNullDateInInput(string culture)
{
const string json = """{"updatedAt":null}""";
DateTime value = DateTime.MinValue;

TestWithCulture(culture, () =>
{
Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
entity.UpdatedAt.Should().Be(value);
});
}

class SUT
#region Models
public class Entity
{
public DateTime dt { get; set; }
public DateTime UpdatedAt { get; set; }
}
#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,84 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.Datasync.Client.Serialization;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace CommunityToolkit.Datasync.Client.Test.Serialization;

[ExcludeFromCodeCoverage]
public class DateTimeOffsetConverter_Tests
public class DateTimeOffsetConverter_Tests : SerializerTests
{
private readonly JsonSerializerOptions serializerOptions;
[Theory]
[MemberData(nameof(Locales))]
public void Converter_ReadsJson(string culture)
{
const string json = "{\"updatedAt\":\"2021-08-21T12:30:15.123+00:00\"}";
DateTimeOffset value = new(2021, 8, 21, 12, 30, 15, 123, TimeSpan.Zero);

TestWithCulture(culture, () =>
{
Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
entity.UpdatedAt.Should().Be(value);
});
}

public DateTimeOffsetConverter_Tests()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_WritesJson(string culture)
{
this.serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
this.serializerOptions.Converters.Add(new DateTimeOffsetConverter());
const string json = "{\"updatedAt\":\"2021-08-21T12:30:15.123Z\"}";
DateTimeOffset value = new(2021, 8, 21, 12, 30, 15, 123, 456, TimeSpan.Zero);

TestWithCulture(culture, () =>
{
Entity entity = new() { UpdatedAt = value };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
});
}

[Fact]
public void Read_Null_Works()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_WritesJson_WithTimeZone(string culture)
{
string json = """{"dt":null}""";
SUT actual = JsonSerializer.Deserialize<SUT>(json, this.serializerOptions);
actual.dt.Should().Be(DateTimeOffset.MinValue);
const string json = "{\"updatedAt\":\"2021-08-21T12:30:15.123Z\"}";
DateTimeOffset value = new(2021, 8, 21, 20, 30, 15, 123, 456, TimeSpan.FromHours(8));

TestWithCulture(culture, () =>
{
Entity entity = new() { UpdatedAt = value };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
});
}

[Fact]
public void Read_Int_Throws()
public void Converter_ThrowsOnBadDateInInput()
{
string json = """{"dt":42}""";
Action act = () => _ = JsonSerializer.Deserialize<SUT>(json, this.serializerOptions);
act.Should().Throw<JsonException>();
const string json = "{\"updatedAt\":\"foo\"}";
Action act = () => _ = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
act.Should().Throw<Exception>();
}

[Theory]
[MemberData(nameof(Locales))]
public void Converter_HandlesNullDateInInput(string culture)
{
const string json = """{"updatedAt":null}""";
DateTimeOffset value = DateTimeOffset.MinValue;

TestWithCulture(culture, () =>
{
Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
entity.UpdatedAt.Should().Be(value);
});
}

class SUT
#region Models
public class Entity
{
public DateTimeOffset dt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
}
}
#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.Datasync.Client.Serialization;
using System.Globalization;
using System.Text.Json;

#pragma warning disable IDE0028 // Simplify collection initialization

namespace CommunityToolkit.Datasync.Client.Test.Serialization;

[ExcludeFromCodeCoverage]
public abstract class SerializerTests
{
protected static JsonSerializerOptions SerializerOptions
=> DatasyncSerializer.JsonSerializerOptions;

public static TheoryData<string> Locales => new()
{
"fr-FR",
"da-DA",
"en-US"
};

protected static void TestWithCulture(string culture, Action act)
{
CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
try
{
act.Invoke();
}
finally
{
Thread.CurrentThread.CurrentCulture = currentCulture;
}
}
}
Loading