Skip to content

Commit 8b3ed2c

Browse files
authored
Merge pull request #221 from AzureCosmosDB/copilot/fix-date-format-migration-issue
Fix ISO-8601 date format preservation during data migration
2 parents 37cd7ef + 0a065b1 commit 8b3ed2c

4 files changed

Lines changed: 81 additions & 3 deletions

File tree

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension.UnitTests/CosmosDataSinkExtensionTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,80 @@
11
using Cosmos.DataTransfer.Interfaces;
22
using System.Dynamic;
3+
using System.Text;
34
using Microsoft.VisualStudio.TestTools.UnitTesting;
5+
using Newtonsoft.Json;
46

57
namespace Cosmos.DataTransfer.CosmosExtension.UnitTests
68
{
79
[TestClass]
810
public class CosmosDataSinkExtensionTests
911
{
12+
[TestMethod]
13+
public void CreateItemStream_WithDateString_PreservesFormat()
14+
{
15+
// Arrange - Simulate data read from source with ISO-8601 date string
16+
var sourceSettings = RawJsonCosmosSerializer.GetDefaultSettings();
17+
var sourceJson = "{\"id\": \"1\", \"event_time\": \"2023-12-19T00:00:00.000Z\"}";
18+
var serializer = JsonSerializer.Create(sourceSettings);
19+
using var reader = new JsonTextReader(new StringReader(sourceJson));
20+
var sourceDict = serializer.Deserialize<Dictionary<string, object?>>(reader)!;
21+
22+
// Act - Create data item and build expando object (simulating the pipeline)
23+
var dataItem = new CosmosDictionaryDataItem(sourceDict);
24+
var expando = dataItem.BuildDynamicObjectTree()!;
25+
26+
// Serialize using the same settings that CreateItemStream uses
27+
var json = JsonConvert.SerializeObject(expando, RawJsonCosmosSerializer.GetDefaultSettings());
28+
29+
// Assert - The date string format should be preserved
30+
Assert.IsTrue(json.Contains("\"2023-12-19T00:00:00.000Z\""),
31+
$"Date format should be preserved. Actual JSON: {json}");
32+
}
33+
34+
[TestMethod]
35+
public void CreateItemStream_WithNestedDateString_PreservesFormat()
36+
{
37+
// Arrange - Simulate data with nested date strings
38+
var sourceSettings = RawJsonCosmosSerializer.GetDefaultSettings();
39+
var sourceJson = "{\"id\": \"1\", \"data\": {\"created\": \"2023-12-19T00:00:00.000Z\", \"modified\": \"2023-12-20T12:30:45.123Z\"}}";
40+
var serializer = JsonSerializer.Create(sourceSettings);
41+
using var reader = new JsonTextReader(new StringReader(sourceJson));
42+
var sourceDict = serializer.Deserialize<Dictionary<string, object?>>(reader)!;
43+
44+
// Act
45+
var dataItem = new CosmosDictionaryDataItem(sourceDict);
46+
var expando = dataItem.BuildDynamicObjectTree()!;
47+
var json = JsonConvert.SerializeObject(expando, RawJsonCosmosSerializer.GetDefaultSettings());
48+
49+
// Assert
50+
Assert.IsTrue(json.Contains("\"2023-12-19T00:00:00.000Z\""),
51+
$"Created date format should be preserved. Actual JSON: {json}");
52+
Assert.IsTrue(json.Contains("\"2023-12-20T12:30:45.123Z\""),
53+
$"Modified date format should be preserved. Actual JSON: {json}");
54+
}
55+
56+
[TestMethod]
57+
public void CreateItemStream_WithDateStringArray_PreservesFormat()
58+
{
59+
// Arrange - Simulate data with date strings in array
60+
var sourceSettings = RawJsonCosmosSerializer.GetDefaultSettings();
61+
var sourceJson = "{\"id\": \"1\", \"timestamps\": [\"2023-12-19T00:00:00.000Z\", \"2023-12-20T00:00:00.000Z\"]}";
62+
var serializer = JsonSerializer.Create(sourceSettings);
63+
using var reader = new JsonTextReader(new StringReader(sourceJson));
64+
var sourceDict = serializer.Deserialize<Dictionary<string, object?>>(reader)!;
65+
66+
// Act
67+
var dataItem = new CosmosDictionaryDataItem(sourceDict);
68+
var expando = dataItem.BuildDynamicObjectTree()!;
69+
var json = JsonConvert.SerializeObject(expando, RawJsonCosmosSerializer.GetDefaultSettings());
70+
71+
// Assert
72+
Assert.IsTrue(json.Contains("\"2023-12-19T00:00:00.000Z\""),
73+
$"First date format should be preserved. Actual JSON: {json}");
74+
Assert.IsTrue(json.Contains("\"2023-12-20T00:00:00.000Z\""),
75+
$"Second date format should be preserved. Actual JSON: {json}");
76+
}
77+
1078
[TestMethod]
1179
public void BuildDynamicObjectTree_WithNestedArrays_WorksCorrectly()
1280
{

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosDataSinkExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ private static async Task<ItemResult> PopulateItem(Container container, ExpandoO
280280

281281
private static MemoryStream CreateItemStream(ExpandoObject item)
282282
{
283-
var json = JsonConvert.SerializeObject(item);
283+
var json = JsonConvert.SerializeObject(item, RawJsonCosmosSerializer.GetDefaultSettings());
284284
return new MemoryStream(Encoding.UTF8.GetBytes(json));
285285
}
286286

Interfaces/Cosmos.DataTransfer.Common.UnitTests/DataItemJsonConverterTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public static IEnumerable<object?[]> Test_WriteFieldValue_Data { get
6464
yield return new object[] { false, "\"x\":false" };
6565
yield return new object[] { new DateTime(2025, 10, 15, 16, 45, 12, 666, DateTimeKind.Unspecified), "\"x\":\"2025-10-15T16:45:12.6660000\"" };
6666
yield return new object[] { new DateTime(2025, 10, 15, 16, 45, 12, 666, DateTimeKind.Utc), "\"x\":\"2025-10-15T16:45:12.6660000Z\"" };
67+
yield return new object[] { new DateTimeOffset(2025, 10, 15, 16, 45, 12, 666, TimeSpan.Zero), "\"x\":\"2025-10-15T16:45:12.6660000+00:00\"" };
68+
yield return new object[] { new DateTimeOffset(2025, 10, 15, 16, 45, 12, 666, TimeSpan.FromHours(5)), "\"x\":\"2025-10-15T16:45:12.6660000+05:00\"" };
6769
yield return new object[] { 'a', "\"x\":\"a\"" };
6870
yield return new object[] { "Greetings human ", "\"x\":\"Greetings human \"" };
6971
// yield return new object[] { true, "\"x\":true" };

Interfaces/Cosmos.DataTransfer.Common/DataItemJsonConverter.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,11 @@ internal static void WriteFieldValue(Utf8JsonWriter writer, string fieldName, ob
139139
}
140140
else if (arrayItem is DateTime date)
141141
{
142-
writer.WriteStringValue(date.ToString("O"));
142+
writer.WriteStringValue(GetAsUnescaped(date.ToString("O")));
143+
}
144+
else if (arrayItem is DateTimeOffset dateOffset)
145+
{
146+
writer.WriteStringValue(GetAsUnescaped(dateOffset.ToString("O")));
143147
}
144148
else if (arrayItem is null)
145149
{
@@ -178,7 +182,11 @@ internal static void WriteFieldValue(Utf8JsonWriter writer, string fieldName, ob
178182
}
179183
else if (fieldValue is DateTime date)
180184
{
181-
writer.WriteString(propertyName, date.ToString("O"));
185+
writer.WriteString(propertyName, GetAsUnescaped(date.ToString("O")));
186+
}
187+
else if (fieldValue is DateTimeOffset dateOffset)
188+
{
189+
writer.WriteString(propertyName, GetAsUnescaped(dateOffset.ToString("O")));
182190
}
183191
else
184192
{

0 commit comments

Comments
 (0)