Adding time to POSIXct object in R - r

I would like to add 1 hour to a POSIXct object, but it does not support '+'.
This command:
as.POSIXct("2012/06/30","GMT")
+ as.POSIXct(paste(event_hour, event_minute,0,":"), ,"%H:%M:$S")
returns this error:
Error in `+.POSIXt`(as.POSIXct("2012/06/30", "GMT"), as.POSIXct(paste(event_hour, :
binary '+' is not defined for "POSIXt" objects
How can I add a few hours to a POSIXct object ?

POSIXct objects are a measure of seconds from an origin, usually the UNIX epoch (1st Jan 1970). Just add the requisite number of seconds to the object:
x <- Sys.time()
x
[1] "2012-08-12 13:33:13 BST"
x + 3*60*60 # add 3 hours
[1] "2012-08-12 16:33:13 BST"

The lubridate package also implements this nicely with convenience functions hours, minutes, etc.
x = Sys.time()
library(lubridate)
x + hours(3) # add 3 hours

James and Gregor's answers are great, but they handle daylight saving differently. Here's an elaboration of them.
# Start with d1 set to 12AM on March 3rd, 2019 in U.S. Central time, two hours before daylight saving
d1 <- as.POSIXct("2019-03-10 00:00:00", tz = "America/Chicago")
print(d1) # "2019-03-10 CST"
# Daylight saving begins # 2AM. See how a sequence of hours works. (Basically it skips the time between 2AM and 3AM)
seq.POSIXt(from = d1, by = "hour", length.out = 4)
# "2019-03-10 00:00:00 CST" "2019-03-10 01:00:00 CST" "2019-03-10 03:00:00 CDT" "2019-03-10 04:00:00 CDT"
# Now let's add 24 hours to d1 by adding 86400 seconds to it.
d1 + 24*60*60 # "2019-03-11 01:00:00 CDT"
# Next we add 24 hours to d1 via lubridate seconds/hours/days
d1 + lubridate::seconds(24*60*60) # "2019-03-11 CDT" (i.e. 2019-03-11 00:00:00 CDT)
d1 + lubridate::hours(24) # "2019-03-11 CDT" (i.e. 2019-03-11 00:00:00 CDT)
d1 + lubridate::days(1) # "2019-03-11 CDT" (i.e. 2019-03-11 00:00:00 CDT)
So, either answer is correct depending on what you want. Of course, if you're using UTC or some other timezone that doesn't observe daylight saving, these two methods should be the same.

Related

Lubridate timezone handling

I have the following string desribing a date and time in Central European
Summer Time: "2021-09-23 12:00:00".
In fact, I've got a whole, long column of such time points.
For certain reasons I have to handle these times with functions in the 'lubridate'
package. Using 'as_datetime' I get
t0 = "2021-09-23 12:00:00"
t1 = as_datetime(t0); t1
## [1] "2021-09-23 12:00:00 UTC"
that is, as_datetime uses the hours and minutes given and adds Universal
Time "UTC" as time zone. Supplying the time zone CEST gives instead
t1 = as_datetime(t0, tz = "CEST"); t1
## [1] "2021-09-23 10:00:00 CEST"
that is, changes the time, what I don't want.
What I would like to get (what I really need) is "2021-09-23 12:00:00 CEST",
that is changing the time zone without changing the time.
I tried force_tz and with_tz, but that didn't work either.
What I am also wondering is why 'lubridate' converts 12:00:00 UST to
10:00:00 CEST, because CEST is the same as GMT+2 and UTC is GMT+0, so
the result should actually be the other way around.
Thanks for any help.
Try:
t0 = "2021-09-23 12:00:00"
library(lubridate)
#1
t1 = as_datetime(t0, tz = "Europe/Berlin")
[1] "2021-09-23 12:00:00 CEST"
or
#2
t1 = as_datetime(t0, tz = "CST6CDT")
[1] "2021-09-23 12:00:00 CDT"

R: How can I convert non-intuitive strings that describe hours, minutes, and seconds to a workable POSIXct format to perform standard arithmatic on?

I have a data set in R that has values in hours, minutes, and seconds format. However, some values only have hours and minutes, some only have minutes and seconds, some only have minutes, and some only have seconds. It's also not formatted very favorably. Sample data can be found below:
example <- as.data.frame(c("22h28m", "17m7s", "15m", "14s"))
I'd like to convert it to a POSIXct format like the below, with the goal of adding/subtracting time:
Column Title
22:28:00
00:17:07
00:15:00
00:00:14
I've tried as POSIXct() and strptime() formulas, but to no avail. Any help would be greatly appreciated - thanks!
Maybe parse_date_time from lubridate?
library("lubridate")
x <- c("22h28m", "17m7s", "15m", "14s")
y <- parse_date_time(x, orders = c("%Hh%Mm%Ss", "%Hh%Mm", "%Hh%Ss", "%Mm%Ss", "%Hh", "%Mm", "%Ss"), exact = TRUE)
y
## [1] "0000-01-01 22:28:00 UTC"
## [2] "0000-01-01 00:17:07 UTC"
## [3] "0000-01-01 00:15:00 UTC"
## [4] "0000-01-01 00:00:14 UTC"
To get numbers of seconds since midnight, you could do:
y0 <- floor_date(y, unit = "day")
dy <- y - y0
dy
## Time differences in secs
## [1] 80880 1027 900 14
Then you could add dy to any reference time specified as a length-1 POSIXct object. For example, the most recent midnight in the current time zone:
y0 <- floor_date(now(), unit = "day")
y0 + dy
## [1] "2022-02-03 22:28:00 EST"
## [2] "2022-02-03 00:17:07 EST"
## [3] "2022-02-03 00:15:00 EST"
## [4] "2022-02-03 00:00:14 EST"
Update
After reading the documentation a bit more carefully, I am realizing that lubridate implements a way to obtain dy directly.
dy <- duration(toupper(x))
dy
## [1] "80880s (~22.47 hours)" "1027s (~17.12 minutes)" "900s (~15 minutes)" "14s"
Then you can do y0 + dy as above to obtain a POSIXct object, and, if you like,
strftime(y0 + dy, "%T")
## [1] "22:28:00" "00:17:07" "00:15:00" "00:00:14"
to obtain a character vector listing the times without dates or time zones.

R POSIXct returns NA with "03/12/2017 02:17:13"

I have a data set containing the following date, along with several others
03/12/2017 02:17:13
I want to put the whole data set into a data table, so I used read_csv and as.data.table to create DT which contained the date/time information in date.
Next I used
DT[, date := as.POSIXct(date, format = "%m/%d/%Y %H:%M:%S")]
Everything looked fine except I had some NA values where the original data had dates. The following expression returns an NA
as.POSIXct("03/12/2017 02:17:13", format = "%m/%d/%Y %H:%M:%S")
The question is why and how to fix.
Just use functions anytime() or utctime() from package anytime
R> library(anytime)
R> anytime("03/12/2017 02:17:13")
[1] "2017-03-12 01:17:13 CST"
R>
or
R> utctime("03/12/2017 02:17:13")
[1] "2017-03-11 20:17:13 CST"
R>
The real crux is that time did not exists in North America due to DST. You could parse it as UTC as UTC does not observer daylight savings:
R> utctime("03/12/2017 02:17:13", tz="UTC")
[1] "2017-03-12 02:17:13 UTC"
R>
You can express that UTC time as Mountain time, but it gets you the previous day:
R> utctime("03/12/2017 02:17:13", tz="America/Denver")
[1] "2017-03-11 19:17:13 MST"
R>
Ultimately, you (as the analyst) have to provide as to what was measured. UTC would make sense, the others may need adjustment.
My solution is below but ways to improve appreciated.
The explanation for the NA is that in the mountain time zone in the US, that date and time is in the window of the switch to daylight savings where the time doesn't exist, hence NA. While the time zone is not explicitly specified, I guess R must be picking it up from the computer's time, which is in "America/Denver"
The solution is to explicitly state the date/time string is in UTC and then convert back as follows:
time.utc <- as.POSIXct("03/12/2017 02:17:13", format = "%m/%d/%Y %H:%M:%S", tz = "UTC")
> time.utc
[1] "2017-03-12 02:17:13 UTC"
>
Next, add 6 hours to the UTC time which is the difference between UTC and MST
time.utc2 <- time.utc + 6 * 60 * 60
> time.utc2
[1] "2017-03-12 08:17:13 UTC"
>
Now convert to America/Denver time using daylight savings.
time.mdt <- format(time.utc2, usetz = TRUE, tz = "America/Denver")
> time.mdt
[1] "2017-03-12 01:17:13 MST"
>
Note that this is in standard time, because daylight savings doesn't start until 2 am.
If you change the original string from 2 am to 3 am, you get the following
> time.mdt
[1] "2017-03-12 03:17:13 MDT"
>
The hour between 2 and 3 is lost in the change from standard to daylight savings but the data are now correct.

How to get the beginning of the day in POSIXct

My day starts at 2016-03-02 00:00:00. Not 2016-03-02 00:00:01.
How do I get the beginning of the day in POSIXct in local time?
My confusing probably comes from the fact that R sees this as the end-date of 2016-03-01? Given that R uses an ISO 8601?
For example if I try to find the beginning of the day using Sys.Date():
as.POSIXct(Sys.Date(), tz = "CET")
"2016-03-01 01:00:00 CET"
Which is not correct - but are there other ways?
I know I can hack my way out using a simple
as.POSIXct(paste(Sys.Date(), "00:00:00", sep = " "), tz = "CET")
But there has to be a more correct way to do this? Base R preferred.
It's a single command---but you want as.POSIXlt():
R> as.POSIXlt(Sys.Date())
[1] "2016-03-02 UTC"
R> format(as.POSIXlt(Sys.Date()), "%Y-%m-%d %H:%M:%S")
[1] "2016-03-02 00:00:00"
R>
It is only when converting to POSIXct happens that the timezone offset to UTC (six hours for me) enters:
R> as.POSIXct(Sys.Date())
[1] "2016-03-01 18:00:00 CST"
R>
Needless to say by wrapping both you get the desired type and value:
R> as.POSIXct(as.POSIXlt(Sys.Date()))
[1] "2016-03-02 UTC"
R>
Filed under once again no need for lubridate or other non-Base R packages.
Notwithstanding that you understandably prefer base R, a "smart way," for certain meaning of "smart," would be:
library(lubridate)
x <- floor_date(Sys.Date(),"day")
> format(x,"%Y-%m-%d-%H-%M-%S")
[1] "2016-03-02-00-00-00"
From ?floor_date:
floor_date takes a date-time object and rounds it down to the nearest
integer value of the specified time unit.
Pretty handy.
Your example is a bit unclear.
You are talking about a 1 minute difference for the day start, but your example shows a 1 hour difference due to the timezone.
You can try
?POSIXct
to get the functionality explained.
Using Sys.Date() withing POSIXct somehow overwrites your timezone setting.
as.POSIXct(Sys.Date(), tz="EET")
"2016-03-01 01:00:00 CET"
While entering a string gives you
as.POSIXct("2016-03-01 00:00:00", tz="EET")
"2016-03-01 EET"
It looks like 00:00:00 is actually the beginning of the day. You can conclude it from the results of the following 2 inequalities
as.POSIXct("2016-03-02 00:00:02 CET")>as.POSIXct("2016-03-02 00:00:01 CET")
TRUE
as.POSIXct("2016-03-02 00:00:01 CET")>as.POSIXct("2016-03-02 00:00:00 CET")
TRUE
So somehow this is a timezone issue. Notice that 00:00:00 is automatically removed from the as.POSIXct result.
as.POSIXct("2016-03-02 00:00:00 CET")
"2016-03-02 CET"

fastPOSIXct equivalent for converting non-UTC to UTC

Hi I have a character vector (rr) that is several million in length, and it represents time and date stamps in the format %Y-%m-%d %H:%M:%S recorded in Australia/Sydney.
How do get a POSIXct object (quickly) that represents this.
I have found fastPOSIXct in the fasttime package, but for this to be accurate, it requires the original character string to be in GMT/UTC, (which mine is not) and then converted back into the correct timezone using the tz arguement...
> head(rr)
[1] "2009-05-01 10:01:00" "2009-05-01 10:02:00" "2009-05-01 10:03:00" "2009-05-01 10:04:00"
[5] "2009-05-01 10:05:00" "2009-05-01 10:06:00"
> as.POSIXct(head(rr),tz="Australia/Sydney")
[1] "2009-05-01 10:01:00 EST" "2009-05-01 10:02:00 EST" "2009-05-01 10:03:00 EST"
[4] "2009-05-01 10:04:00 EST" "2009-05-01 10:05:00 EST" "2009-05-01 10:06:00 EST"
The above line takes ages if doing it on the full set of data...so any speed improvements would be appreciated. Thanks.
Inspired by Dirk's answer to this qn, I made this wrapper for handling a whole bunch of dates across the year:
fastPOSIXct_generic <- function(x, mytz = "America/New_York")
{
# Caution, read: ?DateTimeClasses
stopifnot(is.character(x))
times_UTC <- fastPOSIXct(x, tz='UTC')
num_times <- as.numeric(times_UTC)
t1 <- as.POSIXct(x[1], tz = mytz)
t2 <- as.POSIXct(x[1], tz = "UTC")
offset <- as.numeric(difftime(t1, t2, units = "secs"))
daylightoffset <- as.POSIXlt(t1)$isdst
# For this first 'time' in t1 and t2, remove possible impact of losing one hour by setting clocks one hour forward during summer months:
offset <- offset + daylightoffset * 3600
num_times <- num_times + offset
new_num_times <- as.POSIXct(num_times, tz = mytz, origin = '1970-01-01')
new_num_times2 <- new_num_times - as.POSIXlt(new_num_times)$isdst * 3600
return(new_num_times2)
}
# Test Sydney time
mm <- as.POSIXct(c("2015-03-15 15:00:00", "2015-4-10 15:00:00", "2014-10-01 15:00:00", "2015-10-15 15:00:00"), tz = "Australia/Sydney")
# "2015-03-15 15:00:00 AEDT" "2015-04-10 15:00:00 AEST" "2014-10-01 15:00:00 AEST" "2015-10-15 15:00:00 AEDT"
aus_stamps <- as.character(mm)
aus_back <- fastPOSIXct_generic(x = aus_stamps, mytz = "Australia/Sydney")
#"2015-03-15 15:00:00 AEDT" "2015-04-10 15:00:00 AEST" "2014-10-01 15:00:00 AEST" "2015-10-15 15:00:00 AEDT"
identical(mm, aus_back)
# TRUE
My use cases are nearly always UTC to America/New_York, where so far it has seemed to work fine. I don't know whether it works correctly for other time zones; just the cases where dst has time go forward an hour.
Here is one approach:
i) Lie to fasttime() and pretend the data was UTC, use to parse the data into a vector x
ii) Compute an offset to UTC using your first data point:
R> d1 <- "2009-05-01 10:01:01" ## or use `head(rr,1)`
R> t1 <- as.POSIXct(d1,tz="Australia/Sydney")
R> t2 <- as.POSIXct(d1,tz="UTC")
R> offset <- as.numeric(difftime(t2, t1, units="secs"))
R> offset
[1] 36000
iii) Apply the offset value to your data -- that is a quick addition as POSIXct really is a numeric type with (fractional) seconds (since epoch) as its unit.

Resources