2

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()

Community
  • 1
  • 1
James
  • 75,060
  • 17
  • 154
  • 220
  • 1
    Can't you perform the calculations on the UTC date/time before converting the result to local time? – dtb Apr 15 '10 at 17:18
  • Does it matter if they are local or not? If the DateTimeOffset only gives me an instant in time, are any of the calculations going to be accurate? – James Apr 15 '10 at 17:32
  • 3
    You are really missing the point of UTC. It is the same anywhere in the world. *Don't convert*. – Hans Passant Apr 15 '10 at 18:18
  • @Hans: My point is - Say I have a date/time in UK, this date/time is not the same in different timezones. So say it was for Aussie, I need to convert the UTC time to local time in order to do calculations on it (as a date of `15/04/2010 18:00` in UK time results in something like `16/04/2010 04:00` Aussie time) – James Apr 15 '10 at 18:29

2 Answers2

6

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.

Guffa
  • 640,220
  • 96
  • 678
  • 956
  • @Guffa: So when storing a date/time (for say Aussie) is it safe to convert that UTC time to the specific timezone equivalent before saving? Does this still represent UTC time? (I don't mean using `ToLocalTime()` I mean something like `ConvertTimeBySystemTimeZoneId`. – James Apr 15 '10 at 17:43
  • @Guffa: I am not actually assuming anything :) – James Apr 15 '10 at 17:49
  • @James: No, when you have converted the value to a time zone, it's no longer UTC time. However, if you are using DateTimeOffset it's offset will be changed accordingly, so you can convert it back to the original UTC time. The DateTimeOffset value contains the offset from the UTC time, but not the time zone, so while converting it back to UTC is safe, calculations on DateTimeOffset values doesn't take daylight savings time into account. If you concvert to UTC, do the calculation and convert back, the calculation will be correct. – Guffa Apr 15 '10 at 18:15
  • @James: The recieved date in the mail has a time offset, so it corresponds to a DateTimeOffset value. If you can successfully parse it to a DateTimeOffset value, you can safely convert it to UTC, and save a DateTime value as an unambiguous point in time. From that you can convert it to any time zone so that you can display it in anyones local time if you like. – Guffa Apr 16 '10 at 19:46
  • @Guffa thanks I think I am finally beginning to understand all this! Just one more thing tho. Sometimes I need to parse dates sent in from the users themselves (for example in the body of an email). I would have to treat this date as Local time. How would I store that as UTC? Would I need to get the time offset (based on the timezone) and then convert it to `DateTimeOffset` and then UTC datetime? – James Apr 18 '10 at 11:17
  • @James: To convert the date to UTC you need either a time zone (DateTime + TimeZoneInfo) or a time offset (DateTimeOffset). For time zone conversion you use the `TimeZoneInfo.ConvertTimeToUtc` method. For time offset conversion you use the `DateTimeOffset.ToUniversalTime` method. – Guffa Apr 18 '10 at 13:27
  • Thanks Guffa, finally got my application working as expected! – James Apr 18 '10 at 22:59
  • I wish I could upvote this 20 times, just for this: `If you do the calculation before converting the DateTime/DateTimeOffset value to local time, the result will always be correct.` That just saved me having to deal with daylight savings. – Bob Horn Sep 18 '12 at 20:53
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());
    
chilltemp
  • 8,348
  • 8
  • 38
  • 45
  • @chilltemp: Should I store it as server timezone UTC or local timezone UTC? – James Apr 15 '10 at 17:57
  • @James: I don't understand what you mean between server and local timezone UTC. UTC is UTC, there are no variations. – chilltemp Apr 15 '10 at 18:12
  • 2
    @James - If you don't know whether to use a server's time or a client's time, then always use the time on the server. (In any case it should be UTC as @chilltemp says.) Using the time on the server makes the data consistant for all users, and it is better for security, since you generally don't control the client's time settings. – Jeffrey L Whitledge Apr 15 '10 at 18:23
  • @James: @Jeffery is correct. Server time is best for constancy. The exception would be offline clients. You will need to use the client's time (as UTC) if the client does not have an active channel to the server, and will be communicating the data at a later time (such as batch communications). – chilltemp Apr 15 '10 at 19:25