Date and time zone conversion – ambiguous DST transition handling in as.POSIXct
0 reputation · 14 May 2024, 11:52 UTC
0 reputation · 14 May 2024, 11:52 UTC
The goal is to reliably convert a character string such as "2021-11-07 01:30" to a POSIXct object while preserving the intended local time semantics, particularly around daylight‑saving transitions.
tz argument is omitted, as.POSIXct defaults to the system time zone, producing non‑portable results.These ambiguities make it hard to write reproducible code that behaves consistently on all machines.
as.POSIXct return for an ambiguous time such as "2021-11-07 01:30" in the America/New_York zone?35275 reputation · 14 May 2024, 20:03 UTC
With an explicit tz = "America/New_York", common base R builds parse "2021-11-07 01:30" to the earlier fall-back occurrence: 01:30 EDT = 2021-11-07 05:30 UTC. The later occurrence would be 01:30 EST = 2021-11-07 06:30 UTC.
There is no documented base R option or argument to choose the later instance. as.POSIXct has no fold/ambiguous parameter. Omitting tz uses the system time zone and makes results non-portable.
Do not assume future R releases will standardise this. Base R does not guarantee earlier/later resolution for ambiguous wall-clock times; it depends on the R build and the underlying C time functions and timezone database.
tz controls how a character string is parsed and how the instant is displayed.The parsing is implementation behavior, not an R contract. On many builds the broken-down time 01:30 on 2021-11-07 is interpreted with the DST offset still in effect, i.e. EDT UTC-4, giving the earlier 05:30 UTC instant. This is why the default appears to be the earlier occurrence, but it is not documented and can vary by build and OS timezone data.
tz. Never rely on the system zone.x <- as.POSIXct("2021-11-07 01:30", tz = "America/New_York")
format(x, "%Y-%m-%d %H:%M:%S %Z %z")
as.numeric(x)
Etc/GMT+5 means UTC-5.y <- as.POSIXct("2021-11-07 01:30", tz = "Etc/GMT+5")
format(y, tz = "America/New_York", "%Y-%m-%d %H:%M:%S %Z %z")
as.numeric(y)
When x is the earlier occurrence, as.numeric(y) - as.numeric(x) should be 3600 seconds.
Do not infer the offset from %Z alone; abbreviations repeat. Compare %z numeric offsets and the numeric epoch.
To make a recommendation specific to your environment, what R version and OS/timezone database is the target machine running? The resolved instance can differ by build, so the target-machine result is authoritative.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 14 May 2024, 22:38 UTC
as.POSIXct delegates to the C library's mktime function, so the exact instant chosen for an ambiguous local time (e.g., 2021-11-07 01:30 America/New_York) depends on the OS's zoneinfo implementation. On glibc-based systems (Linux, macOS) the first occurrence (EDT, UTC-4) is returned; on Windows the result can differ because Microsoft's CRT uses a different rule set.
R does not expose a parameter to request the second occurrence (EST, UTC-5). A portable workaround is to construct the intended UTC offset explicitly:
# first occurrence (EDT)
t1 <- as.POSIXct("2021-11-07 01:30", tz = "America/New_York")
# second occurrence (EST) – add one hour in UTC
t2 <- t1 + 3600
For production code, consider the lubridate package's force_tz or with_tz helpers, which make the offset choice explicit and are tested across platforms. Verify on each target OS with Sys.timezone() and a known transition table.