11

I'm storing the user's timezone as a decimal in the session. For instance, if the user is in the EST timezone, I'd have

UserTimeZone = -5.00;

The data in the database is stored in UTC, so I want to calculate the beginning and the end of the day for that user so that when the user wants his data for a specific day, the records are timezone adjusted.

This is what I'm doing:

DateTime StartDate =  DateTime.Now.ToUniversalTime();

StartDate = StartDate.AddHours((double)UserTimeZone);
StartDate = StartDate.Date;
StartDate = StartDate.AddHours((double)UserTimeZone);

DateTime EndDate = StartDate.AddHours(24);

The problem I'm having is that this doesn't account for daylight saving time so even thought EST time is 5 hours behind UTC, for the moment it's actually 4 hours behind UTC because of the daylight saving shift.

What are some of your suggestions? Thanks.

frenchie
  • 51,731
  • 109
  • 304
  • 510
  • 4
    I don' think just having a time offset is enough, look into `TimeZone` / `TimeZoneInfo` – BrokenGlass Sep 11 '11 at 23:08
  • 1
    Don't use DateTime.Now.ToUniversalTime(), use DateTime.UtcNow – Ian Mercer Sep 12 '11 at 06:22
  • 2
    Not all time zones are integer offsets, so even when you switch to getting the offset from the browser you'll need to use something better than an int. – Ian Mercer Sep 12 '11 at 06:23
  • 1
    I'm not using an int, I'm using a decimal. I checked and I didn't find any difference between ToUniversalTime() and DateTime.UtcNow, even when changing the computer's date to some time in December. – frenchie Sep 12 '11 at 20:55
  • using a Decimal is good but not enough... are you storing only this offset per User ? that is not enough - you need to store whether this offset includes daylight saving or not... with both (offset and whether offset includes DS) one can build a working algorithm (although not 100% consistent - best is to store a real Timezone per User!), otherwise NOT :-( – Yahia Sep 16 '11 at 23:58
  • Yes, at the moment, I'm only storing the timezone offset. If the user tells me he's in the EST timezone, is there a good way to know if he's at UTC-5 or UTC-4 without knowing anything else? – frenchie Sep 17 '11 at 01:45
  • Hi! I was glad to be of help. Please consider awarding the bounty if the accepted answer is satisfactory, since the system will let it expire. – Jon Sep 23 '11 at 22:52

8 Answers8

11

To make such calculations you will need to use the TimeZoneInfo and DateTimeOffset classes.

First of all, we need to get a TimeZoneInfo instance for both the local time and the user's local time:

var localTimezone = TimeZoneInfo.Local;
var userTimezone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

The caveat here is that you need to know the id of the user's local timezone (the offset that you currently have is not going to be enough). You can get a list of all TimeZoneInfo instances that the system recognizes with TimeZoneInfo.GetSystemTimeZones, and then you need a way of matching the user-provided timezone with one of those.

For this example, I have hardcoded EST.

Then you need to get a DateTimeOffset instance for today's midnight (start of day) in your local timezone:

var todayDate = DateTime.Today;
var todayLocal = new DateTimeOffset(todayDate,
                                    localTimezone.GetUtcOffset(todayDate));

Given this, you can calculate the DateTimeOffset instance that represents the midnight of "today" (based on your local time) in the user's timezone. Be aware that, depending on timezones, this may actually be in the future for the user's timezone!

var todayUser = TimeZoneInfo.ConvertTime(todayLocal, userTimezone);

And finally, you can create timestamps for both dates like this:

var epochStart = DateTime.Parse("01/01/1970 00:00:00");
var todayLocalTs = (todayLocal.Ticks - epochStart.Ticks)/TimeSpan.TicksPerSecond;
var todayUserTs = (todayUser.Ticks - epochStart.Ticks) / TimeSpan.TicksPerSecond;
Jon
  • 428,835
  • 81
  • 738
  • 806
  • Also, avoid using "EST", it and other TZ abbreviations are not globally unique :( Either use the UTC offset, or use the full timezone name ("Ameria/NewYork"). More info in this SO question: http://stackoverflow.com/questions/4309030/difference-between-timezones-america-los-angeles-and-us-pacific-and-pst8pdt – Jason Peacock Sep 23 '11 at 23:05
  • @jpeacock: .NET is picky with its timezone names however (you do need to see them with `GetSystemTimeZones`). Also, you cannot use the UTC offset -- that's what the OP is doing right now, and it's not satisfactory. – Jon Sep 23 '11 at 23:18
  • @frenchie: Original Poster -- you. :) – Jon Sep 25 '11 at 13:03
3

As BrokenGlass mentioned, a simple offset is not enough information to determine the handling of daylight hours, since different countries in each zone might handle daylight savings differently. The C# TimeZone class is more specific, and has support for daylight savings (check details on MSDN). Unfortunately there is no easy way to get the relevant timezone from the browser, but there are several suggestions on this post regarding how you can allow the user to pick their timezone.

If you want to try and work out the timezone without the user's assistance, there are a few ways to do that (typically revolving around getting the browser's preferred language, and then mapping that to a country...), some examples are here and here.

