# O'Clock

## Overview

O'Clock is the library that provides type-safe time units data types.

Most understandable use case is using `threadDelay`

function.
If you want to wait for *5 seconds* in your program, you need to write something like this:

```
threadDelay (5 * 10^(6 :: Int))
```

With O'Clock you can write in several more convenient ways (and use more preferred to you):

```
threadDelay $ sec 5
threadDelay (Time @Second 5)
threadDelay @Second 5
```

## Features

`O'Clock`

provides the following features to its users:

Single data type for all time units.

- Different time units represented as different type parameters for single
`Time`

data type.
Amount of required boilerplate is minimal.

Time stored as `Rational`

number.

- It means that if you convert
`900`

milliseconds to seconds, you will have `0.9`

second instead of `0`

seconds.
So property `toUnit @to @from . toUnit @from @to ≡ id`

is satisfied.

Different unit types are stored as rational multiplier in type.

`o-clock`

package introduces its own kind `Rat`

for type-level rational numbers.
Units are stored as rational multipliers in type. Because of that some computation is performed on type-level.
So if you want to convert `Week`

to `Day`

, `o-clock`

library ensures that time units will just be multipled by `7`

.

Functions from `base`

that work with time are converted to more time-safe versions:

- These functions are:
`threadDelay`

, `timeout`

, `getCPUTime`

.

Externally extensible interface.

- It means that if you want to roll out your own time units and use it in your project,
this can be done in easy and convenient way (see tutorial below).

`O'Clock`

contains useful instances like `Hashable`

, `NFData`

, `Serialise`

, `ToJSON`

, `FromJSON`

but it's not included to the package by default. To do that you need to provide corresponding
flag from this list: `hashable`

, `deepseq`

, `serialise`

and `aeson`

.

**Note:** features support for `GHC-8.2.2`

and `GHC-8.0.2`

is quite limited.

## Example: How to make your own time unit

This README section contains tutorial on how you can introduce your own time units.
Let's solve the following problem:

*You're CEO of big company. Your employers report you number of hours they worked this month.
You want format hours in more human-readable way, i.e. in number of work weeks and work days.
So we want *`140 hours`

be formatted as `3ww2wd`

(3 full work weeks and 2 full work days).

### Setting up

Since this tutorial is literate haskell file, let's first write some pragmas and imports.

```
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Main where
#if ( __GLASGOW_HASKELL__ >= 804 )
import Time (type (*))
#endif
import Time ((:%), (-:-), Time, Hour, UnitName,floorUnit, hour, seriesF, toUnit)
```

### Introduce custom units

You need to write some code in order to introduce your own time units. In our task we need
work day represented as `8`

hours and work week represented as `5`

work days.

```
-- | Time unit for a working day (8 hours).
#if ( __GLASGOW_HASKELL__ >= 804 )
type WorkDay = 8 * Hour
#else
type WorkDay = 28800 :% 1
#endif
-- | Time unit for a work week (5 working days).
#if ( __GLASGOW_HASKELL__ >= 804 )
type WorkWeek = 5 * WorkDay
#else
type WorkWeek = 144000 :% 1
#endif
-- this allows to use 'Show' and 'Read' functions for our time units
type instance UnitName (28800 :% 1) = "wd" -- One WorkDay contains 28800 seconds
type instance UnitName (144000 :% 1) = "ww" -- One WorkWeek contains 144000 seconds
```

### Calculations

Now let's implement main logic of our application. Our main function should take hours,
convert them to work weeks and work days and then show in human readable format.

```
calculateWork :: Time Hour -> (Time WorkWeek, Time WorkDay)
calculateWork workHours =
let completeWeeks = floorUnit $ toUnit @WorkWeek workHours
completeDays = floorUnit $ toUnit @WorkDay workHours -:- toUnit completeWeeks
in (completeWeeks, completeDays)
formatHours :: Time Hour -> String
formatHours hours = let (weeks, days) = calculateWork hours in show weeks ++ show days
```

After that we can simply print the output we wanted.

Thought we have special function for this kind of formatting purposes `seriesF`

.
So the similar result (but not rounded) can be gained with the usage of it. Check it out:

```
main :: IO ()
main = do
putStrLn $ "The result: " ++ formatHours (hour 140)
putStrLn $ "With seriesF: " ++ (seriesF @'[WorkWeek, WorkDay] $ hour 140)
```

And the output will be

```
The result: 3ww2wd
With seriesF: 3ww2+1/2wd
```