Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are adding weeks, months or years to a date/time independent from time zone?

My software displays date/time using local time and then send it to server in UTC. On the server-side I want to add months, years, weeks, days etc to this date/time. However, the question is, if I use such methods with UTC date/time and then convert it back to local time, would the result be always the same, as if I use this methods with local time directly?

This is an example in C#:

// #1
var utc = DateTime.Now.ToUtcTime();
utc = utc.AddWeeks(2); // or AddDays, AddYears, AddMonths...
var localtime = utc.ToLocalTime();

// #2
var localtime = DateTime.Now;
localtime = localtime.AddWeeks(2); // or AddDays, AddYears, AddMonths...

Would the results in #1 and #2 always be the same? Or timezone can influence the result?

like image 326
user1224129 Avatar asked Aug 04 '13 09:08

user1224129


People also ask

How do you read a timestamp with time zones?

To calculate UTC time one has to subtract the offset from the local time, e.g. for "15:00−03:30" do 15:00 − (−03:30) to get 18:30 UTC. Show activity on this post. That timestamp has a timezone offset that is telling you what time it was and the UTC offset. With no offset it becomes 2017-02-03T14:16:59.094-00:00 .

Do dates have timezone?

There's nothing there for time or time zone. It's just a date. While it is true that not everywhere on Earth is on the same date simultaneously (because of time zones), that doesn't mean a date itself has time zone awareness. As a real-world analogy, think of a date as just a square on a calendar.

How do you read timezone offset?

A common way to express a zone offset in field-based formats is with +/- followed by the offset. So for example, Japan is 9 hours ahead of UTC, so you may see a time written as 2016-06-11 05:10+09:00 .

What is date/time offset?

The DateTimeOffset structure represents a date and time value, together with an offset that indicates how much that value differs from UTC. Thus, the value always unambiguously identifies a single point in time.


1 Answers

The answer may surprise you but it is NO. You cannot add days, weeks, months, or years to a UTC timestamp, convert it to a local time zone, and expect to have the same result as if you had added directly to the local time.

The reason is that not all local days have 24 hours. Depending on the time zone, the rules for that zone, and whether DST is transitioning in the period in question, some "days" may have 23, 23.5, 24, 24.5 or 25 hours. (If you are trying to be precise, then instead use the term "standard days" to indicate you mean exactly 24 hours.)

As an example, first set your computer to one of the USA time zones that changes for DST, such as Pacific Time or Eastern Time. Then run these examples:

This one covers the 2013 "spring-forward" transition:

DateTime local1 = new DateTime(2013, 3, 10, 0, 0, 0, DateTimeKind.Local);
DateTime local2 = local1.AddDays(1);

DateTime utc1 = local1.ToUniversalTime();
DateTime utc2 = utc1.AddDays(1);
DateTime local3 = utc2.ToLocalTime();

Debug.WriteLine(local2); //  3/11/2013 12:00:00 AM
Debug.WriteLine(local3); //  3/11/2013 1:00:00 AM

And this one covers the 2013 "fall-back" transition:

DateTime local1 = new DateTime(2013, 11, 3, 0, 0, 0, DateTimeKind.Local);
DateTime local2 = local1.AddDays(1);

DateTime utc1 = local1.ToUniversalTime();
DateTime utc2 = utc1.AddDays(1);
DateTime local3 = utc2.ToLocalTime();

Debug.WriteLine(local2); //  11/4/2013 12:00:00 AM
Debug.WriteLine(local3); //  11/3/2013 11:00:00 PM

As you can see in both examples - the result was an hour off, one direction or the other.

A couple of other points:

  • There is no AddWeeks method. Multiply by 7 and add days instead.
  • There is no ToUtcTime method. I think you were looking for ToUniversalTime.
  • Don't call DateTime.Now.ToUniversalTime(). That is redundant since inside .Now it has to take the UTC time and convert to local time anyway. Instead, use DateTime.UtcNow.
  • If this code is running on a server, you shouldn't be calling .Now or .ToLocalTime or ever working with DateTime that has a Local kind. If you do, then you are introducing the time zone of the server - not of the user. If your users are not in the same time zone, or if you ever deploy your application somewhere else, you will have problems.
  • If you want to avoid these kind of problems, then look into NodaTime. It's API will prevent you from making common mistakes.

Here's what you should be doing instead:

// on the client
DateTime local = new DateTime(2013, 3, 10, 0, 0, 0, DateTimeKind.Local);
DateTime utc = local.ToUniversalTime();
string zoneId = TimeZoneInfo.Local.Id;

// send both utc time and zone to the server
// ...

// on the server
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(zoneId);
DateTime theirTime = TimeZoneInfo.ConvertTimeFromUtc(utc, tzi);
DateTime newDate = theirTime.AddDays(1);
Debug.WriteLine(newDate); //   3/11/2013 12:00:00 AM

And just for good measure, here is how it would look if you used Noda Time instead:

// on the client
LocalDateTime local = new LocalDateTime(2013, 3, 10, 0, 0, 0);
DateTimeZone zone = DateTimeZoneProviders.Tzdb.GetSystemDefault();
ZonedDateTime zdt = local.InZoneStrictly(zone);

// send zdt to server
// ...

// on the server
LocalDateTime newDate = zdt.LocalDateTime.PlusDays(1);
Debug.WriteLine(newDate); // 3/11/2013 12:00:00 AM
like image 169
Matt Johnson-Pint Avatar answered Sep 27 '22 22:09

Matt Johnson-Pint