Add support to pre-epoch Unix time (#2614)

Co-authored-by: Max A. Kiselev <kiselevma@gmail.com>
Co-authored-by: James Newton-King <james@newtonking.com>
This commit is contained in:
MaxKot
2022-05-15 22:43:37 +08:00
committed by GitHub
co-authored by Max A. Kiselev James Newton-King
parent 13717cfc45
commit 7f78562d44
2 changed files with 120 additions and 2 deletions
@@ -25,6 +25,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
#if DNXCORE50
using Xunit;
using Test = Xunit.FactAttribute;
@@ -71,6 +72,17 @@ namespace Newtonsoft.Json.Tests.Converters
);
}
[Test]
public void SerializeDateBeforeEpoch()
{
DateTime date = new DateTime(1964, 2, 7);
long dateSeconds = (long)(date.ToUniversalTime() - new DateTime(1970, 1, 1)).TotalSeconds;
string result = JsonConvert.SerializeObject(date, new UnixDateTimeConverter { AllowPreEpoch = true });
Assert.AreEqual(dateSeconds.ToString(CultureInfo.InvariantCulture), result);
}
[Test]
public void WriteJsonInvalidType()
{
@@ -159,6 +171,19 @@ namespace Newtonsoft.Json.Tests.Converters
"Cannot convert invalid value to System.DateTimeOffset. Path '', line 1, position 5."
);
}
[Test]
public void DeserializeDateTimeOffsetBeforeEpoch()
{
UnixDateTimeConverter converter = new UnixDateTimeConverter(true);
DateTimeOffset d = new DateTimeOffset(1969, 2, 1, 20, 6, 18, TimeSpan.Zero);
string json = JsonConvert.SerializeObject(d, converter);
DateTimeOffset result = JsonConvert.DeserializeObject<DateTimeOffset>(json, converter);
Assert.AreEqual(new DateTimeOffset(1969, 2, 1, 20, 6, 18, TimeSpan.Zero), result);
}
#endif
[Test]
@@ -186,6 +211,14 @@ namespace Newtonsoft.Json.Tests.Converters
);
}
[Test]
public void DeserializeNegativeIntegerToDateTimeBeforeEpoch()
{
DateTime result = JsonConvert.DeserializeObject<DateTime>("-1514840476", new UnixDateTimeConverter(true));
Assert.AreEqual(new DateTime(1921, 12, 31, 02, 58, 44, DateTimeKind.Utc), result);
}
[Test]
public void DeserializeInvalidValueType()
{
@@ -263,6 +296,49 @@ namespace Newtonsoft.Json.Tests.Converters
Assert.IsNull(obj2.Object2);
Assert.AreEqual(new DateTime(2018, 1, 1, 21, 1, 16, DateTimeKind.Utc), obj2.ObjectNotHandled);
}
#if !NET20
[Test]
public void ConverterObjectWithDatesBeforeEpoch()
{
PreEpochUnixConverterObject obj1 = new PreEpochUnixConverterObject
{
Date1 = new DateTime(1969, 1, 1, 0, 0, 3, DateTimeKind.Utc),
Date2 = new DateTimeOffset(1969, 1, 1, 0, 0, 3, TimeSpan.Zero)
};
string json = JsonConvert.SerializeObject(obj1, Formatting.Indented);
StringAssert.AreEqual(@"{
""Date1"": -31535997,
""Date2"": -31535997
}", json);
PreEpochUnixConverterObject obj2 = JsonConvert.DeserializeObject<PreEpochUnixConverterObject>(json);
Assert.IsNotNull(obj2);
Assert.AreEqual(new DateTime(1969, 1, 1, 0, 0, 3, DateTimeKind.Utc), obj2.Date1);
Assert.AreEqual(new DateTimeOffset(1969, 1, 1, 0, 0, 3, TimeSpan.Zero), obj2.Date2);
}
#else
[Test]
public void ConverterObjectWithDatesBeforeEpoch()
{
PreEpochUnixConverterObject obj1 = new PreEpochUnixConverterObject
{
Date1 = new DateTime(1969, 1, 1, 0, 0, 3, DateTimeKind.Utc)
};
string json = JsonConvert.SerializeObject(obj1, Formatting.Indented);
StringAssert.AreEqual(@"{
""Date1"": -31535997
}", json);
PreEpochUnixConverterObject obj2 = JsonConvert.DeserializeObject<PreEpochUnixConverterObject>(json);
Assert.IsNotNull(obj2);
Assert.AreEqual(new DateTime(1969, 1, 1, 0, 0, 3, DateTimeKind.Utc), obj2.Date1);
}
#endif
}
[JsonArray(ItemConverterType = typeof(UnixDateTimeConverter))]
@@ -281,4 +357,15 @@ namespace Newtonsoft.Json.Tests.Converters
[JsonConverter(typeof(UnixDateTimeConverter))]
public object ObjectNotHandled { get; set; }
}
public class PreEpochUnixConverterObject
{
[JsonConverter(typeof(UnixDateTimeConverter), true)]
public DateTime Date1 { get; set; }
#if !NET20
[JsonConverter (typeof(UnixDateTimeConverter), true)]
public DateTimeOffset Date2 { get; set; }
#endif
}
}
@@ -36,6 +36,37 @@ namespace Newtonsoft.Json.Converters
{
internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
/// <summary>
/// Gets or sets a value indicating whether the dates before Unix epoch
/// should converted to and from JSON.
/// </summary>
/// <value>
/// <c>true</c> to allow converting dates before Unix epoch to and from JSON;
/// <c>false</c> to throw an exception when a date being converted to or from JSON
/// occurred before Unix epoch. The default value is <c>false</c>.
/// </value>
public bool AllowPreEpoch { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="UnixDateTimeConverter"/> class.
/// </summary>
public UnixDateTimeConverter() : this(false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UnixDateTimeConverter"/> class.
/// </summary>
/// <param name="allowPreEpoch">
/// <c>true</c> to allow converting dates before Unix epoch to and from JSON;
/// <c>false</c> to throw an exception when a date being converted to or from JSON
/// occurred before Unix epoch. The default value is <c>false</c>.
/// </param>
public UnixDateTimeConverter(bool allowPreEpoch)
{
AllowPreEpoch = allowPreEpoch;
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
@@ -61,7 +92,7 @@ namespace Newtonsoft.Json.Converters
throw new JsonSerializationException("Expected date object value.");
}
if (seconds < 0)
if (!AllowPreEpoch && seconds < 0)
{
throw new JsonSerializationException("Cannot convert date value that is before Unix epoch of 00:00:00 UTC on 1 January 1970.");
}
@@ -108,7 +139,7 @@ namespace Newtonsoft.Json.Converters
throw JsonSerializationException.Create(reader, "Unexpected token parsing date. Expected Integer or String, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
}
if (seconds >= 0)
if (AllowPreEpoch || seconds >= 0)
{
DateTime d = UnixEpoch.AddSeconds(seconds);