Duration representation for humans!

This is a small library useful for time duration humanization.

HUMANIZE-DURATION ASDF System Details

Introduction

It is different from LOCAL-TIME-DURATION:HUMAN-READABLE-DURATION, because allows to output only significant parts of a duration object.

Also it limits a number of part. For example, if there days, hours, minutes and seconds, then most probably it is already not important exactly how many minutes and seconds passed since the moment.

Compare these two examples:

local-time-duration:human-readable-duration

CL-USER> (local-time-duration:human-readable-duration
          (local-time-duration:duration :day 5
                                        :hour 13
                                        :minute 0
                                        :sec 14
                                        :nsec 10042))
" 5 days 13 hours 14 seconds 10042 nsecs"

humanize-duration:humanize-duration

CL-USER> (humanize-duration:humanize-duration
          (local-time-duration:duration :day 5
                                        :hour 13
                                        :minute 0
                                        :sec 14
                                        :nsec 10042))
"5 days 13 hours"

Main job is done at humanize-duration (1 2):

function
duration &key stream (n-parts 2) (format-part #'default-format-part)

This is the better version of LOCAL-TIME-DURATION:HUMAN-READABLE-DURATION.

By default it returns only 2 most significant duration parts.

If duration is 2 hour, 43 seconds and 15 nanoseconsds, then function will return "2 hours 43 seconds":

CL-USER> (ultralisp/utils/time:humanize-duration
          (local-time-duration:duration :hour 2
                                        :sec 43
                                        :nsec 15))
"2 hours 43 seconds"

Also, you can pass a :format-part argument. It should be a function of three arguments: (stream part-type part) where part-type is a keyword from this list:

(list :weeks :days :hours :minutes :secs :nsecs)

humanize-duration (1 2) accepts :FORMAT-PART argument, which is default-format-part function by default: your own version. This could be useful if you want to support localization to other languages.

This is should return a string with propertly pluralized form.

  • PART-TYPE argument is a member of (list :weeks :days :hours :minutes :secs :nsecs).

  • PART is an integer.

Here are possible results:

(t :weeks 1) -> "1 week"
(t :weeks 5) -> "5 weeks"
(t :day 2) -> "2 days"

Here is how you can localize output for your language

Localization

humanize-duration (1 2) comes with predefined Russian localization.

Russian localization

This package includes a single function, useful to display duration in Russian language:

This is Russian version of part formatter for humanize-duration

You can use it as an template, to define other languages.

Please, don't forget to contribute your solutions!

Here is how you can use it in your code:

CL-USER> (humanize-duration:humanize-duration
          (local-time-duration:duration :day 5
                                        :hour 13
                                        :minute 0
                                        :sec 14
                                        :nsec 10042)
          :format-part #'humanize-duration/ru:format-part)
"5 дней 13 часов"

Here is how format-part function is defined in the code:

(defun format-part (stream part-type part)
  "This is Russian version of part formatter for HUMANIZE-DURATION"
  (format stream "~d ~A"
          part
          (apply #'choose-form
                 part
                 (ecase part-type
                   (:weeks '("неделя" "недели" "недель"))
                   (:days '("день" "дня" "дней"))
                   (:hours '("час" "часа" "часов"))
                   (:minutes '("минута" "минуты" "минут"))
                   (:secs '("секунда" "секунды" "секунд"))
                   (:nsecs '("наносекунда" "наносекунды" "наносекунд"))))))

Russian version uses internal helper, to choose a correct word form:

(defun choose-form (n &rest forms)
  "This function is based on this gettext formula:

   ```
   Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
   ```
"
  (let ((n%10 (rem n 10))
        (n%100 (rem n 100)))
    (cond
      ((and (= n%10
               1)
            (not (= n%100
                    11)))
       (first forms))
      ((and (>= n%10
                2)
            (<= n%10
                4)
            (or (<= n%100
                    10)
                (>= n%100
                    20)))
       (second forms))
      (t
       (third forms)))))

Applied to a different numbers it produces the following:

CL-USER> (flet ((p (n)
                  (format t "~A ~A~%"
                          n
                          (choose-form n "яблоко" "яблока" "яблок"))))
           (loop for i upto 12
                 do (p i)))
0 яблок
1 яблоко
2 яблока
3 яблока
4 яблока
5 яблок
6 яблок
7 яблок
8 яблок
9 яблок
10 яблок
11 яблок
12 яблок