packages feed

o-clock-0.1.0: README.md

# O'Clock

[![Hackage](https://img.shields.io/hackage/v/o-clock.svg)](https://hackage.haskell.org/package/o-clock)
[![Build status](https://travis-ci.org/serokell/o-clock.svg?branch=master)](https://travis-ci.org/serokell/o-clock)
[![Stackage Nightly](http://stackage.org/package/o-clock/badge/nightly)](http://stackage.org/nightly/package/o-clock)
[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/serokell/o-clock/blob/master/LICENSE)

## Overview

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

Most understandable use case is using [`threadDelay`](http://hackage.haskell.org/package/base-4.10.1.0/docs/Control-Concurrent.html#v:threadDelay) function.
If you want to wait for _5 seconds_ in your program, you need to write something like this:

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

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

```haskell ignore
threadDelay $ sec 5
threadDelay (5 :: Time Second)
threadDelay @Second 5
```

## Features

`O'Clock` provides the following features to its users:

1. 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.

2. 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.

3. 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`.

4. Functions from `base` that work with time are converted to more time-safe versions:

   * These functions are: `threadDelay`, `timeout`, `getCPUTime`.

5. 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).

_**Note:**_ features support for `GHC-8.2.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.

```haskell
{-# 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.

```haskell
-- | 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.

```haskell
calculateWork :: Time Hour  -- type synonym for 'Time HourUnit'
              -> (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:

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

And the output will be

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