Search code examples
rtimezonedstlubridate

R: lubridate's dst() not working as expected


I suspect I am doing something very silly, but I can't get the dst function in lubridate to work as expected.

library(lubridate)

x <- c("2016-01-01", "2016-06-01")

dst(x) # Returns c(FALSE, TRUE)
dst(as.Date(x)) # Returns c(FALSE, FALSE)

The result I expect in both cases is c(FALSE, TRUE). However, I only get the expected result if I pass dst a character vector and not with a Date object. I'm using OS X, my current timezone is PST (America/Los_Angeles).


Solution

  • dst() calls a piece of code which is essentially:

    c(NA, FALSE, TRUE)[as.POSIXlt(x)$isdst + 2]
    

    as.POSIXlt is by default:

    as.POSIXlt(x=, tz="")
    

    ...which will take your system timezone by default. So, given your location in L.A., let's look at:

    as.POSIXlt(x, tz="America/Los_Angeles")
    #[1] "2016-01-01 PST" "2016-06-01 PDT"
    c(NA, FALSE, TRUE)[as.POSIXlt(x, tz="America/Los_Angeles")$isdst + 2]
    #[1] FALSE  TRUE
    

    Everything is fine. Hooray. Now, let's try with as.Date(x)

    as.POSIXlt(as.Date(x))
    #[1] "2016-01-01 UTC" "2016-06-01 UTC"
    as.POSIXlt(as.Date(x), tz="America/Los_Angeles")
    #[1] "2016-01-01 UTC" "2016-06-01 UTC"
    

    Ohhh. So, as.POSIXlt does not play nicely with Date objects, and always returns UTC instead of the local timezone, and seemingly ignores any tz= argument. And since UTC does not abide by any daylight savings, you will always end up with FALSE returned.

    Looking at the R source code, this seems to be the case. In https://svn.r-project.org/R/trunk/src/main/datetime.c you can find:

    # R call:
    #> as.POSIXlt.Date
    #function (x, ...) 
    #.Internal(Date2POSIXlt(x))
    
    # source code:
    #SEXP attribute_hidden do_D2POSIXlt(SEXP call, SEXP op, SEXP args, SEXP env)
    #{
    #...
    setAttrib(ans, s_tzone, mkString("UTC"));
    

    ...as a hard-coded string.