0

I am storing datetime in a stringified JSON object in Redis cache like so:

"{\"email\":\"myemail@testdomain.com\", \"expiry\": \"2018-03-19T23:00:03.0658822+00:00\"}"

In C#, when I query this data from Redis and convert it to string, it loses its timezone value and gets automatically stripped off of its timezone information.

RedisValue cookie = GetRedisDatabase().StringGet("sessionhash");
JObject cookieValue = JObject.Parse(cookie.ToString());
    var email = JObject.Parse(cookie.ToString())["email"];
    var expiry = JObject.Parse(cookie.ToString())["expiry"].ToString();

The "expiry" string above only contains "2018/03/19 23:00:03". It seems like C# is automatically detecting the string to be of datetime format, and is stripping off timezone information from it.

How can I ensure the "expiry" string is "2018-03-19T23:00:03.0658822+00:00"?

Sun
  • 559
  • 2
  • 9
  • 30

5 Answers5

1

DateTime does not know about timezones. Instead it has a DateTimeKind property which tells you if the time is machine local, UTC, or unknown. Methods ToLocalTime will convert a known UTC or unknown time to local time, and do nothing of already local.

You'll need to use something else that keeps the timezone information, i believe DateTimeOffset can track a time with a variable offset, but not the timezone.

NodaTime is a library which understands timezones.

Andy
  • 8,432
  • 6
  • 38
  • 76
  • " i believe DateTimeOffset can track a time with a variable offset, but not the timezone." If you read the documentation for DateTimeOffset, you'd know that the offset is the number of hours it's offset from UTC, not from local time. – Powerlord Mar 19 '18 at 23:49
  • 1
    @powerlord Where did i say the offset was from a local time? – Andy Mar 19 '18 at 23:57
1
internal class Program
{
    private static void Main()
    {
        string expiry = "2018-03-19T23:00:03.0658822+00:00";
        DateTime parsedExpiry = DateTime.Parse(expiry);
        Console.WriteLine(parsedExpiry.ToString());
        Console.ReadKey();
    }
}

This code converts 19/3/2018 23:00 into 20/3/2018 7:00.

The reason it does this is because, as per above answers, DateTime doesn't hold on to any TimeZone information. The only information you have is DateTime.Kind, which in the case of my code, outputs Local. I can use parsedExpirey.ToUniversalTime() to get UTC.

You could do some extra parsing on the string representation and use the TimeZoneInfo class to maintain the timezone, but you'll likely need an extra column / storage space to store that info. You can use the Convert option, but then you'll be storing DateTimes in all different timezones, you'd be better off using ToUniversalTime and storing it all in UTC (best practice), then converting it to Local time for presentation to the user (or leave it UTC, depending on the application).

Trent
  • 1,595
  • 15
  • 37
0

Your final ToString asked for the time without TZ info. Do this

RedisValue cookie = GetRedisDatabase().StringGet("sessionhash");
JObject cookieValue = JObject.Parse(cookie.ToString());
    var email = JObject.Parse(cookie.ToString())["email"];
    var expiry = JObject.Parse(cookie.ToString())["expiry"].ToString("O");
pm100
  • 48,078
  • 23
  • 82
  • 145
0

I have a few general rules regarding handling DateTimes:

  1. Always store, retrieve and transmit the value in UTC. Windows is pretty good at translating any UTC value to whatever the current user picked as his favourite timezone. You do not want to deal with Timezones if you can avoid it at all.
  2. Never store, retrieve and transmit the value as string.
  3. In case 3 can not work, at least pick a fixed culture and string encoding at all endpoints. You do not want to add those to your worries.
  4. In rare cases (Callendar Apps) it might be beneficial to store the "originally saved timezone".
Christopher
  • 9,634
  • 2
  • 17
  • 31
0

Unfortunately you cannot determine the time zone from an ISO date/time string. You can only determine the offset. The time zone names and codes are not unique-- for example, "Arabia Standard Time" has an offset of UTC+03, but has the code "AST," which collides with "Atlantic Standard Time" (offset UTC-04). So while you can map in one direction, you can't reliably map in the other.

That being said, getting the offset isn't so bad if you use a DateTimeOffset instead of DateTime. If the field isn't a DateTimeOffset in your object model, you can create a temporary anonymous type as a template and get it that way. Example:

public static void Main()
{
    var input = "{\"email\":\"myemail@testdomain.com\", \"expiry\": \"2018-03-19T23:00:03.0658822+01:00\"}";
    var template = new { email = "", expiry = DateTimeOffset.Now };
    var result = JsonConvert.DeserializeAnonymousType(input, template);
    Console.WriteLine("DateTime (GMT): {0:R}\r\nOffset from GMT: {1}", result.expiry, result.expiry.Offset);
}

Output:

DateTime (GMT): Mon, 19 Mar 2018 22:00:03 GMT
Offset from GMT: 01:00:00

Code on DotNetFiddle

John Wu
  • 50,556
  • 8
  • 44
  • 80