Reputation: 97
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
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.
Kind
is Utc
, then the ISO8601 string will end with a Z
.Kind
is Local
, then the ISO8601 string will end with the machine-local offset for that timestamp, such as -08:00
.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