Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/serokell/o-clock
:hourglass: Type-safe time units in Haskell
https://github.com/serokell/o-clock
haskell rational-numbers time type-level-programming type-safety
Last synced: 10 days ago
JSON representation
:hourglass: Type-safe time units in Haskell
- Host: GitHub
- URL: https://github.com/serokell/o-clock
- Owner: serokell
- License: mpl-2.0
- Created: 2018-01-03T15:45:30.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2024-01-17T13:07:47.000Z (10 months ago)
- Last Synced: 2024-10-12T08:07:53.490Z (25 days ago)
- Topics: haskell, rational-numbers, time, type-level-programming, type-safety
- Language: Haskell
- Homepage:
- Size: 286 KB
- Stars: 49
- Watchers: 23
- Forks: 6
- Open Issues: 6
-
Metadata Files:
- Readme: README.lhs
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# O'Clock
[![GitHub CI](https://github.com/serokell/o-clock/workflows/CI/badge.svg)](https://github.com/serokell/o-clock/actions)
[![Hackage](https://img.shields.io/hackage/v/o-clock.svg)](https://hackage.haskell.org/package/o-clock)
[![Stackage](http://stackage.org/package/o-clock/badge/lts)](http://stackage.org/lts/package/o-clock)
[![Stackage Nightly](http://stackage.org/package/o-clock/badge/nightly)](http://stackage.org/nightly/package/o-clock)
[![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.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 (Time @Second 5)
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).6. `O'Clock` contains useful instances: `ToJSON` and `FromJSON`.
However, they are not included to the package by default.
To do that, you need to provide the corresponding flag: `aeson`.## 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 NoStarIsType #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}module Main where
import Time (type (*))
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).
type WorkDay = 8 * Hour-- | Time unit for a work week (5 working days).
type WorkWeek = 5 * WorkDay-- 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 -> (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 (hour 140)
putStrLn $ "With seriesF: " ++ (seriesF @'[WorkWeek, WorkDay] $ hour 140)
```And the output will be
```haskell ignore
The result: 3ww2wd
With seriesF: 3ww2+1/2wd
```