8

I'm trying to get ahold of this timezone issue we are having. We would like to store all DateTimes in UTC, and then convert the DateTime to the user's timezone.

We decided to use NodaTime for this, as it seems like the right approach. However, we are experiencing an issue with it.

This is how we convert the DateTime to UTC (note - I hardcoded the usersTimeZone for now):

public static DateTime ConvertLocaltoUTC(this DateTime dateTime)
{
    LocalDateTime localDateTime = LocalDateTime.FromDateTime(dateTime);

    IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
    var usersTimezoneId = "Europe/Copenhagen";
    var usersTimezone = timeZoneProvider[usersTimezoneId];

    var zonedDbDateTime = usersTimezone.AtLeniently(localDateTime);
    var returnThis = zonedDbDateTime.ToDateTimeUtc();
    return zonedDbDateTime.ToDateTimeUtc();
}

And here is how we convert it back:

public static DateTime ConvertUTCtoLocal(this DateTime dateTime)
{
    Instant instant = Instant.FromDateTimeUtc(dateTime);
    IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
    var usersTimezoneId = "Europe/Copenhagen"; //just an example
    var usersTimezone = timeZoneProvider[usersTimezoneId];
    var usersZonedDateTime = instant.InZone(usersTimezone);
    return usersZonedDateTime.ToDateTimeUnspecified();
}

However, when we convert it back to local time, it throws an exception:

Argument Exception: Invalid DateTime.Kind for Instant.FromDateTimeUtc

at the first line of ConvertUTCtoLocal().

An example of the DateTime could be: "9/18/2017 5:28:46 PM" - yes this has been through the ConvertLocalToUTC method.

Am I providing an incorrect format? What am I doing wrong here?

Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
Jannik
  • 401
  • 1
  • 5
  • 17
  • Please provide a [mcve]. The value returned from `ZonedDateTime.ToDateTimeUtc()` should certainly be Utc, which is what `Instant.FromDateTimeUtc` requires. My guess is that the `DateTime` you're passing into `ConvertUTCtoLocal` isn't the result of calling `ConvertLocalToUTC`. However, I'd recommend avoiding using `DateTime` at all if you *possibly* can - use Noda Time types as far as possible. – Jon Skeet Sep 20 '17 at 10:07
  • (It would also be useful if you'd provide the version of Noda Time you're using.) – Jon Skeet Sep 20 '17 at 10:09
  • Please update this question with more information. As noted in both comments and answers, the problem lies elsewhere - which means at the moment, it's not a useful question. – Jon Skeet Sep 27 '17 at 10:11
  • Should we assume you're no longer interested in this question, as you haven't clarified it? – Jon Skeet Nov 21 '17 at 10:11
  • Hi John, I got it solved, it was because of corrupt data in my database. Sorry for the inconvenience – Jannik Nov 21 '17 at 12:22
  • If it's a problem you can't reproduce, I'd argue it's not a useful question any more - I'd suggest deleting it. You probably can't while Matt's answer is present, but I suspect he'd be willing to delete his answer if the question's going to be deleted. (If you can find a case where the question *does* actually make sense, that's a different matter.) – Jon Skeet Nov 21 '17 at 12:54

2 Answers2

12

The exception you show:

Argument Exception: Invalid DateTime.Kind for Instant.FromDateTimeUtc

Is thrown from this code:

Instant instant = Instant.FromDateTimeUtc(dateTime);

It means that dateTime.Kind needs to be DateTimeKind.Utc to be convertible to an Instant, and for whatever reason it is not.

If you look at the result of your ConvertLocaltoUTC method, you'll find that it does have .Kind == DateTimeKind.Utc.

So, the problem lies elsewhere in your code, wherever you created the dateTime you're passing in to ConvertUTCtoLocal.

You may find the solution could be any of the following:

  • You might need to call DateTime.SpecifyKind to set the kind to UTC, but be careful to only do this when your values are actually UTC and it's just not setting the kind. For example, use this when loading a UTC-based DateTime from a database.

  • You might need to call .ToUniversalTime(), but be careful to only do this if the machine's local time zone is relevant to your situation. For example, do this in desktop or mobile apps where a UI control is picking a date, but you meant it to mean UTC instead of local time.

  • You might need to change how you parse strings into DateTime values, such as by passing DateTimeStyles.RoundTripKind to a DateTime.Parse call (or any of its variants. For example, do this if you are reading data from text, csv, etc.

If you want to avoid having to decide, don't write functions that take DateTime as input or give DateTime as output. Instead, use DateTimeOffset, or use Noda-Time types like Instant, LocalDateTime, etc. as early as possible.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
9

This is what worked for me:

Instant instant = Instant.FromDateTimeUtc(DateTime.SpecifyKind(datetime, DateTimeKind.Utc));
Chris Halcrow
  • 28,994
  • 18
  • 176
  • 206