开发者

determining the beginning of a day with timezones AND daylight saving

开发者 https://www.devze.com 2023-04-04 08:12 出处:网络
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

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 havin开发者_StackOverflow中文版g 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.


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;


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.


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...


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.


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.


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);
}


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


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消