From af9c81e718c33bbc2c2effcd719c9abd673dc68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Tr=C3=B8strup?= Date: Sun, 17 Nov 2024 12:27:28 +0100 Subject: [PATCH] Format DateTime strings as correct ISO8601 (#296) Fixes #295 Turns out that the default string formatting of DateTime objects doesn't conform to ISO8601, so even when the object is saved as UTC; the app does not recognize it as UTC. --- .../Services/v2/AccountService.cs | 2 +- .../Services/v2/VoucherService.cs | 2 +- .../Utils/DateTimeConverter.cs | 22 +++++++ .../Utils/DateTimeConverterTest.cs | 60 +++++++++++++++++++ coffeecard/CoffeeCard.WebApi/Startup.cs | 1 + 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 coffeecard/CoffeeCard.Library/Utils/DateTimeConverter.cs create mode 100644 coffeecard/CoffeeCard.Tests.Unit/Utils/DateTimeConverterTest.cs diff --git a/coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs b/coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs index 2e91b90b..c55fb8f5 100644 --- a/coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs +++ b/coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs @@ -184,7 +184,7 @@ private async Task AnonymizeUserAsync(User user) user.Name = string.Empty; user.Password = string.Empty; user.Salt = string.Empty; - user.DateUpdated = DateTime.Now; + user.DateUpdated = DateTime.UtcNow; user.PrivacyActivated = true; user.UserState = UserState.Deleted; await _context.SaveChangesAsync(); diff --git a/coffeecard/CoffeeCard.Library/Services/v2/VoucherService.cs b/coffeecard/CoffeeCard.Library/Services/v2/VoucherService.cs index 0cc99df1..30d42cee 100644 --- a/coffeecard/CoffeeCard.Library/Services/v2/VoucherService.cs +++ b/coffeecard/CoffeeCard.Library/Services/v2/VoucherService.cs @@ -42,7 +42,7 @@ public async Task> CreateVouchers(IssueVoucher .Select(code => new Voucher { Code = code, - DateCreated = DateTime.Now, + DateCreated = DateTime.UtcNow, Product = product, Description = request.Description, Requester = request.Requester diff --git a/coffeecard/CoffeeCard.Library/Utils/DateTimeConverter.cs b/coffeecard/CoffeeCard.Library/Utils/DateTimeConverter.cs new file mode 100644 index 00000000..976739c5 --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Utils/DateTimeConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace CoffeeCard.Library.Utils +{ + public class DateTimeConverter : JsonConverter + { + private const string DateFormatIso8601 = "yyyy-MM-ddTHH:mm:ss.fffZ"; + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse(reader.GetString()!, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToUniversalTime().ToString(DateFormatIso8601, CultureInfo.InvariantCulture)); + } + } +} \ No newline at end of file diff --git a/coffeecard/CoffeeCard.Tests.Unit/Utils/DateTimeConverterTest.cs b/coffeecard/CoffeeCard.Tests.Unit/Utils/DateTimeConverterTest.cs new file mode 100644 index 00000000..7190acd8 --- /dev/null +++ b/coffeecard/CoffeeCard.Tests.Unit/Utils/DateTimeConverterTest.cs @@ -0,0 +1,60 @@ +using System; +using System.Text.Json; +using CoffeeCard.Library.Utils; +using Xunit; + +namespace CoffeeCard.Tests.Unit.Utils +{ + public class DateTimeConverterTests + { + private readonly JsonSerializerOptions _options; + + public DateTimeConverterTests() + { + _options = new JsonSerializerOptions(); + _options.Converters.Add(new DateTimeConverter()); + } + + [Fact] + public void Serialize_ShouldFormatDateTimeCorrectly() + { + // Arrange + var dateTime = new DateTime(2022, 1, 9, 21, 3, 52, millisecond: 123, DateTimeKind.Utc); + var expectedJson = "\"2022-01-09T21:03:52.123Z\""; + + // Act + var json = JsonSerializer.Serialize(dateTime, _options); + + // Assert + Assert.Equal(expectedJson, json); + } + + [Fact] + public void Deserialize_ShouldParseDateTimeCorrectly() + { + // Arrange + var json = "\"2022-01-09T21:03:52Z\""; + var expectedDateTime = new DateTime(2022, 1, 9, 21, 3, 52, DateTimeKind.Utc); + + // Act + var dateTime = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.Equal(expectedDateTime, dateTime); + } + + [Fact] + public void Serialize_Deserialize_RoundTrip_ShouldMatchOriginalDateTime() + { + // Arrange + var originalDateTime = new DateTime(2022, 1, 9, 21, 3, 52, DateTimeKind.Utc); + + // Act + var json = JsonSerializer.Serialize(originalDateTime, _options); + var deserializedDateTime = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.Equal(originalDateTime, deserializedDateTime); + } + } +} \ No newline at end of file diff --git a/coffeecard/CoffeeCard.WebApi/Startup.cs b/coffeecard/CoffeeCard.WebApi/Startup.cs index 320f9cff..e227dc7e 100644 --- a/coffeecard/CoffeeCard.WebApi/Startup.cs +++ b/coffeecard/CoffeeCard.WebApi/Startup.cs @@ -122,6 +122,7 @@ public void ConfigureServices(IServiceCollection services) options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.Never; options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + options.JsonSerializerOptions.Converters.Add(new DateTimeConverter()); }); services.AddCors(options => options.AddDefaultPolicy(builder =>