Community
  • 1
  • 1
Daniel B
  • 2,877
  • 18
  • 18
  • The user provides his timezone at the time of registration; what i need is a way to determine if his timezone is under daylight saving. – frenchie Sep 12 '11 at 18:38
  • Have a look at [this](http://en.wikipedia.org/wiki/Daylight_saving_time) - bottom line, your definition of timezone information is not sufficient to determine daylight savings. You want them to provide you with their *country*, as well as their time zone within that country. – Daniel B Sep 13 '11 at 06:14
  • Daniel is correct, especially since a time zone spans from north to south, yet daylight saving times in the north and south hemisphere are at exactly opposite times. – Andreas Sep 17 '11 at 19:59
3

You will need to use JavaScript to gather the necessary information from the user's browser - for this part see http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/

When you have this information you can setup UserTimeZone (btw this should NOT be an int as there are timezones with fractions of hours!) to accomodate the current timezone including DST...

Yahia
  • 69,653
  • 9
  • 115
  • 144
  • The user provides his timezone at the time of registration; what i need is a way to determine if his timezone is under daylight saving. – frenchie Sep 12 '11 at 18:38
  • If you follow the link it provides a way to find out whether the current timezone of the user is under daylight saving! – Yahia Sep 12 '11 at 20:16
  • Javascript is not the way I'm looking to do it. The user provides me with his timezone (and address too) at registration time and I need to figure out the time at which his day starts with consideration to the daylight saving time. He could be from California and travelling to the East Coast, with a browser showing EST at an internet cafe. – frenchie Sep 12 '11 at 20:49
  • whether this is possible depends absolutely on what the user provides as "timezone information" - if you show that (code, data, whatever "timezone information" you store with the user) then it is at least possible to try to find a way... – Yahia Sep 12 '11 at 21:00
2

The accepted answer doesn't consider that there are time zones that have DST spring-forward transitions right at midnight, and thus the start of the day might not be 00:00 but rather 01:00. Iran, Cuba, and Brazil (some parts) are good examples.

Additionally, some time zones may have fall-back transitions that give two possible point in time that are midnight.

Consider the following function, which accommodates both scenarios:

using System.Linq;

static DateTimeOffset GetStartOfDay(DateTime dt, TimeZoneInfo tz)
{
    // Work in the time zone provided
    if (dt.Kind != DateTimeKind.Unspecified)
    {
        dt = TimeZoneInfo.ConvertTime(dt, tz);
    }

    // Start with assuming midnight
    var d = dt.Date;

    // Check for the time being invalid and handle if so
    if (tz.IsInvalidTime(d))
    {
        // the gap is *usually* 1hr, but not always, so calculate it
        var gap = tz.GetUtcOffset(dt.AddDays(1)) - tz.GetUtcOffset(dt.AddDays(-1));

        // advance forward by the amount of the gap
        d = d.Add(gap);
    }

    // Also check for the time being ambiguous, such as in a fall-back transition.
    // We want the *first* occurrence, which will have a *larger* offset
    var offset = tz.IsAmbiguousTime(d)
        ? tz.GetAmbiguousTimeOffsets(d).OrderByDescending(x => x).First()
        : tz.GetUtcOffset(d);

    // Now we know when the date starts precisely
    return new DateTimeOffset(d, offset);
}
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • 1
    Thanks for this. An example of the ambiguous case is that of Cuba, where they put the clocks back from 01:00 to 00:00 in Autumn: https://www.timeanddate.com/time/change/cuba/havana?year=2018 – Quppa Dec 10 '18 at 03:59
2

The correct answer is what everyone has told you to do - use the TimeZone APIs in the framework. But prior to .NET 3.5 the TimeZoneInfo APIs didnt exist. If you really don't want to use the APIS, or you are using something prior to .NET 3.5, you can find all the timezone info in the registry at

HKLM/Software/Microsoft/Windows NT/CurrentVersion/Timezones

There are a set of classes available at http://www.michaelbrumm.com/simpletimezone.html that read the registry data directly and do all the timezone calculations required - with adjustments for DST. It's good code (we've used it reliably for years) with source, so you can see what they are actually doing.

ScottTx
  • 1,483
  • 8
  • 12
2

I would recommend adding a timezone to the users settings and storing that. Timezones have different times depending on the time of year. you can provide a list of time zones for the user to choose from by using the TimeZoneInfo.GetSystemTimeZones method. You can store any date as UTC and convert it (with the TimeZoneInfo.ConvertTime method) to the users time when displaying, and convert it back to UTC when saving it. This will allow the user to change their timezone at any time without causing problems. If you follow this format you should not run into any problems.

Be forewarned, if you do not store the dates as UTC and convert as suggested above you may run into problems. In some time zones certain times do not exist on certain days when changing from daylight savings time to standard time. The TimeZoneInfo class does not play nice with these non-existent times.

Charles Lambert
  • 5,042
  • 26
  • 47
1

You shouldn't be doing any of this. You should just use the TimeZoneInfo built into .net.

For example:

TimeZoneInfo.ConvertTimeToUtc();
TimeZoneInfo.ConvertTimeFromUtc();

Since you don't seem able to lookup API Parameters, here you go:

http://msdn.microsoft.com/en-us/library/system.timezoneinfo.converttimefromutc.aspx

http://msdn.microsoft.com/en-us/library/bb381744.aspx

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • And where do I pass the timezone info. I'm looking to get the user's beginning-of-day time (StartTime) so that I can put that time in my queries. I'm looking for something like StartTime = function GetStartTime(Date, UserTimeZone). – frenchie Sep 17 '11 at 23:19
  • @frenchie - pardon me for assuming you would actually look at the API reference to see the arguments to pass to it. So silly of me to think you would do that. – Erik Funkenbusch Sep 17 '11 at 23:24
  • right, so the problem is not in converting time zones to UTC and back, the problem is that if given a timezone offset (ie -5.00 for EST), how do I know if the EST timezone is -5 hours from UTC or -4 hours from UTC in case of day light saving time. The goal is to determine the UTC time of the start of the user's day. Some days it'll be UTC -5 and others it'll be UTC-4, depending on the daylight saving time. If the user wants to see data for HIS september 18th, I need to know what the start time is in UTC format AND taking daylight saving time into consideration. – frenchie Sep 18 '11 at 04:47
  • 1
    @frenchie - That's the point. You don't have to if you use the TimeZoneInfo functions. It figures that out for you. You just have to pass it the right TimeZoneInfo, which you save in your users profile when they register. Forget about offsets, they are unimportant. – Erik Funkenbusch Sep 18 '11 at 07:55
  • Where do I find the list of all timezones supported by this class? This page gives a list by OS but how do I find the list based on the server's OS: http://msdn.microsoft.com/en-us/library/bb384272.aspx – frenchie Sep 20 '11 at 04:59
  • @frenchie - it supports all time zones supported by the OS. This is a user definable thing (although most users won't be bothered to create custom time zones). If you would read the documentation, you would see a method called `GetSystemTimeZones()` which returns an enumeration of all time zones on the system. http://msdn.microsoft.com/en-us/library/system.timezoneinfo.getsystemtimezones.aspx – Erik Funkenbusch Sep 20 '11 at 06:51
0

You need time-zone file http://www.twinsun.com/tz/tz-link.htm
Record all you time in UTC/GMT.

Day Light saving is not consistent, countries change their DLS rule.
Hence your application will need up-to-date Tz file at all the time.

Your user should choose Time-Zone name when signing up and not time-offset. Because two regions can have same time offset (sometimes with DLS).

With Tz Name you can know
- what country/region user belongs to;
- what is his time offset;
- and what is his DLS offset,
add these both to get users current time.

Known Issue: Time in history cannot be converted to actual time if you don't have historic DLS details, as these may not be valid at that historic time.

Atul Gupta
  • 725
  • 1
  • 6
  • 20
  • Your server will have this file up-to-date already; and your APIs will give DLS adjusted time if you pass on Tz name instead of time-offset. – Atul Gupta Sep 22 '11 at 20:29