Question Details

No question body available.

Tags

r sequence

Answers (3)

Accepted Answer Available
Accepted Answer
February 25, 2026 Score: 5 Rep: 70,590 Quality: Expert Completeness: 80%

There's probably a more concise way, but one version could use lubridate and dplyr from the tidyverse to construct a sequence of shift changes within each long shift.

library(tidyverse)

helper function to find shift start from input time

shiftback mutate(across(start:end, ymdhms)) |> reframe(shiftbegan = seq.POSIXt( shiftback(start), shiftback(end + hours(11) + minutes(59), "12 hours"), .by = c(id, start, end)) |> mutate(startadj = pmax(start, shiftbegan), endadj = pmin(end, shiftbegan + hours(12))) |> select(-shiftbegan) |> filter(startadj < endadj)

Resulting in:

id start end startadj endadj 1 A 2026-02-01 15:30:00 2026-02-02 09:30:00 2026-02-01 15:30:00 2026-02-01 18:00:00 2 A 2026-02-01 15:30:00 2026-02-02 09:30:00 2026-02-01 18:00:00 2026-02-02 06:00:00 3 A 2026-02-01 15:30:00 2026-02-02 09:30:00 2026-02-02 06:00:00 2026-02-02 09:30:00 4 B 2026-02-05 19:00:00 2026-02-06 20:00:00 2026-02-05 19:00:00 2026-02-06 06:00:00 5 B 2026-02-05 19:00:00 2026-02-06 20:00:00 2026-02-06 06:00:00 2026-02-06 18:00:00 6 B 2026-02-05 19:00:00 2026-02-06 20:00:00 2026-02-06 18:00:00 2026-02-06 20:00:00

We can feed this into ggplot2 to get a visual confirmation:

enter image description here

... |> ggplot() + ggarrow::geomarrowsegment( aes(startadj, xend = endadj, y = id, yend = id)) + scalexdatetime(breaks = seq.POSIXt( ymdh(2026010106), ymdh(2026123118), "12 hours"), date_labels = "%m/%d\n%I%p")

I think @Tobo's join approach is more elegant and makes more sense in most cases. (That said, I can imagine bizarre edge cases where it could be unwieldy, like tracking 1min ranges for events spanning 100 years -- then you'd be creating 50 million buckets when it might be simpler to make those "on the fly" as needed.)

A dplyr adaptation of that approach is below. Note, the overlaps() helper for non-equi joins is very useful here, as it pulls in the full range of possible overlapping shifts in one step. I specify bounds = () here so that a shift that starts or ends exactly at a shift change does not include the adjacent shift.

x$start
February 25, 2026 Score: 2 Rep: 1,154 Quality: Low Completeness: 60%

Asked and answered, but a join might be much faster and gives you another option. Base R apart from the range join.

# character to datetime x$start 5 B 2026-02-05 19:00:00 2026-02-06 20:00:00 2026-02-06 06:00:00 #> 6 B 2026-02-05 19:00:00 2026-02-06 20:00:00 2026-02-06 18:00:00
February 25, 2026 Score: 0 Rep: 76,593 Quality: Low Completeness: 50%

Using a vector of times from start to end and grepv "06:" and "18:". To have a shorter vector, we can "round" the times by using substrings.

mkshiftstart_df