dreeves
dreeves

Reputation: 26952

Daylight Savings Time "bug"

Here's a calculation of the number of hours between two consecutive days:

(AbsoluteTime[{2011, 3, 14}] - AbsoluteTime[{2011, 3, 13}]) / 3600

So you might not be surprised that Mathematica returns 24. But it is surprising. Every other programming language will say 23 because March 13 was the start of Daylight Savings Time. I need my Mathematica program to be consistent with other languages in this regard. What would you recommend?

To be clear about the problem: AbsoluteTime[{2011,3,13}] gives 3508963200. Subtract the unix epoch and that's a unixtime of 1299988800. But give that unixtime to any other programming language and ask it what date it corresponds to and it will say March 12 instead of March 13. (The same thing will work fine for March 14.)

(OK, I know you're dying to know why I'd want to conform to all those clearly broken languages. Well, first of all, the other languages have a point: Thanks to "springing ahead", midnight on March 14 was 23 hours after midnight on March 13. Why I actually care: We use unixtime as the canonical representation for dates. So when I want to convey "2011-03-13 00:00 EST" to another program I send AbsoluteTime minus the unix epoch. That works fine within Mathematica. When I convert that unixtime back I get "2011-03-13 00:00 EST" again. But if I send that unixtime to another program it interprets it as "2011-03-12 23:00 EST" which turns out to be a problem, since that's the previous day.)

Upvotes: 10

Views: 967

Answers (4)

Basil Bourque
Basil Bourque

Reputation: 339679

While I do not know Mathematica, the other Answers suggest that you can execute Java within that environment. If so, I can give you example code updated for modern Java.

Modern Java

For an industry-leading date-time framework, look to the java.time classes found in Java 8+.

Define your two dates. The LocalDate class represents a date-only value, without time-of-day, without time zone or offset-from-UTC.

LocalDate date = LocalDate.of ( 2011 , 3 , 13 ) ;
LocalDate nextDate = date.plusDays( 1 ) ;  // The 14th.

We need to determine the moment the day begins on each date. For that we need a time zone.

Terms:

  • Offset
    Merely a number of hours-minutes-seconds ahead/behind the temporal meridian of UTC. Example: +05:30.
  • Time Zone
    A named history of the past, present, and future changes to the offset used by the people of a particular region, as determined by their politicians. Example: Asia/Kolkata.

You said: "2011-03-13 00:00 EST". Understand that the 2-4 abbreviations such as EST, PST, CST, and IST are not official time zones. These pseudo-zones are not standardized, and are not even unique! Real time zone names are in the format of Continent/Region. Use the pseudo-zones only when presenting localized text to users, never in data storage or data exchange.

Several time zones may be implied by EST. One is America/New_York.

ZoneId z = ZoneId.of( "America/Edmonton" ) ;

Apply that time zone to determine the first moment of the day. Do not assume the day begins at 00:00. On some dates in some time zones, the day may begin at another time such as 01:00. Let java.time determine.

The moment is represented by the ZonedDateTime class.

ZonedDateTime start = date.atStartOfDay ( z ) ;
ZonedDateTime end = nextDate.atStartOfDay ( z ) ;

Calculate the time elapsed between those two moments. To represent a span-of-time unattached to the timeline, on a scale of hours-minutes-seconds, use Duration class.

Duration d = Duration.between ( start , end ) ;

See this code run at Ideone.com.

Notice the change in offsets, from -07:00 to -06:00. So we know the clocks in that Alberta province of Canada jumped ahead an hour, shortening the day the day from the usual 24 hours to only 23.

2011-03-13T00:00-07:00[America/Edmonton]
2011-03-14T00:00-06:00[America/Edmonton]
PT23H

You said:

We use unixtime as the canonical representation for dates.

By unixtime I will assume you mean a count of either whole seconds or milliseconds since the epoch reference of first moment of 1970 as seen in UTC (an offset of zero), 1970-01-01T00:00Z.

To represent that idea in Java, use the java.time.Instant class. This class stores a count of whole seconds, plus a count of nanoseconds for any partial second.

We can extract a Instant from our ZonedDateTime object. Same moment, same point on the timeline, but different wall-clock/calendar.

Instant startInstant = start.toInstant () ;

Get either count:

long secondsSinceEpoch = startInstant.getEpochSecond () ;

… or:

long millisSinceEpoch = startInstant.toEpochMilli () ; 

In reverse:

Instant instant = Instant.ofEpochSecond ( secondsSinceEpoch ) ;

… or:

Instant instant = Instant.ofEpochMilli ( millisSinceEpoch ) ;

Get back to the desired wall-clock/calendar by applying a ZoneId to get a ZonedDateTime.

ZonedDateTime zdt = instant.atZone( z ) ;

So when I want to convey "2011-03-13 00:00 EST" to another program I send AbsoluteTime minus the unix epoch. That works fine within Mathematica. When I convert that unixtime back I get "2011-03-13 00:00 EST" again. But if I send that unixtime to another program it interprets it as "2011-03-12 23:00 EST" which turns out to be a problem, since that's the previous day.)

You did not give specifics, so I cannot diagnose. But I can tell you:

  • Never use EST or any such pseudo-zones in your data. Use only when localizing for presentation to a user. In your data, use only real Continent/Region time zone names.
  • Use only standardized ISO 8601 formats when storing/exchanging date-time values textually.
  • Generally best to work in UTC (offset of zero) rather than a particular time zone unless specifically required by business domain rules.

Be sure you are using an implementation of Java that has been updated to a recent copy of tzdata. Time zone rules change with surprising frequency.

Upvotes: 2

Dr. belisarius
Dr. belisarius

Reputation: 61046

You may try something like this:

tzDreeves = {"Buenos Aires", "13 March", "13 September", 3, 4};

tZone[date_, tz_] := 
 Piecewise[{{tz[[4]],
   First@
    DateDifference[tz[[2]]<>" "<>DateString[date,"Year"], date, "Second"] > 0 &&
   First@
    DateDifference[tz[[3]]<>" "<>DateString[date,"Year"], date, "Second"] < 0}},
 tz[[5]]];

myTimeDif[d1_, d2_, tz_] := 
 DateDifference[DateList@AbsoluteTime[d1, TimeZone -> tZone[d1, tz]], 
                DateList@AbsoluteTime[d2, TimeZone -> tZone[d2, tz]], "Second"]


myTimeDif["March 13, 2011", "March 14, 2011", tzDreeves]
myTimeDif["March 12, 2011", "March 13, 2011", tzDreeves]  

->

{82800,Second}  -> 23 hours
{86400,Second}  -> 24 hours   

In the following example you can see the effect of the DS. We plot the time difference to a fixed date across the DST frontier:

data = Table[{
         DateList@DatePlus["March 12, 2011, 11PM", {i 10, "Minute"}], 
         First@myTimeDif[DatePlus["March 12, 2011, 11PM", {i 10, "Minute"}], 
                         "March 14, 2011, 2 AM", tzDreeves]},
       {i, 1, 13}];

DateListPlot[data, 
 DateTicksFormat -> {"MonthNameShort", " ", "Day", "\n ", "Time"}, 
 GridLines -> {{{{2011, 3, 13}, Red}}, None}, 
 PlotStyle -> PointSize[Large]]

enter image description here

Upvotes: 3

Cassini
Cassini

Reputation: 477

I posted a related question in comp.soft-sys.math.mathematica on 8/4/10:

http://groups.google.com/group/comp.soft-sys.math.mathematica/browse_thread/thread/6f50f6930f1ac325/

It turns out that there is (was?) a bug in the Mac version of M7 which essentially disregarded the Timezone specification in calls to AbsoluteTime. I think the problem was fixed in M8, but I'm not certain.

Upvotes: 3

WReach
WReach

Reputation: 18271

You could use Java to convert back and forth from Unix time:

Needs["JLink`"]
LoadJavaClass["java.util.Calendar"]

ToUnixTime[year_, month_, day_, hour_:0, minute_:0, second_:0] :=
  JavaBlock[
    Module[{calendar}
    , calendar = java`util`Calendar`getInstance[]
    ; calendar@set[year, month - 1, day, hour, minute, second]
    ; Floor[calendar@getTimeInMillis[] / 1000]
    ]
  ]

FromUnixTime[time_Integer] :=
  JavaBlock[
    Module[{calendar}
    , calendar = java`util`Calendar`getInstance[]
    ; calendar@setTimeInMillis[time * 1000]
    ; calendar@getTime[]@toString[]
    ]
  ]

Sample use:

In[19]:= ToUnixTime[2011, 4, 26, 1, 2, 3]
Out[19]= 1303801323

In[20]:= FromUnixTime[1303801323]
Out[20]= "Tue Apr 26 01:02:03 MDT 2011"

As written, the preceding definitions will use your local time zone and locale settings in the conversions.

Upvotes: 4

Related Questions