oclock
Typesafe time library. https://github.com/serokell/oclock
Version on this page:  1.0.0.1 
LTS Haskell 12.18:  1.0.0.1 
Stackage Nightly 20180928:  1.0.0 
Latest on Hackage:  1.0.0.1 
Module documentation for 1.0.0.1
O’Clock
Overview
O’Clock is the library that provides typesafe 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.
 Different time units represented as different type parameters for single

Time stored as
Rational
number. It means that if you convert
900
milliseconds to seconds, you will have0.9
second instead of0
seconds. So propertytoUnit @to @from . toUnit @from @to ≡ id
is satisfied.
 It means that if you convert

Different unit types are stored as rational multiplier in type.
oclock
package introduces its own kindRat
for typelevel rational numbers. Units are stored as rational multipliers in type. Because of that some computation is performed on typelevel. So if you want to convertWeek
toDay
,oclock
library ensures that time units will just be multipled by7
.

Functions from
base
that work with time are converted to more timesafe versions: These functions are:
threadDelay
,timeout
,getCPUTime
.
 These functions are:

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 likeHashable
,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
andaeson
.
Note: features support for GHC8.2.2
and GHC8.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 humanreadable 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 #}
#if ( __GLASGOW_HASKELL__ >= 806 )
{# LANGUAGE NoStarIsType #}
#endif
{# 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
Changes
Change log
o’clock uses PVP Versioning. The change log is available on GitHub.
1.0.0.1
 Add support for GHC8.6.1
1.0.0
 #106:
Remove
Num
,Fractional
,Real
,RealFrac
instancies ofTime
.  #100:
Add
Hashable
,NFData
,Serialise
,ToJSON
,FromJSON
instances forTime
.
0.1.1
 #98: Support GHC8.0.2.
 #95:
Add
Semigroup
andMonoid
instances forTime
.  #93:
Remove
transformers
dependency.
0.1.0
 #85:
Add
fromUnixTime
function.  #71:
Add
toNum
function.  #64:
Add property tests for
unitsP . unitsF ≡ id
 #63:
Rename
Formatting
module toSeries
. AddSeriesP
class for parsing time.  #81:
Rename
TimeStamp
toTimestamp
.  #60:
Show fractional as the last argument in the result of
seriesF
.  #76:
Remove useless instances of
TimeStamp
. Make TimeStamp always deal withSecond
s internally.  #61:
Change
Show
andRead
instances forTime
to use mixed fractions.  #72:
Move
+:+
and:
toTimeStamp
module. Make operators*:*
and/:/
fortimeMul
andtimeDiv
. Add%
operator. ChangetimeAdd
function to work withTimeStamp
.  #56:
Add
doctest
to documentation.  #62:
Add
.ghci
file. Make time creation helpers work withRatioNat
instead ofNaturals
. Rename+:
to+:+
add:
.  #46:
Introduce
...
type to create custom time unit lists in provided bounds.  #51:
Add
IsDescending
type family to check lists of time units inseriesF
function on right order  #45:
Fix behavior of 0 time passed to
seriesF
.
0.0.0
 Initially created. See
README
for more information.