Reputation: 33
I'm working in a simple application to convert some Unix Timestamp dates to localtime. I'm printing both, UTC time and "E. South America Standard Time" -> (GMT-03:00) Brasilia. The code below runs fine, but seems to mess things with DST:
public static void Main (string[] args)
{
long[] timestamps = {1413685800L, 1413689400L, 1424568600L, 1424572200L, 1424575800L};
string formatUtc = "{0:dd MMM yyyy HH:mm:ss}";
string formatLocal = "{0:dd MMM yyyy HH:mm:ss z}";
TimeZoneInfo tzBr = null;
tzBr = TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time");
DateTime dt;
Console.WriteLine("UTC\t\t\t\tAmerica/Sao_Paulo");
Console.WriteLine("---------------------------------------------------------");
foreach (long ts in timestamps) {
dt = new DateTime(1970,1,1,0,0,0,0,System.DateTimeKind.Utc).AddSeconds(ts);
Console.Write(string.Format(formatUtc, dt));
dt = TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Utc, tzBr);
Console.WriteLine("\t\t" + string.Format(formatLocal, dt));
}
}
I've tested this code in three different machines getting the following results:
Windows 7 (.Net):
UTC America/Sao_Paulo
---------------------------------------------------------
19 out 2014 02:30:00 18 out 2014 23:30:00 -3
19 out 2014 03:30:00 19 out 2014 01:30:00 -2
22 fev 2015 01:30:00 21 fev 2015 23:30:00 -3 <- Wrong!
22 fev 2015 02:30:00 21 fev 2015 23:30:00 -3
22 fev 2015 03:30:00 22 fev 2015 00:30:00 -3
Another Windows 7 box (.Net):
UTC America/Sao_Paulo
---------------------------------------------------------
19 out 2014 02:30:00 -3 18 out 2014 23:30:00 -3
19 out 2014 03:30:00 -3 19 out 2014 01:30:00 -3 <- Wrong!
22 fev 2015 01:30:00 -3 21 fev 2015 23:30:00 -3 <- Wrong!
22 fev 2015 02:30:00 -3 21 fev 2015 23:30:00 -3
22 fev 2015 03:30:00 -3 22 fev 2015 00:30:00 -3
Linux Fedora 22 (Mono):
UTC America/Sao_Paulo
---------------------------------------------------------
19 out 2014 02:30:00 18 out 2014 23:30:00 -3
19 out 2014 03:30:00 19 out 2014 01:30:00 -2
22 fev 2015 01:30:00 21 fev 2015 22:30:00 -2 <- Wrong!
22 fev 2015 02:30:00 21 fev 2015 23:30:00 -2 <- Wrong!
22 fev 2015 03:30:00 22 fev 2015 00:30:00 -3
Expected results, from Java app (BRT means -3 and BRST means -2):
UTC America/Sao_Paulo
---------------------------------------------------------
19 Out 2014 02:30:00 UTC 18 Out 2014 23:30:00 BRT
19 Out 2014 03:30:00 UTC 19 Out 2014 01:30:00 BRST
22 Fev 2015 01:30:00 UTC 21 Fev 2015 23:30:00 BRST
22 Fev 2015 02:30:00 UTC 21 Fev 2015 23:30:00 BRT
22 Fev 2015 03:30:00 UTC 22 Fev 2015 00:30:00 BRT
Any suggestions on something I'm missing?
Upvotes: 3
Views: 294
Reputation: 241758
I agree with Jon that Noda Time is much better for this scenario. I highly recommend you go with his implementation.
However, just to explain your results:
In the last line, you format the dt
variable as a string. This variable is a DateTime
type, and its .Kind
is DateTimeKind.Unspecified
.
Your formatLocal
formatter contains the z
token to return the time zone offset.
When you apply the z
format specifier with a DateTime
, the Kind
is evaluated. For Utc
kind, it emits "+0"
. For Local
kind, it emits the offset for the local time zone where the computer runs. For Unspecified
kind, it is treated as local.
So the offsets are not necessarily from the time zone you converted to, but from your local computer's time zone!
MSDN says this about the z
specifier:
With
DateTime
values, the "z" custom format specifier represents the signed offset of the local operating system's time zone from Coordinated Universal Time (UTC), measured in hours. It does not reflect the value of an instance'sDateTime.Kind
property. For this reason, the "z" format specifier is not recommended for use withDateTime
values.With
DateTimeOffset values
, this format specifier represents theDateTimeOffset
value's offset from UTC in hours.
That wording is slightly incorrect, since DateTimeKind.Utc
does indeed return "+0"
, but I think you get the point. You should be using DateTimeOffset
.
DateTimeOffset epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero);
foreach (long ts in timestamps)
{
DateTimeOffset dto = epoch.AddSeconds(ts);
Console.Write(formatUtc, dto);
dto = TimeZoneInfo.ConvertTime(dto, tzBr);
Console.WriteLine("\t\t" + formatLocal, dto);
}
UTC America/Sao_Paulo
---------------------------------------------------------
19 Oct 2014 02:30:00 18 Oct 2014 23:30:00 -3
19 Oct 2014 03:30:00 19 Oct 2014 01:30:00 -2
22 Feb 2015 01:30:00 21 Feb 2015 23:30:00 -2
22 Feb 2015 02:30:00 21 Feb 2015 23:30:00 -3
22 Feb 2015 03:30:00 22 Feb 2015 00:30:00 -3
Upvotes: 1
Reputation: 1502696
Well, you're probably just missing the fact that the Windows time zone data is not the same as the IANA data that Java is using, and that your two Windows 7 boxes probably have a different set of Windows Updates applied. I wouldn't like to guess at exactly what Mono's using, I'm afraid.
One option you might want to consider is using my Noda Time library, which uses the IANA data (and allows you to use whichever version of that data you want), as well as being a generally better API, IMO. Here's the equivalent code:
using System;
using NodaTime;
using NodaTime.Text;
class Test
{
public static void Main (string[] args)
{
long[] timestamps = {1413685800L, 1413689400L, 1424568600L, 1424572200L, 1424575800L};
var zone = DateTimeZoneProviders.Tzdb["America/Sao_Paulo"];
var instantPattern = InstantPattern.CreateWithInvariantCulture("dd MMM yyyy HH:mm:ss");
var zonedPattern = ZonedDateTimePattern.CreateWithInvariantCulture
("dd MMM yyyy HH:mm:ss o<g> (x)", null);
foreach (long ts in timestamps) {
var instant = Instant.FromSecondsSinceUnixEpoch(ts);
var zonedDateTime = instant.InZone(zone);
Console.WriteLine("{0} UTC - {1}",
instantPattern.Format(instant),
zonedPattern.Format(zonedDateTime));
}
}
}
Output:
19 Oct 2014 02:30:00 UTC - 18 Oct 2014 23:30:00 -03 (BRT)
19 Oct 2014 03:30:00 UTC - 19 Oct 2014 01:30:00 -02 (BRST)
22 Feb 2015 01:30:00 UTC - 21 Feb 2015 23:30:00 -02 (BRST)
22 Feb 2015 02:30:00 UTC - 21 Feb 2015 23:30:00 -03 (BRT)
22 Feb 2015 03:30:00 UTC - 22 Feb 2015 00:30:00 -03 (BRT)
Upvotes: 3