determine if a date/time interval spans noon, regardless of the day, in R

Viewed 155

I have a df describing the number of visits a bird makes to it's nest over the summer. This includes a starttime and an endtime (POSIXct) for each visit.

df <- read.csv(text="
starttime,endtime,duration
2015-03-23 11:07:10,2015-03-23 17:57:10,420    
2015-03-31 22:47:10,2015-04-01 06:47:10,490    
2015-04-11 23:47:10,2015-04-12 06:17:10,400    
2015-04-14 00:07:10,2015-04-15 06:27:10,1830   
2015-06-23 02:17:10,2015-06-25 04:07:10,3000", stringsAsFactors=FALSE)
df$starttime <- as.POSIXct(df$starttime)
df$endtime <- as.POSIXct(df$endtime)

What I need to determine is whether each visit spans noon hour (12:00), regardless of the day of the visit. I also need to determine the number of times noon hour is passed in the case of multi-day visits.

I'm at a loss for where to start. Ideas?

2 Answers

Here's a function that can count the number of noons with a bit of help from dplyr

count_noons <- function(start, end) {
  stopifnot(all(start<end))
  full_days <- pmax(0, date(end) - date(start) - 1)
  full_days + (lubridate::hour(start) <=12) + (lubridate::hour(end) >=12) - (date(start)==date(end))
}

with(df, count_noons(starttime, endtime))

The basic idea is that I see if noon has passed on the first day and the last day (but don't double count if the start and end are on the same day) and then add in the difference in days between start and end (since each full day has one noon)

I tested with a few more cases

df <- read.csv(text="
starttime,endtime
2015-03-23 11:07:10,2015-03-23 17:57:10
2015-03-31 22:47:10,2015-04-01 06:47:10
2015-04-11 23:47:10,2015-04-12 06:17:10
2015-04-12 13:47:10,2015-04-12 19:17:10
2015-04-14 00:07:10,2015-04-15 06:27:10
2015-06-23 02:17:10,2015-06-25 04:07:10
2015-06-23 02:17:10,2015-06-25 14:07:10", stringsAsFactors=FALSE)
df$starttime <- as.POSIXct(df$starttime)
df$endtime <- as.POSIXct(df$endtime)

And I get

transform(df, noons=count_noons(starttime, endtime))
#             starttime             endtime noons
# 1 2015-03-23 11:07:10 2015-03-23 17:57:10     1
# 2 2015-03-31 22:47:10 2015-04-01 06:47:10     0
# 3 2015-04-11 23:47:10 2015-04-12 06:17:10     0
# 4 2015-04-12 13:47:10 2015-04-12 19:17:10     0
# 5 2015-04-14 00:07:10 2015-04-15 06:27:10     1
# 6 2015-06-23 02:17:10 2015-06-25 04:07:10     2
# 7 2015-06-23 02:17:10 2015-06-25 14:07:10     3
format(df$starttime, format = "%p") == "AM" & format(df$endtime, format="%p") == "PM"
# [1]  TRUE FALSE FALSE FALSE FALSE

This is likely sensitive to locale, so you might need to be a little more careful about that.

AM <- format(as.POSIXct("2020-01-01 06:00:00"), format = "%p")
format(df$starttime, format = "%p") == AM & format(df$endtime, format="%p") != AM
# [1]  TRUE FALSE FALSE FALSE FALSE

To get the number of noons, we can combine that technique with a count of "days":

round(difftime(df$endtime, df$starttime, units="days"), 0)
# Time differences in days
# [1] 0 0 0 1 2

to get

pmax(+(format(df$starttime, format = "%p") == AM & format(df$endtime, format="%p") != AM),
     round(difftime(df$endtime, df$starttime, units="days"), 0))
# [1] 1 0 0 1 2
Related