Safely convert UTC datetimes to local time (based on TZ) for calculations?

10,332

Solution 1

No, you can't assume that. UTC time is completely linear, and you can safely do calculations on them. Once you converted it to a local time, it's no longer completely linear.

When daylight savings time changes occur, there is an overlap or gap in the local time. If you do a calculation that spans over a daylight savings time change, the result will be off by one hour (if that is how much the time changes, which is the most common).

If you do the calculation before converting the DateTime/DateTimeOffset value to local time, the result will always be correct. Note however that converting the value to a local DateTime value can make it ambiguous, if the value happens to fall inside an overlap at the daylight savings time change, it's impossible to tell if it's the first or second time that exact time occurs that day.

Solution 2

The safest way to handle date/time correctly is to store everything as UTC and display it in local time. All date/time math should be done in UTC as Guffa suggests. Store in UTC and convert to local time on the fly as you display it.

How to make a Time Zone aware date/time

Microsoft has an article on how to encapsulate a DateTime and TimeZoneInfo variable into a structure here.

Here's Microsoft's sample structure with 1 property added to easily get local time. This needs more work to be a fully useful, but it's a good start.

public struct TimeZoneTime
{
   public TimeZoneInfo TimeZone;
   public DateTimeOffset Time;

   public TimeZoneTime(DateTimeOffset time)
   {
      this.TimeZone = TimeZone.Local;
      this.Time = time;   
   }

   public TimeZoneTime(TimeZoneInfo tz, DateTimeOffset time)
   {
      if (tz == null) 
         throw new ArgumentNullException("The time zone cannot be a null reference.");

      this.TimeZone = tz;
      this.Time = time;   
   }

   public TimeZoneTime AddTime(TimeSpan interval)
   {
      // Convert time to UTC
      DateTimeOffset utcTime = TimeZoneInfo.ConvertTime(this.Time, TimeZoneInfo.Utc);      
      // Add time interval to time
      utcTime = utcTime.Add(interval);
      // Convert time back to time in time zone
      return new TimeZoneTime(this.TimeZone, TimeZoneInfo.ConvertTime(utcTime, this.TimeZone));
   }

    public DateTime LocalDate 
    {
        get { return Time.ToOffset(TimeZone); }
    }
}

Your Scenario

  1. Yes, use either the mail object's ReceivedTime or SentOn and convert it to UTC for storage & calculations. This is much less complex than the samples above.

    Message msg = new Message();
    DateTime received = msg.ReceivedTime.ToUniversalTime();
    received.AddDays(7);
    Console.WriteLine(received.ToLocalTime());
    
Share:
10,332
James
Author by

James

Updated on June 30, 2022

Comments

  • James
    James almost 2 years

    Following from my last question which @Jon Skeet gave me a lot of help with (thanks again!)

    I am now wondering how I can safely work with date/times, stored as UTC, when they are converted back to Local Date/Time.

    As Jon indicated in my last question using DateTimeOffset represents an instant in time and there is no way to predict what the local time would be say a minute later. I need to be able to do calculations based on these date/times.

    So how can I assure when I pull the dates from the database, convert them to local date/time and do specific calculations on them they are going to be accurate?

    Scenario

    My application records information sent in via email. The date/time the email is received is recorded as the submission time. Emails are pulled from exchange.

    What I need to know is:

    1) If these emails are coming from different countries, do I just convert the Recieved date/time of the email to UTC format and store that? e.g. Email.Received.ToUniversalTime()