2

I have MVC web application. I am storing UTC time in database. (Not datetime but just a time). In C# When I retrieve this time from the database I get timespan object back. I also have offset available in minutes. For example.

double offset = 600;

How do I use this offset to convert timespan to local datetime.

Note I don't want to use DateTime.ToLocalTime () method because that will use server's timezone.

UPDATE1
I am using the Javascript new Date().getTimezoneOffset() method to get the client's offset, and i have offset value stored on the server. Then I also have drop down list that show times as 12:00 AM, 12.30 AM, 1:00 AM etc etc. The dropdownlist is bound to model property SelectedDateTime of type DateTime. Idea is to convert user selected time to UTC and then UTC to localtime based on the offset. So lets say i have offset 300 minitues that would be 300/60 = 5 hours

double offset = 5.00; // this is available on the server

When the user selects time in a drop down list, I am getting a datetime object on the server, ignoring the date part i want to store UTC time into database. This is how I'm converting to UTC time.

TimeSpan utcTime = SelectedDateTime.AddHours(offset).TimeOfday;

I store this utcTime into the database. Now I want to convert UTC timespan into the client's datetime. I am assuming i have Subtract offset now

var newLocalTimeSpan = utcTime.Subtract(TimeSpan.FromHours(offset));
var newLocalDateTime = new DateTime(newLocalTimeSpan.Ticks, DateTimeKind.Local);

However this throws the error:

Ticks must be between DateTime.MinValue.Ticks and DateTime.MaxValue.Ticks.\r\nParameter name: ticks

