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
20 changes: 14 additions & 6 deletions src/KubernetesClient/KubernetesJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
var iso8601TimeSpanString = XmlConvert.ToString(value); // XmlConvert for TimeSpan uses ISO8601, so delegate serialization to it
writer.WriteStringValue(iso8601TimeSpanString);

Check warning on line 27 in src/KubernetesClient/KubernetesJson.cs

View workflow job for this annotation

GitHub Actions / e2e

In externally visible method 'void Iso8601TimeSpanConverter.Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)', validate parameter 'writer' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
}
}

Expand Down Expand Up @@ -60,12 +60,20 @@
// Output as RFC3339Micro
var date = value.ToUniversalTime();

var basePart = date.ToString("yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture);
var frac = date.ToString(".ffffff", CultureInfo.InvariantCulture)
.TrimEnd('0')
.TrimEnd('.');

writer.WriteStringValue(basePart + frac + "Z");
// Check if there are any fractional seconds
var ticks = date.Ticks % TimeSpan.TicksPerSecond;
if (ticks == 0)
{
// No fractional seconds - use format without fractional part
var basePart = date.ToString("yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture);
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] For consistency with the existing format strings (like RFC3339Format and RFC3339MicroFormat), consider using quoted separators: "yyyy'-'MM'-'dd'T'HH':'mm':'ss" instead of "yyyy-MM-dd'T'HH:mm:ss".

Suggested change
var basePart = date.ToString("yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture);
var basePart = date.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss", CultureInfo.InvariantCulture);

Copilot uses AI. Check for mistakes.
writer.WriteStringValue(basePart + "Z");

Check warning on line 69 in src/KubernetesClient/KubernetesJson.cs

View workflow job for this annotation

GitHub Actions / e2e

In externally visible method 'void KubernetesDateTimeOffsetConverter.Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)', validate parameter 'writer' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
}
else
{
// Has fractional seconds - always use exactly 6 decimal places
var formatted = date.ToString(RFC3339MicroFormat, CultureInfo.InvariantCulture);
writer.WriteStringValue(formatted);
}
}
}

Expand Down Expand Up @@ -172,7 +180,7 @@
public static string Serialize(object value, JsonSerializerOptions jsonSerializerOptions = null)
{
#if NET8_0_OR_GREATER
var info = (jsonSerializerOptions ?? JsonSerializerOptions).GetTypeInfo(value.GetType());

Check warning on line 183 in src/KubernetesClient/KubernetesJson.cs

View workflow job for this annotation

GitHub Actions / e2e

In externally visible method 'string KubernetesJson.Serialize(object value, JsonSerializerOptions jsonSerializerOptions = null)', validate parameter 'value' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
return JsonSerializer.Serialize(value, info);
#else
return JsonSerializer.Serialize(value, jsonSerializerOptions ?? JsonSerializerOptions);
Expand All @@ -182,7 +190,7 @@
public static string Serialize(JsonDocument value, JsonSerializerOptions jsonSerializerOptions = null)
{
#if NET8_0_OR_GREATER
var info = (jsonSerializerOptions ?? JsonSerializerOptions).GetTypeInfo(value.GetType());

Check warning on line 193 in src/KubernetesClient/KubernetesJson.cs

View workflow job for this annotation

GitHub Actions / e2e

In externally visible method 'string KubernetesJson.Serialize(JsonDocument value, JsonSerializerOptions jsonSerializerOptions = null)', validate parameter 'value' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
return JsonSerializer.Serialize(value, info);
#else
return JsonSerializer.Serialize(value, jsonSerializerOptions ?? JsonSerializerOptions);
Expand All @@ -202,7 +210,7 @@
public static string Serialize(JsonNode value, JsonSerializerOptions jsonSerializerOptions = null)
{
#if NET8_0_OR_GREATER
var info = (jsonSerializerOptions ?? JsonSerializerOptions).GetTypeInfo(value.GetType());

Check warning on line 213 in src/KubernetesClient/KubernetesJson.cs

View workflow job for this annotation

GitHub Actions / e2e

In externally visible method 'string KubernetesJson.Serialize(JsonNode value, JsonSerializerOptions jsonSerializerOptions = null)', validate parameter 'value' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
return JsonSerializer.Serialize(value, info);
#else
return JsonSerializer.Serialize(value, jsonSerializerOptions ?? JsonSerializerOptions);
Expand Down
27 changes: 27 additions & 0 deletions tests/KubernetesClient.Tests/KubernetesJsonTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,31 @@ public void ReadWriteDatesJson()

Assert.Equal(kManifest, jsonFromObj2);
}

[Fact]
public void DateTimeWithFractionalSecondsAlwaysHasSixDigits()
{
// Test that datetime fields with fractional seconds always output exactly 6 decimal places
// This is required by Kubernetes API which expects RFC3339Micro format

// Create a datetime with 5 digits of precision (962170 microseconds = .96217 seconds)
var dt = new DateTime(2025, 11, 17, 22, 52, 34, 962, DateTimeKind.Utc).AddTicks(1700);

var secret = new V1Secret
{
Metadata = new V1ObjectMeta
{
Name = "test-secret",
CreationTimestamp = dt,
},
};

var json = KubernetesJson.Serialize(secret);

// Verify the datetime is serialized with exactly 6 decimal places
Assert.Contains("2025-11-17T22:52:34.962170Z", json);

// Also verify it doesn't have 5 digits (which would fail in Kubernetes)
Assert.DoesNotContain("2025-11-17T22:52:34.96217Z", json);
}
Comment on lines +146 to +171
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test only covers one specific case with non-zero trailing digits. Consider adding a test case for fractional seconds with trailing zeros (e.g., 100000 microseconds = .100000 seconds) to ensure they are always formatted with exactly 6 decimal places, not trimmed to fewer digits like .1Z.

Copilot uses AI. Check for mistakes.
}
Loading