Cat
Cat

Reputation: 49

Convert 24 hour time from Character type to 24 hour time and subtract two times

I have two columns stored as character data that represent 24-hour time.

Wakeup <- c("0700", "0515", "0815")

Bedtime <- c("2400", "2230", "0100")

I would like to subtract these times and get the desired column below (as numeric).

Hours_slept <- c(7, 6.75, 7.25)

I would like to use the tidyverse.

Upvotes: 1

Views: 469

Answers (2)

Tom
Tom

Reputation: 8800

Here's a base R implementation. Like Andy mentioned, there is an assumption that the Bedtime must be before Wakeup, and by no more than a day:

Wakeup <- c("0700", "0515", "0815")
Bedtime <- c("2400", "2230", "0100")

Wakeup <- as.POSIXct(Wakeup, format='%H%M')
Bedtime <- as.POSIXct(Bedtime, format='%H%M')
Bedtime[Bedtime > Wakeup] <- Bedtime[Bedtime > Wakeup] - as.difftime(1, units='days')
Hours_slept <- as.numeric(difftime(Wakeup, Bedtime, units='hours'))
# [1] 7.00 6.75 7.25

The input hours are converted into datetimes, (which by default will take the current date). Subtract one day from any times where the bed time is greater than the corresponding wakeup. Then subtract the datetimes with difftime and get the data in numeric hours.


OP brought up the issue of missing values in the comments. My initial solution fails with Error in NextMethod(.Generic) : NAs are not allowed in subscripted assignments. Following here, one fix is to check for NAs when subscripting:

Wakeup <- c("0700", NA, "0815")
Bedtime <- c("2400", "2230", "0100")
IsNA <- is.na(Wakeup) | is.na(Bedtime) # FALSE  TRUE FALSE

Wakeup <- as.POSIXct(Wakeup, format='%H%M')
Bedtime <- as.POSIXct(Bedtime, format='%H%M')

# subscripting accounts for NAs now
Bedtime[Bedtime > Wakeup & ! IsNA] <- Bedtime[Bedtime > Wakeup & ! IsNA] - as.difftime(1, units='days')
Hours_slept <- as.numeric(difftime(Wakeup, Bedtime, units='hours'))
# 7.00   NA 7.25

But this also led me to another solution, which I maybe prefer in general. Calculate the difference in the automatically generated date times, and add 24 (hours) to any negative values. This seems to work with NAs without any additional fuss (it uses ifelse and avoids the subscripting with NA):

Wakeup <- c("0700", "0515", "0815")
Bedtime <- c("2400", "2230", NA)

Wakeup <- as.POSIXct(Wakeup, format='%H%M')
Bedtime <- as.POSIXct(Bedtime, format='%H%M')
Hours_slept <- as.numeric(difftime(Wakeup, Bedtime, units='hours'))
Hours_slept <- ifelse(Hours_slept < 0, Hours_slept + 24, Hours_slept)
# 7.00 6.75   NA

Upvotes: 3

Andy Baxter
Andy Baxter

Reputation: 7636

There's a few interesting things to think through here, such as how to convert to a time object (if that's what you're aiming for) and how to work out time distance without a date (we can tell that 2230 is day before wakeup and 0100 is same day, but R doesn't).

If you want to stick to very tidyversey ways of doing things, then you can make it a tibble and mutate using lubridate functions:

library(tidyverse)
library(lubridate)

Wakeup <- c("0700", "0515", "0815")

Bedtime <- c("2400", "2230", "0100")

tibble(Bedtime, Wakeup) %>%
  mutate(
    # First change makes each a time, formatting time string by inserting space
    across(.fns = ~ str_replace(.x, "(?=\\d{2}$)", " ") %>% hm()),
    # Then time difference
    time_diff = Wakeup - Bedtime,
    # Some are negative - a quick fix may be just to add 24h
    time_diff = if_else(time_diff < 0, hours(24) + time_diff, time_diff),
    # Then convert to decimal number of hours
    time_diff = as.numeric(time_diff) / (60 * 60)
  )

#> # A tibble: 3 × 3
#>   Bedtime    Wakeup    time_diff
#>   <Period>   <Period>      <dbl>
#> 1 24H 0M 0S  7H 0M 0S       7   
#> 2 22H 30M 0S 5H 15M 0S      6.75
#> 3 1H 0M 0S   8H 15M 0S      7.25

Created on 2022-05-26 by the reprex package (v2.0.1)

Upvotes: 4

Related Questions