For example with offest 5 hours, If user selects 8:00 PM then it will be converted to UTC and will be stored as 01:00:00.0000000 in database. When I retrieve the UTC value from database its '1:00:00 AM'. Then I subtract 5 hours form this TimeSpan which equals to `-4' now and if I pass Ticks to DateTime..i get above error.

NOTES: If you are curious why model property is DateTime instead of TimeSpan thats because i am using Kendo TimePicker which needs DateTime type.

UPDATE 2
I really appreciate all for your help. I have gone through all the articles @Matt Johnson has posted and it looks like I should not be using offset for calculating the UTC time. Mainly because of the day light time saving. But instead I should be using timezone. So I have 3 options here to find client’s time zone:

1> Use JavaScript to detect time zone
In JavaScript I can do new Date().toString() which returns date time as Sun May 22 2016 02:12:36 GMT-0500 (Central Daylight Time) I can then parse the string to get “Central Daylight Time” and post it to the server. However on server, for .net “Central Daylight Time” is not a valid windows time zone ID.
Question
Is this correct approach? Is JavaScript returning IANA zone id? Will it always return IANA zone id?

If JavaScript is returning IANA Id then I can use Matt’s article here to get windows time zone id

2> Use http://momentjs.com/ to detect client’s time zone
Question
Is momentjs returns IANA zone id?
If momentjs return IANA zone id then I can use Matt’s article above to get windows zone id. One of the reason I don’t like this approach is because I have to use 2 third party libraries momentjs and Noda Time

3> Provide user a drop down list using TimeZoneInfo.GetSystemTimeZones() and let the user selects the timezone.
User will select a time and timezone, then on server I will convert it to UTC using selected timezone and save it DB. However I have to show that time on some other pages, So I again need timezone. That means I have to put the drop down list in such a place on UI where it will be available all the time. Like top menu.

(I can certainly save timezone into DB along with the time, however if user travel to other place he will still see time in initially selected time zone. Which I don’t want)

Are these correct approaches? Am i missing something?

Question
Assume that I implement timezone selection using one of the approach above and i have correct client's time zone with windows timezone id on server in some variable.
Now lets say user selects 6:00 PM (Central Daylight Time , UTC -5) which will convert to UTC as 23:00:00. As long we are in Central Daylight Time the conversion from UTC to local will show 6:00 PM. Once we go into Central Standard Time which is UTC -6 Will the conversion still show 6:00 PM or 5:00 PM?
I am planning to use TimeZoneInfo.ConvertFromUtc(datetimevalue, timezone) method for converting UTC to Local

d219
  • 2,707
  • 5
  • 31
  • 36
LP13
  • 30,567
  • 53
  • 217
  • 400
  • can u give me example? – LP13 May 19 '16 at 19:49
  • please see my update 1 above – LP13 May 19 '16 at 20:51
  • Read [the time zone tag wiki](http://stackoverflow.com/tags/timezone/info), especially "time zone != offset". Then do some searching for other similar questions. I'll close this as duplicate later, when I can spend some time time to figure out which is the most appropriate duplicate of the many times this has been asked before. – Matt Johnson-Pint May 20 '16 at 00:07
  • Please dont mark duplicate. Will update my details. Thanks – LP13 May 20 '16 at 17:26
  • 1
    Please read [this](http://stackoverflow.com/a/16351529/634824), [this](http://stackoverflow.com/a/22625076/634824) and [this](http://stackoverflow.com/a/16526897/634824). If you *still* have questions, let me know. I will say that you've really got to understand that converting time without a date doesn't make any sense, because you have to know which offset is in effect. You also cannot just take the offset from one point in time (from the browser) and apply it to any point in time on your server. The offset may have changed. – Matt Johnson-Pint May 21 '16 at 06:01
  • For example, in US Pacific Time, the standard offset is UTC-8, and the daylight offset is UTC-7. Also consider that even standard offsets have changed over time for some time zones, and there are time zones that have four transitions between two or three offsets in a single year. – Matt Johnson-Pint May 21 '16 at 06:04
  • @MattJohnson Thank You very much. Please see my update 2 above – LP13 May 22 '16 at 08:10

2 Answers2

4

In general, there are only two viable approaches:

  1. Pass only UTC dates and times to the client, and do all conversions to local time in the browser using JavaScript.

    • Use this approach when you don't care what the time zone actually is, but you just want it to match the browser's local time.
    • The Date object can do this, but you may find it easier to use a library such as moment.js, which gives you better control of output format, among other things.
  2. Apply a time zone (not just an offset) to the UTC date and time on the server side, to produce the correct local time value.

    • Use this approach when the time zone affects an entire application, and needs to be known in server-side business logic.
    • You can try to guess the user's time zone using jsTimeZoneDetect or moment.tz.guess() in moment-timezone. However, it's just a guess, and it is always an IANA time zone ID (such as America/Los_Angeles).
    • Asking the user for their time zone from a list is a good idea. Usually one would place this on a user settings or profile page. You can use the guess made earlier to pick a default value from the list.
    • You will indeed need to use Noda Time on the server if you are using IANA time zones on the client.
    • Some applications choose to list Windows time zones instead, which is a much simpler approach as you can get everything from the TimeZoneInfo class. However, recognize that there are limitations with this approach including:
      • Localization issues, as you cannot easily get at display name strings other than the ones matching the operating system's default language, not .NET's globalization and localization features.
      • Maintainability issues, as you yield control to the operating system for keeping the time zone data updated. This may seem more convenient, but you may find that your hands are tied when keeping up with short-notice time zone changes. This is especially problematic when you don't have control over how or when updates are applied to the OS, such as with Microsoft Azure App Service.
      • Compatibility issues, as Windows time zones aren't generally recognized outside of Windows. If you ever expose the user's time zone setting in an API, you'll likely have translation issues for callers from other platforms.

Now, getting to your specific points:

I am using javascript new Date().getTimezoneOffset() method to get the client's offset...

That gives you the client's current offset. You have no guarantees that it is the correct time zone to apply for an arbitrary date and time.

If wanted to apply a fixed offset to a UTC DateTime in C#, the best way is with a DateTimeOffset.

DateTime utc = new DateTime(2016, 12, 31, 0, 0, 0, DateTimeKind.Utc);
DateTimeOffset dto = new DateTimeOffset(utc); // DateTimeKind matters here
TimeSpan offset = TimeSpan.FromMinutes(-300); // The offset is inverse of JavaScript's
DateTimeOffset result = dto.ToOffset(offset);

But do note this is only for a fixed time zone offset. For a true time zone, you would use the TimeZoneInfo class if you're using Windows time zones, or you would use NodaTime's DateTimeZone class for IANA time zones.

In JavaScript I can do new Date().toString() which returns date time as Sun May 22 2016 02:12:36 GMT-0500 (Central Daylight Time) I can then parse the string to get "Central Daylight Time" and post it to the server.

No, this approach is not recommended, for several reasons:

  • There's no guarantee you will get output in any particular format from JavaScript's toString function. The results are implementation specific, and will vary across browsers and platforms.

  • They are generally intended for display purposes. When DST is in effect, they'll show a daylight name, and when standard time is in effect they'll show a standard name.

  • They are often localized for the user's language, English, French, Chinese, etc.

The only native API that can return the user's time zone is:

Intl.DateTimeFormat().resolvedOptions().timeZone

This is part of the ECMAScript Internationalization API. Unfortunately, it currently only works in a handful of browsers. Both jsTimeZoneDetect and moment.tz.guess() will use this API if it's available, then will fall back to their own guessing logic if not.

Assume that i implement timezone selection using one of the approach above and i have correct client's time zone with windows timezone id on server in some variable. Now lets say user selects 6:00 PM (Central Daylight Time , UTC -5) which will convert to UTC as 23:00:00. As long we are in Central Daylight Time the conversion from UTC to local will show 6:00 PM. Once we go into Central Standard Time which is UTC -6 Will the conversion still show 6:00 PM or 5:00 PM? I am planning to use TimeZoneInfo.ConvertFromUtc(datetimevalue, timezone) method for converting UTC to Local

As you said earlier, "Central Daylight Time" is not a valid Windows time zone identifier. Your user wouldn't pick that. You'd display a list generated from TimeZoneInfo.GetSystemTimeZones(), showing the DisplayName to the user, and using the Id for the value. The Id would be "Central Standard Time", which indeed is the correct identifier for US Central Time, inclusive of both CST and CDT - despite having the word "Standard" in the string.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • I'll also point you at [this youtube video](https://www.youtube.com/watch?v=2BdFg5JT9lg), which covers some of these concerns. – Matt Johnson-Pint May 23 '16 at 03:08
  • Thank You very much. That helped making my decision. – LP13 May 23 '16 at 14:02
  • `Asking the user for their time zone from a list is a good idea. Usually one would place this on a user settings or profile page. You can use the guess made earlier to pick a default value from the list.` However if im showing windows timezones in the dropdownlist..i am assuming i cannot use momentjs's guess to select default value because there is no 1-1 mapping with windows zone id. That means i still have to use noda. Is that correct assumption? – LP13 May 23 '16 at 14:24
  • 1
    Correct. You'd have to make a guess of IANA zone, then translate it to the Windows zone using Noda Time. And if you have Noda Time, then you might as well use IANA zones in the first place. Maybe eventually we'll add support for Windows zones to moment-timezone, but generally it's preferred to use IANA zones. (Note that even Windows itself is working to move towards IANA zones, as evidenced by WinRT APIs such as `Windows.Globalization.Calendar` and `Windows.Globalization.DateTimeFormatting.DateTimeFormatter`.) – Matt Johnson-Pint May 23 '16 at 15:37
0

You need to convert the TimeSpan to a DateTime, using the current Year, Month and Day. If you subtract from a TimeSpan without doing so, it can result in an unobtainable date.
Also, I noticed in your update that you left the results in a DateTime, so I did the same. This code is showing you the time if the UTC time was 1:00 AM, as your problem states.

        double offset = 5.00;
        TimeSpan utcTime = new TimeSpan(1,0,0); //setting manually to your representation of 1 am.
        DateTime newLocalDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, utcTime.Hours, utcTime.Minutes, utcTime.Seconds);
        newLocalDateTime = newLocalDateTime.Subtract(TimeSpan.FromHours(offset));
Joe
  • 52
  • 8
  • i dont have datetime..I have timespan which has UTC time – LP13 May 19 '16 at 19:50
  • I edited my answer. TimeSpan s is a representation of your TimeSpan you are returned. Note that the TimeSpan.FromMinutes could be FromSeconds, or whatever fits your project the best. The value that you pass can be negative, as well, which would subtract time from the time span. That said, I'm a little confused as to why you would be returning a timespan to represent the current server time. But, this should help. – Joe May 19 '16 at 19:55
  • Let me explain time span and date time a little bit. TimeSpan is a representation of an amount of time. Such as 5 hours (5:00.0). DateTime is a representation of a specific time 5/19/2016 5:36 pm. I think what you are missing is that 1:00.0 is a representation of an hour, not really 1 am. When you're trying to go before that time, it doesn't roll back to the previous day as a datetime object would. We could resolve this, though, by creating a datetime object from that TimeSpan, then advancing or subtracting the time, and converting back to a timespan. – Joe May 19 '16 at 21:37
  • All right. I pretty heavily edited my answer. I've demonstrated creating a DateTime object to allow you subtract the offset. My example shows manually setting the TimeSpan you start with to 1:00am, which was the problem you were running into. I think this resolves your issues fairly well, though I still would say it is best to keep the UTC time as DateTime so that you're not having to convert back like this. But it will be required if you're going to use a TimeSpan like that. Let me know if you need anything else, or more explanation. – Joe May 19 '16 at 22:05
  • You really don't want to be doing any addition or subtraction to account for time zone. Take a look at `DateTimeOffset`, `TimeZoneInfo` and [Noda Time](http://nodatime.org) – Matt Johnson-Pint May 20 '16 at 00:11