diff --git a/alarmclock.cabal b/alarmclock.cabal
--- a/alarmclock.cabal
+++ b/alarmclock.cabal
@@ -1,5 +1,5 @@
 name:                alarmclock
-version:             0.4.0.4
+version:             0.5.0.0
 synopsis:            Wake up and perform an action at a certain time.
 description:         Wake up and perform an action at a certain time.
 homepage:            https://bitbucket.org/davecturner/alarmclock
@@ -7,7 +7,7 @@
 license-file:        LICENSE
 author:              David Turner
 maintainer:          dave.c.turner@gmail.com
-copyright:           (c) David Turner 2014
+copyright:           (c) David Turner 2014-2018
 category:            Concurrency
 build-type:          Simple
 cabal-version:       >=1.10
diff --git a/src/Control/Concurrent/AlarmClock.hs b/src/Control/Concurrent/AlarmClock.hs
--- a/src/Control/Concurrent/AlarmClock.hs
+++ b/src/Control/Concurrent/AlarmClock.hs
@@ -31,7 +31,7 @@
   , setAlarmNow
   , isAlarmSet
   , isAlarmSetSTM
-  , TimeScale(..)
+  , TimeScale
   , MonotonicTime(..)
   ) where
 
@@ -49,6 +49,27 @@
                                              diffTimeSpec, getTime,
                                              toNanoSecs)
 
+{-| Abstraction that allows for a choice between the UTC timescale and a
+monotonic timescale, which differ in their handling of irregularities such as
+clock adjustments and leap seconds.
+
+Alarms set using the 'UTCTime' timescale wait for the system clock to pass the
+given time before going off, and account for the clock being adjusted
+backwards and for (positive) leap seconds while waiting. If the clock is set
+forwards, or a negative leap second occurs, then the alarm may go off later
+than expected by an amount that is roughly equal to the adjustment. It is
+possible to correct for this by setting the alarm again after the adjustment
+has occurred.
+
+The 'Monotonic' timescale cannot be so adjusted, which may be more suitable for
+some applications.
+
+Note that the timeliness of the alarm going off is very much on a "best effort"
+basis, and there are many environmental factors that could cause the alarm to
+go off later than expected.
+
+-}
+
 class TimeScale t where
   getAbsoluteTime   :: IO t
   microsecondsDiff  :: t -> t -> Integer
@@ -59,7 +80,7 @@
   earlierOf              = min
   microsecondsDiff t1 t2 = ceiling $ (1000000 *) $ diffUTCTime t1 t2
 
-{-| Representation of system monotonic clock. #-}
+{-| Representation of system monotonic clock. -}
 newtype MonotonicTime = MonotonicTime TimeSpec deriving (Show, Eq, Ord)
 
 instance TimeScale MonotonicTime where
@@ -148,7 +169,7 @@
 runAlarmClock :: TimeScale t => AlarmClock t -> (t -> IO ()) -> IO ()
 runAlarmClock AlarmClock{..} wakeUpAction = labelMyThread "alarmclock" >> loop
   where
-  loop = readNextSetting >>= go
+  loop = readNextSetting >>= handleNewSetting
 
   readNextSetting = atomically $ readTVar acNewSetting >>= \case
     AlarmNotSet    -> retry
@@ -158,21 +179,16 @@
       writeTVar acIsSet True
       return $ Just t
 
-  go Nothing           = return ()
-  go (Just wakeUpTime) = wakeNoLaterThan wakeUpTime
-
-  wakeNoLaterThan wakeUpTime = do
-    timeoutLength <- microsecondsDiff wakeUpTime <$> getAbsoluteTime
-    safeTimeout timeoutLength readNextSetting >>= \case
-      Nothing -> actAndContinue
-      Just newSetting -> go newSetting
-
-  -- Times out immediately if the duration is nonpositive (unlike 'timeout' which waits forever)
-  safeTimeout dt action
-    | dt > 0    = timeout dt action
-    | otherwise = return Nothing
+  handleNewSetting Nothing           = return ()
+  handleNewSetting (Just wakeUpTime) = wakeShortlyAfter wakeUpTime
 
-  actAndContinue = do
-    atomically $ writeTVar acIsSet False
-    wakeUpAction =<< getAbsoluteTime
-    loop
+  wakeShortlyAfter wakeUpTime = do
+    now <- getAbsoluteTime
+    let microsecondsTimeout = microsecondsDiff wakeUpTime now
+    if 0 < microsecondsTimeout
+      then maybe (wakeShortlyAfter wakeUpTime) handleNewSetting
+              =<< timeout microsecondsTimeout readNextSetting
+      else do
+        atomically $ writeTVar acIsSet False
+        wakeUpAction now
+        loop
