From 0e20cdbeb01160f41745a3bb5c14abac9709d721 Mon Sep 17 00:00:00 2001 From: Luis Cantero Date: Sat, 13 Dec 2025 10:29:06 +0100 Subject: [PATCH 1/4] Add last resort to parse DateTimeOff --- src/KubernetesClient/KubernetesJson.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/KubernetesClient/KubernetesJson.cs b/src/KubernetesClient/KubernetesJson.cs index 35cfe3ac..feef16e4 100644 --- a/src/KubernetesClient/KubernetesJson.cs +++ b/src/KubernetesClient/KubernetesJson.cs @@ -34,19 +34,28 @@ public sealed class KubernetesDateTimeOffsetConverter : JsonConverter (m.Value + "000000000").Substring(0, 7 + 1)); // 7 digits + 1 for the dot - if (DateTimeOffset.TryParseExact(str, new[] { RFC3339NanoFormat }, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) + if (DateTimeOffset.TryParseExact(str, RFC3339NanoFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) + { + return result; + } + + // Last resort: try general DateTimeOffset parsing + if (DateTimeOffset.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out result)) { return result; } @@ -54,7 +63,6 @@ public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConver throw new FormatException($"Unable to parse {originalstr} as RFC3339 RFC3339Micro or RFC3339Nano"); } - public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) { // Output as RFC3339Micro From c2292dec419a0d9058397df8aca0255db629ee1f Mon Sep 17 00:00:00 2001 From: Luis Cantero Date: Sat, 13 Dec 2025 10:44:15 +0100 Subject: [PATCH 2/4] Apply suggestion Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/KubernetesClient/KubernetesJson.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/KubernetesClient/KubernetesJson.cs b/src/KubernetesClient/KubernetesJson.cs index feef16e4..bde96afc 100644 --- a/src/KubernetesClient/KubernetesJson.cs +++ b/src/KubernetesClient/KubernetesJson.cs @@ -55,7 +55,7 @@ public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConver } // Last resort: try general DateTimeOffset parsing - if (DateTimeOffset.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out result)) + if (DateTimeOffset.TryParse(originalstr, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out result)) { return result; } From 5a5afcb807d6d58fc999cca8b34503be59230679 Mon Sep 17 00:00:00 2001 From: Luis Cantero Date: Sat, 13 Dec 2025 16:10:42 +0100 Subject: [PATCH 3/4] Be more restrictive --- src/KubernetesClient/KubernetesJson.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/KubernetesClient/KubernetesJson.cs b/src/KubernetesClient/KubernetesJson.cs index bde96afc..3b7d048d 100644 --- a/src/KubernetesClient/KubernetesJson.cs +++ b/src/KubernetesClient/KubernetesJson.cs @@ -34,7 +34,12 @@ public sealed class KubernetesDateTimeOffsetConverter : JsonConverter (m.Value + "000000000").Substring(0, 7 + 1)); // 7 digits + 1 for the dot - if (DateTimeOffset.TryParseExact(str, RFC3339NanoFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) - { - return result; - } - - // Last resort: try general DateTimeOffset parsing - if (DateTimeOffset.TryParse(originalstr, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out result)) + if (DateTimeOffset.TryParseExact(str, NanoFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) { return result; } From 290d57a0b6821dc72d2181a1ca707506330c59dd Mon Sep 17 00:00:00 2001 From: Luis Cantero Date: Sat, 13 Dec 2025 16:10:48 +0100 Subject: [PATCH 4/4] Add unit test --- .../KubernetesJsonTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/KubernetesClient.Tests/KubernetesJsonTests.cs b/tests/KubernetesClient.Tests/KubernetesJsonTests.cs index e8f19945..38fd0500 100644 --- a/tests/KubernetesClient.Tests/KubernetesJsonTests.cs +++ b/tests/KubernetesClient.Tests/KubernetesJsonTests.cs @@ -169,4 +169,36 @@ public void DateTimeWithFractionalSecondsAlwaysHasSixDigits() // Also verify it doesn't have 5 digits (which would fail in Kubernetes) Assert.DoesNotContain("2025-11-17T22:52:34.96217Z", json); } + + [Fact] + public void DateTimeWithTimezoneOffsetParsesCorrectly() + { + // Test that datetime with explicit timezone offset (e.g., +00:00) is parsed correctly + // This uses the "last resort" general DateTimeOffset parsing fallback + var json = "{\"metadata\":{\"creationTimestamp\":\"2025-12-12T16:16:55.079293+00:00\"}}"; + + var secret = KubernetesJson.Deserialize(json); + + Assert.NotNull(secret.Metadata?.CreationTimestamp); + var dt = secret.Metadata.CreationTimestamp.Value; + + Assert.Equal(2025, dt.Year); + Assert.Equal(12, dt.Month); + Assert.Equal(12, dt.Day); + Assert.Equal(16, dt.Hour); + Assert.Equal(16, dt.Minute); + Assert.Equal(55, dt.Second); + Assert.Equal(79, dt.Millisecond); +#if NET7_0_OR_GREATER + Assert.Equal(293, dt.Microsecond); +#endif + } + + [Fact] + public void DateTimeNonRfc3339FormatIsRejected() + { + var json = "{\"metadata\":{\"creationTimestamp\":\"12/31/2023\"}}"; + + Assert.Throws(() => KubernetesJson.Deserialize(json)); + } }