3

23:59:59 o'clock is closer to midnight than 03:00:00 o'clock to midnight, for example. Unfortunately, R tells the opposite (at least the package I use). Here is what I mean:

image

In fact I do not only care about midnight but I need to find the closest time of the day in a vector to a given time and I do not care about the date. There is a similiar question with a great answer but the code doesn't work as expected because in the link time is a timeline not a circle. See here:

library("chron")
x <- times(c("00:00:02", "23:59:59"))
v <- times("00:00:00")
indx <- which(abs(x-v) == min(abs(x - v)))
x[indx]
00:00:02 # which is further from 00:00:00 than 23:59:59

According to the code all times between 00:00:00 and 23:59:59 are closer to midnigth than 23:59:59. For example, this leads to the confusing result that 16:23:11 is closer to midnight than 23:59:59. So R seems to start at 00:00:00 and end at 23:59:59 and thus "does not see" that 23:59:59 is pretty close to 00:00:00. I understand that this makes sense if we take into account dates: For example, 2001-01-01 00:00:02 is closer to 2001-01-01 00:00:00 than is 2001-01-01 23:59:59 to 2001-01-01 00:00:00. But how to find the closest time of the day where one considers the time as a circle?

1 Answers1

2

Edit for a more general solution:

If you are just looking for a nice general solution for this problem:

Since this solution above is only applicable for a very specific situation I tried to create a more general solution. I created a function that wil find to time in a vector closest to a given time.

library('chron')

#' This function returns the time in 'timeVector' that is 
#' closest to 'time'
closest.time <- function(timeVector, time) {
  times()
  x <- times(timeVector)
  v <- times(time)

  clockwise_distance = abs(x - v) 
  anticlockwise_distance = times("23:59:59") - clockwise_distance + times("00:00:01")
  clockwise_and_anticlockwise <-  matrix(c(anticlockwise_distance,  clockwise_distance), ncol = 2)
  shortest_distance_of_the_two <- apply(clockwise_and_anticlockwise, 1, min)

  indx <- which(shortest_distance_of_the_two == min(shortest_distance_of_the_two))

  x[indx]
}

This solution is based on the idea that there are two ways to go round the circle. The first is just the normal clockwise distance. The second is the anticlockwise distance. Since the whole circle is 24 hours, the anticlockwise distance is '24_hours - clockwise_distance'.

Then for each value in timesVector there should be checked if the clockwise or anticlockwise distance is the shortest.

Lastly, there should be checked which time in timeVector is closest to time

Old answer

Since chron does not have a nice function to do this and I can't come up with a solution that uses the times data type, I am going to abandon times and use POSIXct instead.

POSIXct also has a date attribute that you said you don't want to use. This is why the date in our POSIXct is just a dummy value that we don't really look at, except for changing it in ways to solve our problem.

x <- c("00:00:02", "23:59:59")
x <- as.POSIXct(x, format = "%H:%M:%OS")
v <- as.POSIXct("00:00:00", format = "%H:%M:%OS")

# I subtract 24 hours to center all times around the same midnight.
mid.day <- as.POSIXct("12:00:00", format = "%H:%M:%OS")
x[x > mid.day] <- x[x > mid.day] - 24*60*60

# I used your same approach to check the time closest to midnight.
# you might want to change this to something that uses difftime(). Ask me if you  need help with this.
indx <- which(abs(x-v) == min(abs(x - v)))

# shows us the POSIXct object the way we want to see it (only time)
format(x[indx], format="%H:%M:%S")

Note that you might want to use difftime(x,y) now to get the difference in time

 indx <- which(abs(difftime(x, v)) == min(abs(difftime(x, v))))
dylanvanw
  • 3,029
  • 1
  • 8
  • 18
  • Thank you. Unfortunately, here we get the opposite problem: x <- c("12:00:01", "11:59:58") shows that "11:59:58" is closer to "12:00:00" than "12:00:01" is to "12:00:00". –  May 28 '19 at 06:09
  • @schwantke This is because my solution only works when `v` is midnight. When you change the time for v you have to change the variable `mid.day`. `mid.day` works as a delimiter and is dependent on the time that is in `v`. For your different situation where `v` is "12:00:00", the delimiter is not needed since "12:00:00" is already the perfect middle of the day. So: if you change `v` you have to change the delimiter too (`mid.day`). – dylanvanw May 28 '19 at 16:23
  • I have a dataset with more than 60,000 cases and more than 100 Dates per case. So I can't set such things manually but need a code that automatically calculates the difference from the times of my dataset to (any) given time correctly.. –  May 29 '19 at 05:52
  • I see what you mean. I added a new solution in the form of a function that you can just use for your problem. Is this what you were looking for? – dylanvanw May 29 '19 at 19:24
  • 1
    I just thought of a much nicer solution. I'll probably get back to you tomorrow. – dylanvanw May 29 '19 at 19:46
  • Looking forward to it! Thank you :) –  May 29 '19 at 20:37
  • Thanks for asking this question. I really liked the problem. I just edited my answer and created a nicer general function for this problem. Tell me if you find any problems. – dylanvanw Jun 01 '19 at 17:38
  • Thank you! That works as expected. One question: what does the `times()` in the first line of the function? Also, maybe it is due to the class of Dates that I use but I had to do `x <- times(substrRight(as.character(timeVector), 8)) v <- times(substrRight(as.character(time), 8))` and then it works –  Jun 04 '19 at 06:50
  • `times` assumes the string it reads to be in this format: "%H:%M:%OS". This means that if you have a string with also date included like this: "02/02/2019 22:10:39", You should strip the time from the date and only give the string with the time to the function `times`. Does it look like this could be the case with you? – dylanvanw Jun 04 '19 at 07:06