Skip to content

y-taka-23/time-machine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

time-machine

Build Status Hackage

A library to mock the current time and relevant IO functions by using a type class. You can get the great command of the current time in UTC, time zones, and the speed of time.

module Main where

import Control.Monad.TimeMachine
import Control.Monad.Trans ( liftIO )

main :: IO ()
main = backTo (the future) $ do
    t <- getCurrentTime
    liftIO . putStrLn $ "We are at " ++ show t

-- We are at 1985-10-26 08:24:00.035889 UTC

As you know, time-dependent IO actions are extremely hard to test. Assume, for example, the following simple action. It should return "Good morning" only in the morning, thus the results of its unit tests are fatally fragile.

getGreeting :: IO String
getGreeting = do
    t <- getCurrentTime
    if utctDayTime t <= 12 * 60 * 60
        then return "Good morning"
        else return "Hello"

This library aims to make such actions testable with minimal changes. Actually what you have to do is just:

  1. Use MonadTime type class instead of IO
  2. Wrap IO actions in liftIO if necessary

Here is the testable version of getGreeting. You can see that nothing is changed excepting the signature.

getGreeting :: (MonadTime m) => m String
getGreeting = do
    t <- getCurrentTime
    if utctDayTime t <= 12 * 60 * 60
        then return "Good morning"
        else return "Hello"

Examples

Mocking the Current Time

travelTo changes the result of getCurrentTime and relevant actions.

Other than pointing the target UTCTime explicitly, you have two ways to determine how to mock the current time. This library provides a small DSL to construct the destinations of your time travels.

-- By a date-time according to your local time zone
main = travelTo (oct 26 1985 am 1 24) $ do
    getCurrentTime >>= (liftIO . print)
-- By a relative date
main = travelTo (3 `days` ago) $ do
    getCurrentTime >>= (liftIO . print)

For more detail, see the document.

Mocking Time Zones

jumpTo switch the time zone which is used for calculating the local time. By this function, you can test time-zone-sensitive actions.

import qualified Data.Time.Zones as TZ

main = jumpTo "Asia/Shanghai" $ do
    t  <- getCurrentTime
    tz <- loadLocalTZ
    liftIO . print $ TZ.timeZoneForUTCTime tz t  -- CST

Mocking the Speed of Time

accelerate changes the speed of time. In the following example, time flies 60 times faster than the real.

main = accelerate (x 60) $ do
    getCurrentTime >>= (liftIO . print)  -- (*)
    liftIO . threadDelay $ 1000 * 1000   -- wait a second
    getCurrentTime >>= (liftIO . print)  -- around a minute after (*)

Moreover, as a special case of accelerate, halt stops the time. That is remarkably useful to fix the point of time during your tests.

main = halt $ do
    getCurrentTime >>= (liftIO . print)  -- (*)
    liftIO . threadDelay $ 1000 * 1000   -- wait a second
    getCurrentTime >>= (liftIO . print)  -- exactly same as (*)

Installation

The project is managed by Stack, so you can install it simply:

$ git clone https://github.com/y-taka-23/time-machine.git
$ cd time-machine
$ stack install

License

This project is released under the BSD 3-clause license. For more details, see LICENSE file.