8000 Fix: date custom converter to serialize and deserialize ISO 8601 date and time by KoditkarVedant · Pull Request #433 · notion-dotnet/notion-sdk-net · GitHub
[go: up one dir, main page]

Skip to content

Fix: date custom converter to serialize and deserialize ISO 8601 date and time #433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 12, 2025
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
14 changes: 9 additions & 5 deletions Src/Notion.Client/Models/PropertyValue/DateCustomConverter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using System;
using System.Globalization;
using Newtonsoft.Json;

namespace Notion.Client
{
public class DateCustomConverter : JsonConverter<Date>
{
private const string DateFormat = "yyyy-MM-dd";
private const string DateTimeFormat = "yyyy-MM-ddTHH:mm:ssZ";

public override Date ReadJson(JsonReader reader, Type objectType, Date existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
Expand Down Ex 10000 pand Up @@ -39,16 +43,16 @@ public override void WriteJson(JsonWriter writer, Date value, JsonSerializer ser

if (value.Start.HasValue)
{
string startFormat = value.IncludeTime ? "yyyy-MM-ddTHH:mm:ss" : "yyyy-MM-dd";
string startFormat = value.IncludeTime ? DateTimeFormat : DateFormat;
writer.WritePropertyName("start");
writer.WriteValue(value.Start.Value.ToString(startFormat));
writer.WriteValue(value.Start.Value.ToString(startFormat, CultureInfo.InvariantCulture));
}

if (value.End.HasValue)
{
string endFormat = value.IncludeTime ? "yyyy-MM-ddTHH:mm:ss" : "yyyy-MM-dd";
string endFormat = value.IncludeTime ? DateTimeFormat : DateFormat;
writer.WritePropertyName("end");
writer.WriteValue(value.End.Value.ToString(endFormat));
writer.WriteValue(value.End.Value.ToString(endFormat, CultureInfo.InvariantCulture));
}

if (!string.IsNullOrEmpty(value.TimeZone))
Expand All @@ -71,7 +75,7 @@ public override void WriteJson(JsonWriter writer, Date value, JsonSerializer ser

includeTime = dateTimeString.Contains("T") || dateTimeString.Contains(" ");

return DateTimeOffset.Parse(dateTimeString).UtcDateTime;
return DateTimeOffset.Parse(dateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).UtcDateTime;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class Date
/// <summary>
/// Whether to include time
/// </summary>
[JsonIgnore]
public bool IncludeTime { get; set; } = true;
}
}
2 changes: 1 addition & 1 deletion Test/Notion.UnitTests/DatabasesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ var jsonData
});

//var formulaPropertyValue = (FormulaPropertyValue)page.Properties["FormulaProp"];
formulaPropertyValue.Formula.Date.Start.Should().Be(DateTime.Parse("2021-06-28"));
formulaPropertyValue.Formula.Date.Start.Should().Be(DateTimeOffset.Parse("2021-06-28", null, System.Globalization.DateTimeStyles.AssumeUniversal).UtcDateTime);
formulaPropertyValue.Formula.Date.End.Should().BeNull();
}
}
Expand Down
219 changes: 219 additions & 0 deletions Test/Notion.UnitTests/DateCustomConverterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
using System;
using System.IO;
using Newtonsoft.Json;
using Notion.Client;
using Xunit;

namespace NotionUnitTests.PropertyValue;

public class DateCustomConverterTests
{
private readonly DateCustomConverter _converter = new();
private readonly JsonSerializer _serializer = new();

[Fact]
public void Serialize_null_writes_null()
{
// Arrange
Date date = null;
var stringWriter = new StringWriter();
var jsonWriter = new JsonTextWriter(stringWriter);

// Act
_converter.WriteJson(jsonWriter, date, _serializer);
jsonWriter.Flush();

// Assert
Assert.Equal("null", stringWriter.ToString());
}

[Fact]
public void Serialize_start_date_only_produces_correct_json()
{
// Arrange
var date = new Date
{
Start = new DateTimeOffset(2023, 5, 15, 0, 0, 0, TimeSpan.Zero),
IncludeTime = false
};

// Act
var json = JsonConvert.SerializeObject(date);

// Assert
Assert.Contains("\"start\":\"2023-05-15\"", json);
Assert.DoesNotContain("\"end\":", json);
Assert.DoesNotContain("\"time_zone\":", json);
}

[Fact]
public void Serialize_start_and_end_dates_produces_correct_json()
{
// Arrange
var date = new Date
{
Start = new DateTimeOffset(2023, 5, 15, 0, 0, 0, TimeSpan.Zero),
End = new DateTimeOffset(2023, 5, 20, 0, 0, 0, TimeSpan.Zero),
IncludeTime = false
};

// Act
var json = JsonConvert.SerializeObject(date);

// Assert
Assert.Contains("\"start\":\"2023-05-15\"", json);
Assert.Contains("\"end\":\"2023-05-20\"", json);
Assert.DoesNotContain("\"time_zone\":", json);
}

[Fact]
public void Serialize_with_time_included_formats_time_correctly()
{
// Arrange
var date = new Date
{
Start = new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero),
IncludeTime = true
};

// Act
var json = JsonConvert.SerializeObject(date);

// Assert
Assert.Contains("\"start\":\"2023-05-15T14:30:45Z\"", json);
}

