12

I'm trying to write some code to sleep until the start of the next minute in the local timezone, but am having great difficulty doing so. The time library has always been one of my weak points, so I'm assuming there's some easy way to do this.

I thought of just computing a new TimeOfDay, but that wouldn't handle 23:59 to 00:00, and would presumably do very confusing things with daylight savings time switch-overs.

Handling leap seconds would be a nice bonus, too.

Using Control.Concurrent.threadDelay to do the sleeping seems like the simplest method to me, so an alternate question would be: How can I get the number of microseconds until the start of the next minute? DiffTime and NominalDiffTime would be perfectly acceptable ways to achieve this.

ehird
  • 40,602
  • 3
  • 180
  • 182
  • Could you just get seconds component of the current time and then sleep (60-s) seconds? Leap-seconds apart, that should work reasonably well. How precise do you want the wake-up to be? – Paul Johnson Dec 20 '11 at 15:43
  • That would work if there's really no other way to do it without a fully manual calculation, but it would be a shame to lose the increased precision I could get with the same data. It's not that big a deal, though; being a second or so late isn't a problem, but I'd definitely like to avoid waking up *before* the next minute starts — the error it produces would only be corrected the next minute. Admittedly, leap seconds aren't very common. – ehird Dec 20 '11 at 15:47
  • Hmm, the documentation for `UTCTime` seems to imply that leap seconds aren't handled by the measuring actions like `getCurrentTime`, so it would be an acceptable solution after all; the seconds field is actually given to picosecond precision, so it would give the best accuracy I could hope for. I'll give it a try. – ehird Dec 20 '11 at 15:58
  • @ehird - I wouldn't be too concerned about extreme precision. Rather, I would be concerned that the thread may not wake up in time. `threadDelay` only sets a lower bound on the time a thread will sleep; it doesn't guarantee that the thread will be re-scheduled promptly. Although if you don't care about being a bit late, just add some padding to your time, e.g. `61-s` seconds. – John L Dec 20 '11 at 16:00
  • @JohnL: Right, but I'm primarily concerned about the thread waking up *too early*, rather than too late. Obviously, this won't happen without a leap second, but if the `time` library doesn't keep track of them, then that shouldn't be a problem... – ehird Dec 20 '11 at 16:02

3 Answers3

10

I worry this may not be what you want given your later comments. I think this would tolerate leap years, timezone changes, and day changes, but not leap seconds.

import Control.Concurrent (threadDelay)
import Data.Time.Clock

sleepToNextMinute :: IO ()
sleepToNextMinute = do t <- getCurrentTime
                       let secs = round (realToFrac $ utctDayTime t) `rem` 60
                       threadDelay $ 1000000 * (60 - secs)

main = do putStrLn "Starting..."
          sleepToNextMinute
          putStrLn "Minute 1"
          sleepToNextMinute
          putStrLn "Minute 2"
Anthony
  • 3,771
  • 23
  • 16
  • As it turns out, I've ended up using something substantially similar to this, to support sleeps of >60s; only I used `delta' - mod' (utctDayTime t) delta` and then converted that into microseconds. Thanks! – ehird Dec 20 '11 at 19:16
  • For small time intervals that could work as long as the value to threadDelay is less than maxBound::Int – Jonke Nov 06 '13 at 09:35
2

Maybe this will help you: PLEAC-Haskell: Dates and Times.

With the help of it you should be able to get the current minute with which you can create the time of the start of the next minute. Then just take the difference as sleep time.

user905686
  • 4,491
  • 8
  • 39
  • 60
  • I know how to get the current minute, but getting the start of the next minute is significantly trickier than that, since it depends on day changes, year changes, timezone changes, etc. I *could* just cascade minute → hour → day → month → year, and ignore daylight savings time (which would be very inconvenient...), but that would be a lot of manual code for something that should be a fairly simple operation to express... it may end up being that this is the only way to do it, though. (In which case we need a better time library.) – ehird Dec 20 '11 at 12:20
  • Ah, now I understand your problem with special times... But what if the difference between e.g. "11:60:00" (start next minute) and "11:59:42" (current time) is calculated correctly? So it depends on the libraries interpretation / difference calculation (if 11:60:00 is interpreted as 12:00:00 etc. everything's fine). – user905686 Dec 21 '11 at 11:26
  • Yes, that would probably have worked. It feels awkward, though, and I wouldn't want to rely on the correctness of operations on invalid times. – ehird Dec 21 '11 at 20:02
1

I'm no expert on the time package, but how about something like this:

import Data.Time    -- need Clock and LocalTime

curTime <- getCurrentTime
let curTOD = timeToTimeOfDay $ utctDayTime curTime
    last   = TimeOfDay (todHour curTOD) (todMin curTOD) 0
    diff   = timeOfDayToTime last + 60 - utctDayTime curTime

This will result in diff :: DiffTime with the correct difference in seconds; all boundaries and leap years should be accounted for. I'm not sure about leap seconds; you'd probably need to add them in manually.

This doesn't account for any time-zone specific mangling, but as getCurrentTime returns a UTCTime I think it will work generally. You can try using utcToLocalTimeOfDay instead of timeToTimeOfDay to manage timezone specific stuff, but then you'd have to do extra work to manage day offsets.

John L
  • 27,937
  • 4
  • 73
  • 88