MPL-2.0 licensed by @serokell
Maintained by Serokell
This version can be pinned in stack with:o-clock-1.3.0@sha256:c3fd59629aafc2cb6e69889c755530026cf9d04182c9f8aafddb9f0c1a2a3aba,4585

Module documentation for 1.3.0

Depends on 2 packages(full list with versions):
Used by 1 package in nightly-2023-06-08(full list with versions):

O’Clock

GitHub CI Hackage Stackage Stackage Nightly License: MPL 2.0

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:

  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.

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

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

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.

Unreleased

1.3.0

  • #129
    • Deprecate toNum: may cause accidental flooring.
    • Add the toFractional function to avoid the accidental flooring.
    • Change the order of the type variables in the definition of floorRat so that the target type comes first.
  • #131
    • Add ceilingRat and ceilingUnit.

1.2.1.1

  • #125:
    • Remove ghc-prim dependency.
    • Remove old artifacts of GHC<8.6 support.
    • Make base constraints stricter.

1.2.1

  • #121:
    • Remove tasty-hspec dependency from tests.

1.2.0.1

Bump upper versions of some dependencies.

1.2.0

  • #113:
    • Increase some upper bounds.
    • Drop support for GHC-8.4.
    • Drop deepseq, serialize and hashable flags.
    • Fix some warnings.

1.1.0

  • #110: Resurrect o-clock in nightly resolver. Specifically:
    • Explicitly support GHC-8.8.
    • Bump many upper bounds.
    • Make benchmarks not buildable by default.
    • Drop support for GHC-8.0 and GHC-8.2.

1.0.0.1

  • Add support for GHC-8.6.1

1.0.0

  • #106: Remove Num, Fractional, Real, RealFrac instancies of Time.
  • #100: Add Hashable, NFData, Serialise, ToJSON, FromJSON instances for Time.

0.1.1

  • #98: Support GHC-8.0.2.
  • #95: Add Semigroup and Monoid instances for Time.
  • #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 to Series. Add SeriesP class for parsing time.
  • #81: Rename TimeStamp to Timestamp.
  • #60: Show fractional as the last argument in the result of seriesF.
  • #76: Remove useless instances of TimeStamp. Make TimeStamp always deal with Seconds internally.
  • #61: Change Show and Read instances for Time to use mixed fractions.
  • #72: Move +:+ and -:- to TimeStamp module. Make operators *:* and /:/ for timeMul and timeDiv. Add -%- operator. Change timeAdd function to work with TimeStamp.
  • #56: Add doctest to documentation.
  • #62: Add .ghci file. Make time creation helpers work with RatioNat instead of Naturals. 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 in seriesF function on right order
  • #45: Fix behavior of 0 time passed to seriesF.

0.0.0

  • Initially created. See README for more information.