Handling Dates When We Switch to Daylight Savings Time and Back

Handling dates when we switch to daylight savings time and back

I had a similar problem with hydrological data from a sensor. My timestamps were in UTC+1 (CET) and did not switch to daylight saving time (UTC+2, CEST). As I didn't want my data to be one hour off (which would be the case if UTC were used) I took the %z conversion specification of strptime. In ?strptime you'll find:

%z Signed offset in hours and minutes from UTC, so -0800 is 8 hours
behind UTC.

For example: In 2012, the switch from Standard Time to DST occured on 2012-03-25, so there is no 02:00 on this day. If you try to convert "2012-03-25 02:00:00" to a POSIXct-Object,

> as.POSIXct("2012-03-25 02:00:00", tz="Europe/Vienna")
[1] "2012-03-25 CET"

you don't get an error or a warning, you just get date without the time (this behavior is documented).

Using format = "%z" gives the desired result:

> as.POSIXct("2012-03-25 02:00:00 +0100", format="%F %T %z", tz="Europe/Vienna")
[1] "2012-03-25 03:00:00 CEST"

In order to facilitate this import, I wrote a small function with appropriate defaults values:

as.POSIXct.no.dst <- function (x, tz = "", format="%Y-%m-%d %H:%M", offset="+0100", ...)
{
x <- paste(x, offset)
format <- paste(format, "%z")
as.POSIXct(x, tz, format=format, ...)
}

> as.POSIXct.no.dst(c("2012-03-25 00:00", "2012-03-25 01:00", "2012-03-25 02:00", "2012-03-25 03:00"))
[1] "2012-03-25 00:00:00 CET" "2012-03-25 01:00:00 CET" "2012-03-25 03:00:00 CEST"
[4] "2012-03-25 04:00:00 CEST"

Handling of switch to daylight saving time with POSIXct in R

SOLUTION

Thanks to zoowalk and Roland in the comments for this solution:

My timeseries was recorded without time switches. However my OS time zone does record time switches throughout the year. Accordingly, I need to hand a time zone to the function that equally does not have time switches, like UTC:

as.POSIXct(xx, format = "%Y-%m-%d %H:%M", tz="UTC")

Does JavaScript's Date object automatically handle daylight savings?

Javascript Date objects use a time value that is an offset in milliseconds since 1970-01-01T00:00:00Z. It is always UTC.

If the Date constructor is given a single number argument, it is treated as a UTC time value, so represents the same instant in time regardless of system time zone settings.

When you use console.log(date), the built–in toString method is called which generates an implementation dependent string, generally using the current time zone setting of the host system to create a convenient, human readable string.

The current daylight saving rules in the system are used to determine the offset to use for "local" time, so if the date changes from a time when daylight saving applies to one when it doesn't, the time zone offset will be similarly adjusted (note that daylight saving offsets aren't always 1 hour). It does not matter what the current system offset is, the one used is based on the setting for the date and time that the time value represents.

Also, Date objects are very simple, they're just a time value. The time zone offset comes from system settings, it's not a property of the Date itself.

So, given:

My question is, if I were to run this same code again after the 30th
October when the clocks have gone back, would I still get the same
result of 23:30, or would it be 22:30 as if it were GMT?

the answer is "yes", it will still be 23:30 since on 13 September BST applies. It doesn't matter when the code is run, only what the system offset setting is for that date.

How to store repeating dates keeping in mind Daylight Savings Time

First, please recognize that in modern terminology you should say UTC instead of GMT. They are mostly equivalent, except that UTC is more precisely defined. Reserve the term GMT to refer to the portion of the time zone in the United Kingdom that is in effect during the winter months having the offset UTC+0.

Now to your question. UTC is not necessarily the best way to store all date and time values. It works particularly well for past events, or for future absolute events, but it doesn't work so great for future local events - especially future recurring events.

I wrote about this on another answer recently, and is one of the few exception cases where local time makes more sense than UTC. The main argument is the "alarm clock problem". If you set your alarm clock by UTC, you'll be waking up an hour early or late on the day of the DST transitions. This is why most folks set their alarm clocks by local time.

Of course, you can't just store a local time if you are working with data from all over the world. You should store a few different things:

  • The local time of the recurring event, such as "08:00"
  • The time zone that the local time is expressed in, such as "America/New_York"
  • The recurrence pattern, in whatever format makes sense for your application, such as daily, bi-weekly, or the third Thursday of the month, etc.
  • The next immediate UTC date and time equivalent, to the best that you can project it.
  • Perhaps, but not always, a list of future event UTC date and times, projected out some predefined period into the future (perhaps a week, perhaps 6 months, perhaps a year or two, depending on your needs).

