serokell / o-clock

:hourglass: Type-safe time units in Haskell
Mozilla Public License 2.0
49 stars 6 forks source link

Descending time formatting #19

Closed chshersh closed 6 years ago

chshersh commented 6 years ago

It would be good to have some formatting function which can format time units per unit type. For example, if you have 1 day and 1500 ms) this should be formatted as 1d1s500ms.

This can be helpful for something like YouTrack time formatting.

Since we don't know the biggest unit for given Time unit the only way to implement this is as I can see: try FortnightUnit, then try WeekUnit then try DayUnit and so on. When we try unit, we're calling floor function. And we should try until t == floor t.

The only problem is fractions. Like 2 % 7. I propose to format something like 2/7ms as 285mcs714ns.

The nicest API is to give explicitly list of units as a type to formatting function. Something like this:

seriesF @'[WorkWeekUnit, DayUnit, HourUnit, MinuteUnit] (minute 4000)

should give formatting for YouTrack like this one: 2d18h40m

But I need to think how to implement such behavior...

chshersh commented 6 years ago

Okay, I tried to check whether my idea work or not. Seems like it's working. So you can start from this:

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE KindSignatures      #-}
{-# LANGUAGE TypeOperators       #-}

module Time.Formatting
       ( Series (..)
       ) where

import Time.Rational (Rat)
import Time.Units (Time)

class Series (units :: [Rat]) where
    seriesF :: Time unit -> String

instance Series ('[] :: [Rat]) where
    seriesF _ = ""

instance Series (unit ': units :: [Rat]) where
   seriesF _ = "smth"

If you call it from REPl, you can see this:

ghci> seriesF @'[MinuteUnit] (sec 3)
"smth"
ghci> seriesF @'[] (sec 3)
""

So the idea is to change this instance:

instance Series (unit ': units :: [Rat]) where
   seriesF _ = "smth"

What we need to do is:

  1. Convert existing Time unit to unit from type (probably, type variable names can be changed).
  2. Call floorUnit and format current unit with unit name.
  3. Call seriesF recursively. This probably requires to add Series constraint to units list.
  4. Write some tests.
  5. Tweak API in a such way so that we can write seriesF @'[Second, Minute] instead of seriesF @'[SecondUnit, MinuteUnit] (not sure if it's possible or not).

It seems to me that this instance should look very similar to this instance of Show for heterogeneous lists: