strftime coerces all time values to midnight if one entry doesn't have HH:MM

23 Views Asked by At

I have a list of dates and times in character strings, and the midnight values have no HH:MM in the string because of the way they were floored to the nearest hour. I am trying to use strftime in R to get all the date-time strings in the same format.

If any of the strings have no HH:MM value, then all of the returned date-time strings are set to 00:00. This behavior began after updating R to 4.3.1.

This line works as expected: strftime(c("2021-08-09 04:00:00", "2021-08-10 06:00"), format = "%Y-%m-%d %H:%M")

This line coerces everything to midnight: strftime(c("2021-08-09 04:00:00", "2021-08-10"), format = "%Y-%m-%d %H:%M")

1

There are 1 best solutions below

0
thelatemail On

The issue is that strftime calls as.POSIXlt(x), which then calls as.POSIXlt.character(x), and then tries to apply the format= for output after this conversion.

The ultimately successful conversion is dictated by the tryFormats= argument in as.POSIXlt.character(x)

The only format that can succeed for both values is "%Y-%m-%d" so that's what you get, and the hours are dropped. E.g.:

strftime
#function (x, format = "", tz = "", usetz = FALSE, ...) 
#format(as.POSIXlt(x, tz = tz), format = format, usetz = usetz,  ...)

args(as.POSIXlt.character)
#function (x, tz = "", format, tryFormats = c("%Y-%m-%d %H:%M:%OS", 
#    "%Y/%m/%d %H:%M:%OS", "%Y-%m-%d %H:%M", "%Y/%m/%d %H:%M", 
#    "%Y-%m-%d", "%Y/%m/%d"), optional = FALSE, ...) 
#NULL

x <- c("2021-08-09 04:00:00", "2021-08-10")

as.POSIXlt(x, tryFormats=c("%Y-%m-%d %H:%M"))
#Error in as.POSIXlt.character(x, tryFormats = c("%Y-%m-%d %H:%M")) : 
#  character string is not in a standard unambiguous format

as.POSIXlt(x, tryFormats=c("%Y-%m-%d %H:%M", "%Y-%m-%d"))
#[1] "2021-08-09 AEST" "2021-08-10 AEST"

This matches what you are seeing:

strftime(x)
#[1] "2021-08-09" "2021-08-10"
strftime(x, format="%Y-%m-%d %H:%M")
#[1] "2021-08-09 00:00" "2021-08-10 00:00"

To get around this, you will need to fill in the times when they are missing using some appropriate logic:

ifelse(nchar(x)==10, paste(x, "00:00:00"), x)
#[1] "2021-08-09 04:00:00" "2021-08-10 00:00:00"

Which will work in strftime now too, but this might be redundant:

strftime(ifelse(nchar(x)==10, paste(x, "00:00:00"), x))
#[1] "2021-08-09 04:00:00" "2021-08-10 00:00:00"