For the last two, understand that the UTC equivalent of any local date/time can change if the government responsible for that time zone decides to change anything. Since there are multiple time zone database updates every year, you will want to have a plan to subscribe to announcements of updates and update your timezone database regularly. Whenever you update your timezone data, you'll need to recalculate the UTC equivalent times of all future events.

Having the UTC equivalents is important if you plan to show any kind of list of events spanning more than one time zone. Those are the values you'll query to build that list.

Another point to consider is that if an event is scheduled for a local time that occurs during a DST fall-back transition, you will have to decide whether the event occurs on the first instance (usually), or the second instance (sometimes), or both (rarely), and build into your application a mechanism to ensure the event doesn't fire twice unless you want it to.

If you were looking for a simple answer - sorry, but there isn't one. Scheduling future events across time zones is a complicated task.

Alternative Approach

I've had a few people show me a technique where they do use UTC time for scheduling, which is that they pick a starting date in local time, convert that to UTC to be stored, and store the time zone ID as well. Then at runtime, they apply the time zone to convert the original UTC time back to local time, then use that local time to compute the other recurrences, as if it were the one originally stored as above.

While this technique will work, the drawbacks are:

  • If there's a time zone update that changes the local time before the first instance is run, it will throw the whole schedule off. This can be mitigated by choosing a time in the past for the "first" instance, such that the second instance is really the first one.

  • If the time is really a "floating time" that should follow the user around (such as in an alarm clock on a cell phone), you'd still have to store time zone information for the zone it was originally created in - even if that's not the zone you want to run in.

  • It adds additional complexity without any benefit. I'd reserve this technique for situations where you may have had a UTC-only scheduler that you're trying to retrofit time zone support into.

How do I know if today is a day due to change civil local time e.g. daylight saving time in standard python and pandas timestamps?

Using dateutil.rrule can help (install with pip install python-dateutil).

Because we can fetch dates by weeks, we don't need any loops,

from dateutil.rrule import rrule, WEEKLY
from dateutil.rrule import SU as Sunday
from datetime import date
import datetime

def get_last_sunday(year, month):
date = datetime.datetime(year=year, month=month, day=1)
# we can find max 5 sundays in a months
days = rrule(freq=WEEKLY, dtstart=date, byweekday=Sunday, count=5)
# Check if last date is same month,
# If not this couple year/month only have 4 Sundays
if days[-1].month == month:
return days[-1]
else:
return days[-2]

def get_march_switch(year):
# Get 5 next Sundays from first March
day = get_last_sunday(year, 3)
return day.replace(hour=1, minute=0, second=0, microsecond=0)

def get_october_switch(year):
day = get_last_sunday(year, 10)
return day.replace(hour=2, minute=0, second=0, microsecond=0)

print('2019:')
print(' {}'.format(get_march_switch(2019)))
print(' {}'.format(get_october_switch(2019)))

print('2021:')
print(' {}'.format(get_march_switch(2021)))
print(' {}'.format(get_october_switch(2021)))

get_sundays() returns the 5 next sundays from the first day of the given month, because a month can have maximum 5 sundays.

Then I just check (within get_(march|october)_switch()) if the last given sunday is from the expected month, if not well this month only have 4 sunday, I took this one.

Finally I fix the hours, seconds and microseconds.

Output:

2019:
2019-03-31 01:00:00
2019-10-27 02:00:00
2021:
2021-03-28 01:00:00
2021-10-24 02:00:00

What Date time format can be used to handle DST in Java


Getting it right is not obvious

They are telling you their local time, and you can infer the time zone (because "all date is in one time zone").

The basic calculation looks like this:

ZoneId pacific = ZoneId.of("America/Los_Angeles");
DateTimeFormatter local = DateTimeFormatter.ofPattern("uuuu-MM-dd hh:mm a").withZone(pacific);
ZonedDateTime start = ZonedDateTime.parse("2022-11-06 01:30 AM", local);
ZonedDateTime until = ZonedDateTime.parse("2022-11-07 01:30 AM", local);
long hours = start.until(until, ChronoUnit.HOURS);
System.out.printf("%d hours elapsed%n", hours);

This prints "25 hours elapsed." In the Pacific time zone, November 6, 2022, is 25 hours long, because when daylight saving ends in the autumn, the clock is set back one hour. If someone tells you it's 1:00 AM, you don't know if midnight was one hour ago or two.

The default offset heuristic

What you really need is the offset, and you have to rely on some heuristic for that. By default, ZonedDateTime chooses one instant from multiple ambiguous local date-times by selecting the earliest offset (the "summer" offset).

Specifying the offset

If that's not what you want, you can override the offset explicitly. For example, maybe you process these time stamps close to real-time, and you can guess what the offset should be based on the current time. Or maybe you know that these local time stamps are always processed in chronological order; by tracking the latest time you've seen, and noting if an earlier time stamp follows, you can detect the clock set back and change the offset.