[Fact]
public void Serialize_with_time_zone_includes_time_zone()
{
// Arrange
var date = new Date
{
Start = new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero),
TimeZone = "Europe/London",
IncludeTime = true
};

// Act
var json = JsonConvert.SerializeObject(date);

// Assert
Assert.Contains("\"start\":\"2023-05-15T14:30:45Z\"", json);
Assert.Contains("\"time_zone\":\"Europe/London\"", json);
}

[Fact]
public void Deserialize_null_returns_null()
{
// Arrange
const string Json = "null";

// Act
var result = JsonConvert.DeserializeObject<Date>(Json);

// Assert
Assert.Null(result);
}

[Fact]
public void Deserialize_start_date_only_returns_correct_date()
{
// Arrange
const string Json = "{\"start\":\"2023-05-15\"}";

// Act
var result = JsonConvert.DeserializeObject<Date>(Json);

// Assert
Assert.NotNull(result);
Assert.Equal(new DateTimeOffset(2023, 5, 15, 0, 0, 0, TimeSpan.Zero), result.Start);
Assert.Null(result.End);
Assert.Null(result.TimeZone);
Assert.False(result.IncludeTime);
}

[Fact]
public void Deserialize_with_time_sets_include_time_flag()
{
// Arrange
const string Json = "{\"start\":\"2023-05-15T14:30:45\"}";

// Act
var result = JsonConvert.DeserializeObject<Date>(Json);

// Assert
Assert.NotNull(result);
Assert.Equal(new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero), result.Start);
Assert.True(result.IncludeTime);
}

[Fact]
public void Deserialize_with_start_end_and_time_zone_returns_complete_date()
{
// Arrange
const string Json = "{\"start\":\"2023-05-15T14:30:45\",\"end\":\"2023-05-20T16:45:00\",\"time_zone\":\"America/New_York\"}";

// Act
var result = JsonConvert.DeserializeObject<Date>(Json);

// Assert
Assert.NotNull(result);
Assert.Equal(new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero), result.Start);
Assert.Equal(new DateTimeOffset(2023, 5, 20, 16, 45, 0, TimeSpan.Zero), result.End);
Assert.Equal("America/New_York", result.TimeZone);
Assert.True(result.IncludeTime);
}

[Fact]
public void Date_property_value_serialize_deserialize_maintains_data()
{
// Arrange
var datePropertyValue = new DatePropertyValue
{
Date = new Date
{
Start = new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero),
End = new DateTimeOffset(2023, 5, 20, 16, 45, 0, TimeSpan.Zero),
TimeZone = "Europe/Berlin",
IncludeTime = true
}
};

// Act
var json = JsonConvert.SerializeObject(datePropertyValue);
var result = JsonConvert.DeserializeObject<DatePropertyValue>(json);

// Assert
Assert.NotNull(result);
Assert.Equal(PropertyValueType.Date, result.Type);
Assert.NotNull(result.Date);
Assert.Equal(datePropertyValue.Date.Start, result.Date.Start);
Assert.Equal(datePropertyValue.Date.End, result.Date.End);
Assert.Equal(datePropertyValue.Date.TimeZone, result.Date.TimeZone);
Assert.Equal(datePropertyValue.Date.IncludeTime, result.Date.IncludeTime);
}

[Fact]
public void Round_trip_preserves_data()
{
// Arrange
var originalDate = new Date
{
Start = new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero),
End = new DateTimeOffset(2023, 5, 20, 16, 45, 0, TimeSpan.Zero),
TimeZone = "Europe/Berlin",
IncludeTime = true
};

// Act
var json = JsonConvert.SerializeObject(originalDate);
var deserializedDate = JsonConvert.DeserializeObject<Date>(json);

// Assert
Assert.NotNull(deserializedDate);
Assert.Equal(originalDate.Start, deserializedDate.Start);
Assert.Equal(originalDate.End, deserializedDate.End);
Assert.Equal(originalDate.TimeZone, deserializedDate.TimeZone);
Assert.True(deserializedDate.IncludeTime);
}
}
Loading
0