time-machine: A library to mock the current time.

[ bsd3, control, library ] [ Propose Tags ]

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.


[Skip to Readme]
Versions 0.1.0
Dependencies base (>=4.7 && <5), mtl, time, tz [details]
License BSD-3-Clause
Copyright Copyright (C) 2017 TAKAHASHI Yuto
Author TAKAHASHI Yuto <ytaka23dev@gmail.com>
Maintainer TAKAHASHI Yuto <ytaka23dev@gmail.com>
Category Control
Home page https://github.com/y-taka-23/time-machine#readme
Source repo head: git clone https://github.com/y-taka-23/time-machine
Uploaded by y_taka_23 at Sun Nov 26 07:41:48 UTC 2017
Distributions NixOS:0.1.0
Downloads 211 total (7 in the last 30 days)
Rating (no votes yet) [estimated by rule of succession]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2017-11-26 [all 1 reports]
Hackage Matrix CI

Modules

[Index]

Downloads

Maintainer's Corner

For package maintainers and hackage trustees


Readme for time-machine-0.1.0

[back to package description]

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.