Skip to content

Feature/configurable timezone #294

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 6 commits into from
Feb 8, 2023
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ We'd love your contributions! If you want to contribute please read our [Contrib
* [@Jeevananthan](https://github.com/Jeevananthan-23)
* [@mariusmuntean](https://github.com/mariusmuntean)
* [@jcreus1](https://github.com/jcreus1)
* [@JuliusMikkela](https://github.com/JuliusMikkela)

<!-- Logo -->
[Logo]: images/logo.svg
Expand Down
11 changes: 0 additions & 11 deletions src/Redis.OM.POC/RedisCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,12 @@
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using System.Text.Json.Serialization;
using Redis.OM.Contracts;
using Redis.OM;
using Redis.OM.Modeling;

namespace Redis.OM
{
public static class RedisCommands
{
private static JsonSerializerOptions _options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
static RedisCommands()
{
_options.Converters.Add(new GeoLocJsonConverter());
}
public static string Ping(this IRedisConnection connection) => connection.Execute("PING");

public static string Set(this IRedisConnection connection, string key, string value) => connection.Execute("SET", key, value);
Expand Down
2 changes: 1 addition & 1 deletion src/Redis.OM/Modeling/DateTimeJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso
long val = reader.TokenType == JsonTokenType.String ? long.Parse(reader.GetString() !) : reader.GetInt64();

DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
return TimeZoneInfo.ConvertTimeFromUtc(dateTime.AddMilliseconds(val), TimeZoneInfo.Local);
return TimeZoneInfo.ConvertTimeFromUtc(dateTime.AddMilliseconds(val), RedisSerializationSettings.TimeZone);
}

/// <inheritdoc />
Expand Down
19 changes: 2 additions & 17 deletions src/Redis.OM/Modeling/RedisCollectionStateManager.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Redis.OM;
using Redis.OM.Modeling;
using JsonSerializer = System.Text.Json.JsonSerializer;

namespace Redis.OM.Modeling
Expand All @@ -15,17 +11,6 @@ namespace Redis.OM.Modeling
/// </summary>
public class RedisCollectionStateManager
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new ()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};

static RedisCollectionStateManager()
{
JsonSerializerOptions.Converters.Add(new GeoLocJsonConverter());
JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
}

/// <summary>
/// Initializes a new instance of the <see cref="RedisCollectionStateManager"/> class.
/// </summary>
Expand Down Expand Up @@ -118,7 +103,7 @@ internal bool TryDetectDifferencesSingle(string key, object value, out IList<IOb

if (DocumentAttribute.StorageType == StorageType.Json)
{
var dataJson = JsonSerializer.Serialize(value, JsonSerializerOptions);
var dataJson = JsonSerializer.Serialize(value, RedisSerializationSettings.JsonSerializerOptions);
var current = JsonConvert.DeserializeObject<JObject>(dataJson, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var snapshot = (JToken)Snapshot[key];
var diff = FindDiff(current!, snapshot);
Expand Down Expand Up @@ -153,7 +138,7 @@ internal IDictionary<string, IList<IObjectDiff>> DetectDifferences()
{
if (Data.ContainsKey(key))
{
var dataJson = JsonSerializer.Serialize(Data[key], JsonSerializerOptions);
var dataJson = JsonSerializer.Serialize(Data[key], RedisSerializationSettings.JsonSerializerOptions);
var current = JsonConvert.DeserializeObject<JObject>(dataJson, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var snapshot = (JToken)Snapshot[key];
var diff = FindDiff(current!, snapshot);
Expand Down
32 changes: 10 additions & 22 deletions src/Redis.OM/RedisCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Redis.OM.Contracts;
using Redis.OM.Modeling;
Expand All @@ -15,17 +14,6 @@ namespace Redis.OM
/// </summary>
public static class RedisCommands
{
private static readonly JsonSerializerOptions Options = new ()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};

static RedisCommands()
{
Options.Converters.Add(new GeoLocJsonConverter());
Options.Converters.Add(new DateTimeJsonConverter());
}

/// <summary>
/// Serializes an object to either hash or json (depending on how it's decorated), and saves it in redis.
/// </summary>
Expand Down Expand Up @@ -150,7 +138,7 @@ public static async Task<bool> JsonSetAsync(this IRedisConnection connection, st
/// <returns>whether the operation succeeded.</returns>
public static async Task<bool> JsonSetAsync(this IRedisConnection connection, string key, string path, object obj)
{
var json = JsonSerializer.Serialize(obj, Options);
var json = JsonSerializer.Serialize(obj, RedisSerializationSettings.JsonSerializerOptions);
var result = await connection.ExecuteAsync("JSON.SET", key, path, json);
return result == "OK";
}
Expand Down Expand Up @@ -181,7 +169,7 @@ public static async Task<bool> JsonSetAsync(this IRedisConnection connection, st
/// <returns>whether the operation succeeded.</returns>
public static async Task<bool> JsonSetAsync(this IRedisConnection connection, string key, string path, object obj, TimeSpan timeSpan)
{
var json = JsonSerializer.Serialize(obj, Options);
var json = JsonSerializer.Serialize(obj, RedisSerializationSettings.JsonSerializerOptions);
var result = await connection.JsonSetAsync(key, path, json, timeSpan);
return result;
}
Expand Down Expand Up @@ -224,7 +212,7 @@ public static async Task<bool> JsonSetAsync(this IRedisConnection connection, st
/// <returns>whether the operation succeeded.</returns>
public static async Task<bool> JsonSetAsync(this IRedisConnection connection, string key, string path, object obj, WhenKey when, TimeSpan? timeSpan = null)
{
var json = JsonSerializer.Serialize(obj, Options);
var json = JsonSerializer.Serialize(obj, RedisSerializationSettings.JsonSerializerOptions);
return await connection.JsonSetAsync(key, path, json, when, timeSpan);
}

Expand Down Expand Up @@ -292,7 +280,7 @@ public static bool JsonSet(this IRedisConnection connection, string key, string
/// <returns>whether the operation succeeded.</returns>
public static bool JsonSet(this IRedisConnection connection, string key, string path, object obj)
{
var json = JsonSerializer.Serialize(obj, Options);
var json = JsonSerializer.Serialize(obj, RedisSerializationSettings.JsonSerializerOptions);
var result = connection.Execute("JSON.SET", key, path, json);
return result == "OK";
}
Expand Down Expand Up @@ -323,7 +311,7 @@ public static bool JsonSet(this IRedisConnection connection, string key, string
/// <returns>whether the operation succeeded.</returns>
public static bool JsonSet(this IRedisConnection connection, string key, string path, object obj, TimeSpan timeSpan)
{
var json = JsonSerializer.Serialize(obj, Options);
var json = JsonSerializer.Serialize(obj, RedisSerializationSettings.JsonSerializerOptions);
return connection.JsonSet(key, path, json, timeSpan);
}

Expand Down Expand Up @@ -365,7 +353,7 @@ public static bool JsonSet(this IRedisConnection connection, string key, string
/// <returns>whether the operation succeeded.</returns>
public static bool JsonSet(this IRedisConnection connection, string key, string path, object obj, WhenKey when, TimeSpan? timeSpan = null)
{
var json = JsonSerializer.Serialize(obj, Options);
var json = JsonSerializer.Serialize(obj, RedisSerializationSettings.JsonSerializerOptions);
return connection.JsonSet(key, path, json, when, timeSpan);
}

Expand Down Expand Up @@ -585,7 +573,7 @@ public static string Set(this IRedisConnection connection, object obj, TimeSpan
var args = new List<string> { key };
args.AddRange(paths);
var res = (string)connection.Execute("JSON.GET", args.ToArray());
return !string.IsNullOrEmpty(res) ? JsonSerializer.Deserialize<T>(res, Options) : default;
return !string.IsNullOrEmpty(res) ? JsonSerializer.Deserialize<T>(res, RedisSerializationSettings.JsonSerializerOptions) : default;
}

/// <summary>
Expand All @@ -601,7 +589,7 @@ public static string Set(this IRedisConnection connection, object obj, TimeSpan
var args = new List<string> { key };
args.AddRange(paths);
var res = (string)await connection.ExecuteAsync("JSON.GET", args.ToArray());
return !string.IsNullOrEmpty(res) ? JsonSerializer.Deserialize<T>(res, Options) : default;
return !string.IsNullOrEmpty(res) ? JsonSerializer.Deserialize<T>(res, RedisSerializationSettings.JsonSerializerOptions) : default;
}

/// <summary>
Expand Down Expand Up @@ -791,7 +779,7 @@ internal static void UnlinkAndSet<T>(this IRedisConnection connection, string ke
_ = value ?? throw new ArgumentNullException(nameof(value));
if (storageType == StorageType.Json)
{
connection.CreateAndEval(nameof(Scripts.UnlinkAndSendJson), new[] { key }, new[] { JsonSerializer.Serialize(value, Options) });
connection.CreateAndEval(nameof(Scripts.UnlinkAndSendJson), new[] { key }, new[] { JsonSerializer.Serialize(value, RedisSerializationSettings.JsonSerializerOptions) });
}
else
{
Expand Down Expand Up @@ -822,7 +810,7 @@ internal static async Task UnlinkAndSetAsync<T>(this IRedisConnection connection
_ = value ?? throw new ArgumentNullException(nameof(value));
if (storageType == StorageType.Json)
{
await connection.CreateAndEvalAsync(nameof(Scripts.UnlinkAndSendJson), new[] { key }, new[] { JsonSerializer.Serialize(value, Options) });
await connection.CreateAndEvalAsync(nameof(Scripts.UnlinkAndSendJson), new[] { key }, new[] { JsonSerializer.Serialize(value, RedisSerializationSettings.JsonSerializerOptions) });
}
else
{
Expand Down
12 changes: 2 additions & 10 deletions src/Redis.OM/RedisObjectHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ namespace Redis.OM
/// </summary>
internal static class RedisObjectHandler
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new ();

private static readonly Dictionary<Type, object?> TypeDefaultCache = new ()
{
{ typeof(string), null },
Expand All @@ -30,12 +28,6 @@ internal static class RedisObjectHandler
{ typeof(ulong), default(ulong) },
};

static RedisObjectHandler()
{
JsonSerializerOptions.Converters.Add(new GeoLocJsonConverter());
JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
}

/// <summary>
/// Builds object from provided hash set.
/// </summary>
Expand Down Expand Up @@ -64,7 +56,7 @@ internal static T FromHashSet<T>(IDictionary<string, string> hash)
asJson = SendToJson(hash, typeof(T));
}

return JsonSerializer.Deserialize<T>(asJson, JsonSerializerOptions) ?? throw new Exception("Deserialization fail");
return JsonSerializer.Deserialize<T>(asJson, RedisSerializationSettings.JsonSerializerOptions) ?? throw new Exception("Deserialization fail");
}

/// <summary>
Expand Down Expand Up @@ -99,7 +91,7 @@ internal static T FromHashSet<T>(IDictionary<string, RedisReply> hash)
throw new ArgumentException("Type must be decorated with a DocumentAttribute");
}

return JsonSerializer.Deserialize<T>(asJson, JsonSerializerOptions) ?? throw new Exception("Deserialization fail");
return JsonSerializer.Deserialize<T>(asJson, RedisSerializationSettings.JsonSerializerOptions) ?? throw new Exception("Deserialization fail");
}

/// <summary>
Expand Down
48 changes: 48 additions & 0 deletions src/Redis.OM/RedisSerializationSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Redis.OM.Modeling;

namespace Redis.OM
{
/// <summary>
/// Configurable settings for how serialization and deserialization is handled.
/// </summary>
public static class RedisSerializationSettings
{
/// <summary>
/// <see cref="JsonSerializerOptions"/> used when serializing.
/// </summary>
public static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};

static RedisSerializationSettings()
{
JsonSerializerOptions.Converters.Add(new GeoLocJsonConverter());
JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
}

/// <summary>
/// Gets default/assumed timezone used by redis when deserializing datetimes.
/// </summary>
public static TimeZoneInfo TimeZone { get; private set; } = TimeZoneInfo.Local;

/// <summary>
/// Set the default/assumed <see cref="TimeZoneInfo"/> for deserialization to <see cref="TimeZoneInfo.Utc"/> instead of <see cref="TimeZoneInfo.Local"/>.
/// </summary>
public static void UseUtcTime()
{
TimeZone = TimeZoneInfo.Utc;
}

/// <summary>
/// Set the default/assumed <see cref="TimeZoneInfo"/> for deserialization to the default of <see cref="TimeZoneInfo.Local"/>.
/// </summary>
public static void UseLocalTime()
{
TimeZone = TimeZoneInfo.Local;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,10 @@ public void TestBasicQuerySpecialCharacters()
public void TestSave()
{
var collection = new RedisCollection<Person>(_connection,10000);
var count = collection.Count();
var count = 0;
foreach (var person in collection)
{
count++;
person.Name = "TestSave";
person.Mother = new Person {Name = "Diane"};
}
Expand Down Expand Up @@ -204,9 +205,10 @@ public async Task TestSaveAsyncSecondEnumeration()
public async Task TestSaveHashAsync()
{
var collection = new RedisCollection<HashPerson>(_connection, 10000);
var count = collection.Count();
var count = 0;
await foreach (var person in collection)
{
count++;
person.Name = "TestSaveHashAsync";
person.Mother = new HashPerson {Name = "Diane"};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Redis.OM.Contracts;
using Xunit;

Expand All @@ -19,6 +17,7 @@ public DateTimeSerializationTest(RedisSetup setup)
[Fact]
public void TestDateTimeSerialization()
{
RedisSerializationSettings.UseLocalTime();
var time = DateTime.Now;
var obj = new ObjectWithATimestamp { Name = "Foo", Time = time };
var objNonNullNullTime = new ObjectWithATimestamp { Name = "bar", Time = time, NullableTime = time };
Expand All @@ -34,6 +33,7 @@ public void TestDateTimeSerialization()
[Fact]
public void TestJsonDateTimeSerialization()
{
RedisSerializationSettings.UseLocalTime();
var time = DateTime.Now;
var obj = new JsonObjectWithDateTime { Name = "Foo", Time = time };
var objNonNullNullTime = new JsonObjectWithDateTime { Name = "bar", Time = time, NullableTime = time };
Expand All @@ -45,5 +45,21 @@ public void TestJsonDateTimeSerialization()
Assert.Null(reconstituted.NullableTime);
Assert.Equal(time.ToString("yyyy-MM-ddTHH:mm:ss.fff"), reconstitutedObj2.NullableTime.Value.ToString("yyyy-MM-ddTHH:mm:ss.fff"));
}

[Fact]
public void TestJsonUtcDateTimeSerialization()
{
RedisSerializationSettings.UseUtcTime();
var time = DateTime.UtcNow;
var obj = new JsonObjectWithDateTime { Name = "Foo", Time = time };
var objNonNullNullTime = new JsonObjectWithDateTime { Name = "bar", Time = time, NullableTime = time };
var id = _connection.Set(obj);
var id2 = _connection.Set(objNonNullNullTime);
var reconstituted = _connection.Get<JsonObjectWithDateTime>(id);
var reconstitutedObj2 = _connection.Get<JsonObjectWithDateTime>(id2);
Assert.Equal(time.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), reconstituted.Time.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
Assert.Null(reconstituted.NullableTime);
Assert.Equal(time.ToString("yyyy-MM-ddTHH:mm:ss.fff"), reconstitutedObj2.NullableTime.Value.ToString("yyyy-MM-ddTHH:mm:ss.fff"));
}
}
}