Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove DST from DateTime

I've seen that many websites (usually forums) allow users to specify their TimeZone preferences by selecting:

  • the TimeZone
  • whether or not to use DST

As far as I know, when doing conversions, .NET always takes DST into account, so the question is:

How can the "do not use DST" part be implemented in C#?

Below this point I present what I managed to do so far, but it feels hacky and I was wondering if there was a cleaner/better approach.

First, to make sure that DST is applied automatically, I wrote the following test:

[Test]
public void DateTimeConversion_ToLocalTime_HandlesDSTByDefault()
{
    var utcDateInDstInterval = new DateTime(2012, 07, 15, 0, 0, 0, DateTimeKind.Utc);
    var utcDateOutisdeDstInterval = new DateTime(2012, 02, 15, 0, 0, 0, DateTimeKind.Utc);

    var roTimezone = TimeZoneInfo.FindSystemTimeZoneById("GTB Standard Time");

    Assert.AreEqual(3, TimeZoneInfo.ConvertTimeFromUtc(utcDateInDstInterval, roTimezone).Hour);
    Assert.AreEqual(2, TimeZoneInfo.ConvertTimeFromUtc(utcDateOutisdeDstInterval, roTimezone).Hour);
}

This test passes, showing that:

  • when no DST applies (for example in winter) the Romanian time is UTC + 2 hours
  • when DST applies (for example in summer) the Romanian time is UTC + 3 hours
  • when doing DateTime conversions, DST is automatically taken into account

Next, I noticed the TimeZoneInfo.GetAdjustmentRules() method that returns an array of AdjustmentRule objects and figured out that if I undo the effects of these rules I could get the DST-unaffected value.

So I wrote the following method that does this if the DateTime object is affected by DST:

private DateTime RemoveDSTFromDateTime(DateTime dateTime, TimeZoneInfo timeZoneInfo)
{
    if (!dateTime.IsDaylightSavingTime())
        return dateTime;

    var result = dateTime;

    foreach (var adjustmentRule in timeZoneInfo.GetAdjustmentRules())
        result = result.Subtract(adjustmentRule.DaylightDelta);

    return result;
}

Back to the original DST/No DST scenario, but this time forcing the result to be unaffected by DST:

[Test]
public void DateTimeConversion_ToLocalTime_WithoutDST()
{
    var utcDateInDstInterval = new DateTime(2012, 07, 15, 0, 0, 0, DateTimeKind.Utc);
    var utcDateOutisdeDstInterval = new DateTime(2012, 02, 15, 0, 0, 0, DateTimeKind.Utc);

    var roTimezone = TimeZoneInfo.FindSystemTimeZoneById("GTB Standard Time");

    var convertedDateWithDst = TimeZoneInfo.ConvertTimeFromUtc(utcDateInDstInterval, roTimezone);
    var convertedDateWithoutDst = TimeZoneInfo.ConvertTimeFromUtc(utcDateOutisdeDstInterval, roTimezone);

    Assert.AreEqual(2, RemoveDSTFromDateTime(convertedDateWithDst, roTimezone).Hour);
    Assert.AreEqual(2, RemoveDSTFromDateTime(convertedDateWithoutDst, roTimezone).Hour);
}

This test also passes, showing that now the effect of DST is cancelled (we always get UTC + 2h, regardless of the time of the year).

While writing this down I got another idea that appears to work: instead of using any TimeZoneInfo.Convert...() methods, simply add roTimezone.BaseUtcOffset to the UTC date.

Can anyone indicate what is the right way to do this?

like image 712
Cristian Lupascu Avatar asked Sep 04 '12 15:09

Cristian Lupascu


3 Answers

Parsing your question, it seems you have two proposed solutions:

  1. Determine the DST adjustment and remove it from the output post-conversion.

  2. Take the UTC DateTime and add the BaseUtcOffset so you are always dealing with standard time.

Since your question was how to not implement DST, I would say you answered it yourself with option #2.

That said, your initial assumption is flawed. The reason many sites ask for time zone and DST is usually because their time zone implementation doesn't support DST to begin with. What they are doing is having you pick a base offset and decide whether or not you want an hour added during a set time of year. They may call it a "time zone", but it is in no way as robust as either .Net's TimeZoneInfo classes or libraries like Noda Time.

If you are truly picking a "time zone", such as by TimeZoneInfo.GetSystemTimeZones() (you should be saving the Id property and showing the DisplayName property) then you absolutely should NOT ask the user for whether or not to use DST. If the user lives somewhere where DST is not used (like in Arizona), there is already a selection for that. Users are usually pretty good about picking the right selection to get the effect they want, especially if they live in an area with weird DST rules (like Indiana).

The same idea would apply if you are using IANA/Olson style time zones like "America/New York", whether they are provided by Noda Time or some other implementation. You should entrust the library with the DST decision - not the user.

One very valid argument for entrusting this to a library is that these DST rules can change, and have changed at many points in their lifetime. If you are writing any kind of application or site that deals with information in the past, you may find that what is valid today is not the correct adjustment for what happened in the past. Also, there is a lot of weirdness in the world of time zones, like Australia where certain areas are offset by 30 minutes instead of a full hour. Do you really want to manage this yourself? Probably not. :)

like image 180
Matt Johnson-Pint Avatar answered Oct 10 '22 12:10

Matt Johnson-Pint


I recently did this for removing the DST for Arizona when calculating its current time remotely. In my case, I had a DST (y/n) flag in a database by ZIP Code. For anyone making use of the above code (sample #2) for removing the DST adjustment, you may want to qualify the timeframe for the adjustment you intend to apply since some adjustments are present but expired (and I presume can be future dated when a change is coming). Here is an example of what worked for me:

private DateTime RemoveDSTFromDateTime(DateTime dateTime, TimeZoneInfo timeZoneInfo)
{
    #region detail...
    //removes daylight savings time adjustment as defined in current year
    int _currYear = DateTime.Now.Year;

    if (!dateTime.IsDaylightSavingTime())
        return dateTime;

    var result = dateTime;

    foreach (var adjustmentRule in timeZoneInfo.GetAdjustmentRules())
    {
        if (adjustmentRule.DateStart.Year <= _currYear && adjustmentRule.DateEnd.Year >= _currYear)
        {
            result = result.Subtract(adjustmentRule.DaylightDelta);
        }
    }

    return result; 
    #endregion
}
like image 20
puddleglum Avatar answered Oct 10 '22 13:10

puddleglum


One suggestion that I have is to create a custom TimeZoneInfo and use that to generate the DateTime object

myTimeZoneInfo = TimeZoneInfo.CreateCustomTimeZone(
                 TimeZoneInfo.Local.Id,
                 TimeZoneInfo.Local.BaseUtcOffset,
                 TimeZoneInfo.Local.DisplayName,
                 TimeZoneInfo.Local.StandardName, "", null, true);

The key parameter is the true, which is disableDaylightSavingsTime.

Usage:

DateTime noDaylightSavings = TimeZoneInfo.ConvertTimeFromUtc(reading, myTimeZoneInfo);

or

DateTime noDaylightSavings = TimeZoneInfo.ConvertTimeToUtc(reading, myTimeZoneInfo);
like image 30
Alex Avatar answered Oct 10 '22 11:10

Alex