Mjeduar
Mjeduar

Reputation: 97

Date from angular(timezone) to server (utc) then utc to Timezone

I have an issue with getting dates back in the right timezone.

Firs at all in the local machine, everything works fine but not in the server: the server is hosted in USA and the clients mostly are in Australia.

So a date is sent from angular app("12/23/2015 11:00:00 AM") to the server, the server store a date as utc in the database, until this point everything is working(I checked and the date is stored in the right utc)

book.StartDateTime = TimeZoneInfo.ConvertTimeToUtc(DateTime.SpecifyKind(book.StartDateTime.Value, DateTimeKind.Unspecified), ToolsHelper.OlsonTimeZoneToTimeZoneInfo(locationDetails.TimeZone)); // book.CreatedDate.Value.ToUniversalTime();

The issue is:

When a client request some dates stored in the database. The date stored in the database is return back to the client like this:

 bookview.StartDateTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(bookli.StartDateTime.Value, DateTimeKind.Utc), ToolsHelper.OlsonTimeZoneToTimeZoneInfo(deCompany.TimeZone));

I checked and at this point the date is "12/23/2015 11:00:00 AM" -> the conversion is right in the server(in the server side I put a log),

But in the angular is shown as "12/23/2015 10:00:00 PM"

So apparently the issue is when the date is transferred by the api to the client, maybe when is converted to JSON

I have tried different ways nothing work, I have removed "DateTime.SpecifyKind", I convert the date to string then back to datetime format and nothing seems to work.

What could I do?

Upvotes: 4

Views: 10997

Answers (1)

Matt Johnson-Pint
Matt Johnson-Pint

Reputation: 241475

A few things:

  • Your example is not complete, so I can only speculate on some areas. It would be better to show both sides, including how you load and parse the data in Angular, and what the data looks like over the wire.

  • You shouldn't be sending dates back and forth in a locale-specific format like "12/23/2015 11:00:00 AM". You might use those in your UI, but they're not appropriate over the wire (in your JSON). Instead, you should be using ISO8601/RFC3339, such as "2015-12-23T11:00:00Z". (You probably are already doing this if you're using WebAPI.)

  • A DateTime object when serialized to ISO8601 format is coupled with the associated DateTimeKind in the Kind property.

    • If the Kind is Utc, then the ISO8601 string will end with a Z.
    • If the Kind is Local, then the ISO8601 string will end with the machine-local offset for that timestamp, such as -08:00.
    • If the Kind is Unspecified, then the ISO8601 string will not have either Z or offset - which means that it cannot unambiguously represent a specific moment in time.

    This is ultimately the cause of the error. You're converting the DateTime to another time zone, which leaves it with Unspecified kind, which then gets serialized without an offset - so on the client side that gets interpreted (probably) in the local time zone of the browser.

  • A better approach is to use DateTimeOffset instead of DateTime. Then you don't have to worry about Kind, and the offset is always present. If you change your bookview.StartDateTime to a DateTimeOffset type, you can do something like this to fix the problem:

      DateTimeOffset dto = new DateTimeOffset(bookli.StartDateTime.Value, TimeSpan.Zero);
      bookView.StartDTO = TimeZoneInfo.ConvertTime(dto, ToolsHelper.OlsonTimeZoneToTimeZoneInfo(deCompany.TimeZone));
    

    That will ensure that the offset gets persisted in the data.

  • On the client side, pay careful attention to how the ISO string gets parsed. if it's loaded into a Date object, then it will indeed be converted to the client's time zone. Instead, you might take a look at moment.js for client-side time formatting. In particular, use moment.parseZone to keep the value in the same offset that it was presented. For example:

      var s = moment.parseZone("2015-12-31T11:00:00+00:00").format("L LT"); // "12/31/2015 11:00 AM"
    
  • In commented code, you also showed a call to DateTime.ToUniversalTime - be very careful with that. If the source kind is Unspecified, it is treated as Local. Therefore, the local time zone of the computer is reflected in the converted value. It's better to avoid ToUniversalTime and ToLocalTime entirely. Use only the conversion methods on TimeZoneInfo.

  • ToolsHelper.OlsonTimeZoneToTimeZoneInfo isn't something we know about either. But I'll assume it does a CLDR mapping similar to this one. However, if you're working with Olson time zones anyway, the better approach would be to not use TimeZoneInfo at all. Instead, use Noda Time, with it's native support for tzdb time zones.

Upvotes: 6

Related Questions