The ZonedDateTime.ofLocal() and ZonedDateTime.ofStrict() functions can be used to explicitly control the offset.

OffsetDateTime

Alternatively, you might request that they include the offset in the timestamp string. Usually this would be indicated with a signed number of hours and minutes: "-07:00" or "-0800". This will provide unambiguous interpretation of times during DST transitions.


Here is an example using OffsetDateTime. First, if the offset uses a colon, as in "2019-10-07T11:07:00+01:00", it is a standard format, and can be parsed like this:

OffsetDateTime start = OffsetDateTime.parse("2019-10-07T11:07:00+01:00");

If the colon is missing, you need a formatter to handle the non-standard input:

DateTimeFormatter odt = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.appendOffsetId()
.toFormatter();
OffsetDateTime when = OffsetDateTime.parse("2019-10-07T11:07:00+01:00", odt);

From there, the calculation is the same as with ZonedDateTime:

OffsetDateTime start = OffsetDateTime.parse("2022-11-06T01:00:00-07:00", odt);
OffsetDateTime until = OffsetDateTime.parse("2022-11-07T01:54:00-08:00", odt);
long hours = start.until(until, ChronoUnit.HOURS);
System.out.printf("%d complete hours elapsed.%n", hours);
Duration duration = Duration.between(start, until);
System.out.println("Full duration: " + duration);

Eliminating Javascript daylight saving time gap, a cross-browser solution

Using moment.js will save you lots of headache, and is the easiest way to achieve cross-browser compatibility for this sort of thing.

var m = moment.utc("3/22/2015","M/D/YYYY")
var s = m.format("YYYY-MM-DD HH:mm:ss")

Using UTC for this is important since you don't want to be affected by the user's time zone. Otherwise, if your date fell into a DST transition, it could be adjusted to some other value. (You're not really intersted in UTC, you're just using it for stability.)


In response to this part of your updated question:

To simplify the question, I'm looking for a function like this:

function date_decomposition(d) {
...
}

console.log(date_decomposition(new Date("3/22/2015 00:00:00")));
=> [2015, 3, 22, 0, 0, 0]

While it's now clear what you are asking for, you must understand it is not possible to achieve your exact requirements, at least not in a cross-browser, cross-region, cross-timezone manner.

  • Each browser has it's own way of implementing the string-to-date parsing. When you use either the constructor new Date(string) or the Date.parse(string) method, you're invoking functionality that is implementation specific.

    There's a chart showing many of the formatting differences here.

  • Even if the implementation were consistent across all environments, you'd have regional formatting and time zone differences to contend with.

    • In the case of regional formatting issues, consider 01/02/2015. Some regions use mm/dd/yyyy ordering and will treat this as January 2nd, while other regions use dd/mm/yyyy ordering and will treat this as February 1st. (Also, some parts of the world use yyyy/mm/dd formatting.)

      Wikipedia has a list and map of where in the world different date formats are used.

    • In the case of time zones, consider that October 19th, 2014 at Midnight (00:00) in Brazil did not exist, and November 2nd, 2014 at Midnight (00:00) in Cuba existed twice.

      The same thing happens on other dates and times in different time zones. From the information you provided, I can deduce that you are in Iran time zone, which uses UTC+03:30 during standard time, and UTC+04:30 during daylight time. Indeed, March 22, 2105 at Midnight (00:00) did not exist in Iran.

      When you try to parse these invalid or ambiguous values, each browser has its own behavior, and there indeed differences between the browsers.

      • For invalid times, some browsers will jump forward an hour, while others will jump backwards an hour.

      • For ambiguous times, some browsers will assume you meant the first (daylight-time) instance, while others will assume you meant the second (standard-time) instance.

Now with all of that said, you can certainly take a Date object and deconstruct its parts, quite simply:

function date_decomposition(d) {
return [d.getFullYear(), d.getMonth()+1, d.getDate(),
d.getHours(), d.getMinutes(), d.getSeconds()];
}

But this will always be based on the local time zone where the code is running. You see, inside the Date object, there is just one value - a number representing the elapsed milliseconds since 1970-01-01T00:00:00Z (without leap seconds being considered). That number is UTC-based.

So, in recap, all of the issues you are having are related to the way the string was parsed into the Date object to begin with. No amount of focusing on the output functions will help you to resolve that in a completely safe manner. Whether you use a library or write your own code, you'll need to obtain that original string of data to get the result you are looking for. By the time it's in a Date object, you've lost the information you need to make this work.

By the way, you might consider watching my Pluralsight course, Date and Time Fundamentals, which covers much of this in even greater detail. Module 7 is entirely about JavaScript and these sorts of gotchas.



Related Topics



Leave a reply



Submit