Awhile — Time difference from now in different formats (Emacs package)
Below you find the latest version of (1) the package's README and (2) its main source file.
You may also want to read:
For the git repository and issue tracker, see the project's page on sr.ht.
For more packages, see Software.
Overview
Awhile calculates the difference between two moments in time and then displays it in one of a few available formats.
When a second moment is not given, it's assumed to be "now".
You can customize how you want to display a time difference: long or short, exact or fuzzy, how many and which units to use, etc.
You can also directly convert seconds to one of these formats.
And you can convert these formatted outputs back to seconds, or to one of the other formats.
Installation
See my page Software for the most up-to-date instructions on how to download and install any of my Emacs packages.
Having downloaded and installed the package and its dependencies, adapt the configurations below to your init.el file.
(use-package awhile :demand t)
Alternatively, if you don't have use-package:
(require 'awhile)
Summary of callables
Here's an overview of this package's callables:
| Function | Summary |
|---|---|
| awhile-secs | Convert TIME to seconds since the Epoch, rounded to integer. |
| awhile-diff | Subtract TIME2 from TIME1. Return Epoch seconds, rounded to integer. |
| awhile-abs | Display TIME in any of various output formats. |
| awhile-now | Display the difference between TIME and either now or other date. |
| awhile-rev | Convert time difference DIFF to either seconds or an output format. |
| awhile-secs-durn | Convert ISO 8601 duration DURN to signed seconds relative to Epoch. |
| awhile-diff-durn | Convert ISO 8601 duration DURN to signed seconds relative to now. |
| awhile-diff-ival | Convert ISO 8601 interval IVAL to signed seconds. |
| awhile-abs-durn | Convert ISO 8601 duration DURN to ‘awhile-abs’ format. |
| awhile-now-durn | Convert ISO 8601 duration DURN to ‘awhile-now’ format. |
| awhile-now-ival | Convert ISO 8601 interval IVAL to ‘awhile-now’ format. |
| awhile-see-readme | Open awhile's README.org file. |
| awhile-see-news | See the News in awhile's README.org file. |
They are described in more detail below.
Functions
Overview
Here's an overview of this library's functions through examples.
awhile--overview-examples ()
(This function does nothing, except facilitate the display of examples.)
;;; Public functions ;;;; From times to their difference in seconds ;;;;; Since the epoch (awhile-secs "1970-01-01T00:00:01+00") => 1 (awhile-secs "1970-01-01T00:01:00+00") => 60 (awhile-secs "1970-01-01T00:01:01+00") => 61 (awhile-secs "1970-01-01T01:00:00+00") => 3600 (awhile-secs "2042-12-12T12:12:12+00") => 2301999132 ;; ;;;;; Between two dates ;; (if the second date is omitted, use current time) (awhile-diff "2042-12-12" "2042-12-12") => 0 (awhile-diff "2042-12-12" "2042-12-11") => 86400 (awhile-diff "2042-12-12T01" "2042-12-12") => 3600 (awhile-diff "2042-12-12" "2042-12-12T01") => -3600 ;;;; From times to their difference in time units — many formats ;;;;; Since the epoch ;;;;;; All units or restricted (awhile-abs "1970-01-09T17:42+00") => "1 week, 1 day, 17 hours, 42 minutes" (awhile-abs "1970-01-09T17:42+00" :c 3) => "1 week, 1 day, 18 hours" (awhile-abs "1970-01-09T17:42+00" :u"Wh") => "1 week, 42 hours" (awhile-abs "1970-01-09T17:42+00" :u"Dh") => "8 days, 18 hours" (awhile-abs "1970-01-09T17:42+00" :u"s") => "754920 seconds" ;;;;;; Four different output formats (awhile-abs "1970-01-09T17:42+00" :d 'sl) => "1 week, 1 day, 17 hours, 42 minutes" (awhile-abs "1970-01-09T17:42+00" :d 'ss) => "1W1D17h42m" (awhile-abs "1970-01-09T17:42+00" :d 'lv) => '(nil 0 0 1 1 17 42 0) (awhile-abs "1970-01-09T17:42+00" :d 'lp) => '(S nil Y 0 M 0 W 1 D 1 h 17 m 42 s 0) ;;;;;; Different input formats (awhile-abs "Mon Nov 4 11:24 2041 GMT" :d 'ss) => "71Y10M3D8h24m" (awhile-abs "2041-11-04 11:24 GMT" :d 'ss) => "71Y10M3D8h24m" (awhile-abs "2041-11-04T11:24+00" :d 'ss) => "71Y10M3D8h24m" (awhile-abs "2041-W45-1T11:24+00" :d 'ss) => "71Y10M3D8h24m" (awhile-abs "2041-308T11:24+00" :d 'ss) => "71Y10M3D8h24m" ;; (awhile-abs "1970-01-31T10:17:42+00" :d 'ss) => "4W2D10h17m42s" (awhile-abs "1970-01-31T10:17:42Z" :d 'ss) => "4W2D10h17m42s" (awhile-abs 2629062 :d 'ss) => "4W2D10h17m42s" (awhile-abs 2629062.4242424 :d 'ss) => "4W2D10h17m42s" (awhile-abs '(40 7622 424242 400098) :d 'ss) => "4W2D10h17m42s" (awhile-abs '(26290624242424 . 10000000) :d 'ss) => "4W2D10h17m42s" (awhile-abs '(42 17 10 31 1 1970 6 nil 0) :d 'ss) => "4W2D10h17m42s" ;;;;;; Use it to convert seconds: pass seconds instead of dates (awhile-abs 754920) => "1 week, 1 day, 17 hours, 42 minutes" (awhile-abs 754920 :c 3) => "1 week, 1 day, 18 hours" (awhile-abs 754920 :u"Wh") => "1 week, 42 hours" (awhile-abs 754920 :u"Dh") => "8 days, 18 hours" (awhile-abs 754920 :u"s") => "754920 seconds" (awhile-abs 754920 :d 'sl) => "1 week, 1 day, 17 hours, 42 minutes" (awhile-abs 754920 :d 'ss) => "1W1D17h42m" (awhile-abs 754920 :d 'lv) => '(nil 0 0 1 1 17 42 0) (awhile-abs 754920 :d 'lp) => '(S nil Y 0 M 0 W 1 D 1 h 17 m 42 s 0) ;;;;; Between two dates ;; (if the second date is omitted, use current time) ;;;;;; All units or restricted (awhile-now "2042-12-07" :n "2042-12-15T11:42:42") => "1 week, 1 day, 11 hours, 42 minutes, 42 seconds ago" (awhile-now "2042-12-07" :n "2042-12-15T11:42:42" :c 3) => "1 week, 1 day, 12 hours ago" (awhile-now "2042-12-07" :n "2042-12-15T11:42:42" :u "Wh") => "1 week, 36 hours ago" (awhile-now "2042-12-07" :n "2042-12-15T11:42:42" :u "Dh") => "8 days, 12 hours ago" ;;;;;; With or without sign (awhile-now "2042-12-07" :n "2042-12-04") => "3 days from now" (awhile-now "2042-12-07" :n "2042-12-10") => "3 days ago" (awhile-now "2042-12-07" :n "2042-12-04" :S 0) => "3 days" (awhile-now "2042-12-07" :n "2042-12-10" :S 0) => "3 days" ;;;;;; Four different output formats (awhile-now "2042-12-07" :n "2042-12-15T11:42" :d 'sl) => "1 week, 1 day, 11 hours, 42 minutes ago" (awhile-now "2042-12-07" :n "2042-12-15T11:42" :d 'ss) => "-1W1D11h42m" (awhile-now "2042-12-07" :n "2042-12-15T11:42" :d 'lv) => '(- 0 0 1 1 11 42 0) (awhile-now "2042-12-07" :n "2042-12-15T11:42" :d 'lp) => '(S - Y 0 M 0 W 1 D 1 h 11 m 42 s 0) ;;;;;; Different input formats (awhile-now "Mon Nov 4 11:24 2041" :d 'ss :n "2042-12-07") => "-1Y1M1D20h6m" (awhile-now "2041-11-04 11:24" :d 'ss :n "2042-12-07") => "-1Y1M1D20h6m" (awhile-now "<2041-11-04 Mon 11:24>" :d 'ss :n "2042-12-07") => "-1Y1M1D20h6m" (awhile-now "[2041-11-04 Mon 11:24]" :d 'ss :n "2042-12-07") => "-1Y1M1D20h6m" (awhile-now "2041-11-04T11:24" :d 'ss :n "2042-12-07") => "-1Y1M1D20h6m" (awhile-now "2041-W45-1T11:24" :d 'ss :n "2042-12-07") => "-1Y1M1D20h6m" (awhile-now "2041-308T11:24" :d 'ss :n "2042-12-07") => "-1Y1M1D20h6m" (awhile-now 2267177040 :d 'ss :n 2301523200) => "-1Y1M1D20h6m" (awhile-now '(34594 24656) :d 'ss :n '(35118 29952)) => "-1Y1M1D20h6m" (awhile-now "1970-01-31T10:17:42Z" :d 'ss :n 0) => "+4W2D10h17m42s" (awhile-now 2629062 :d 'ss :n 0) => "+4W2D10h17m42s" (awhile-now 2629062.4242424 :d 'ss :n 0) => "+4W2D10h17m42s" (awhile-now '(40 7622 424242 400098) :d 'ss :n 0) => "+4W2D10h17m42s" (awhile-now '(26290624242424 . 10000000) :d 'ss :n 0) => "+4W2D10h17m42s" (awhile-now '(42 17 10 31 1 1970 6 nil 0) :d 'ss :n 0) => "+4W2D10h17m42s" ;;;; From units to either other formats or back to seconds ;;;;; Back to seconds ;;;;;; from 'sl format (awhile-rev "2 hours, 46 minutes, 40 seconds") => '(nil . 10000) (awhile-rev "2 hours, 46 minutes, 40 seconds ago") => '(- . 10000) (awhile-rev "2 hours, 46 minutes, 40 seconds from now") => '(+ . 10000) ;;;;;; from 'ss format (awhile-rev "2h46m40s") => '(nil . 10000) (awhile-rev "-2h46m40s") => '(- . 10000) (awhile-rev "+2h46m40s") => '(+ . 10000) ;;;;;; from 'lv format (awhile-rev '(nil 0 0 0 0 2 46 40)) => '(nil . 10000) (awhile-rev '(- 0 0 0 0 2 46 40)) => '(- . 10000) (awhile-rev '(+ 0 0 0 0 2 46 40)) => '(+ . 10000) ;;;;;; from 'lp format (awhile-rev '(S nil Y 0 M 0 W 0 D 0 h 2 m 46 s 40)) => '(nil . 10000) (awhile-rev '(S - Y 0 M 0 W 0 D 0 h 2 m 46 s 40)) => '(- . 10000) (awhile-rev '(S + Y 0 M 0 W 0 D 0 h 2 m 46 s 40)) => '(+ . 10000) ;;;;; To other formats ;;;;;; to 'ss format (awhile-rev "+2h42m" :d 'ss) => "+2h42m" (awhile-rev "2h42m" :d 'ss :c 1) => "3h" (awhile-rev "2h42m" :d 'ss :u"h") => "3h" (awhile-rev "-2h42m" :d 'ss :u"h") => "-3h" (awhile-rev "+2h42m" :d 'ss :u"h") => "+3h" (awhile-rev "+2h42m" :d 'ss :u"m") => "+162m" ;;;;;; to 'sl format (awhile-rev "+2h42m" :d 'sl) => "2 hours, 42 minutes from now" (awhile-rev "2h42m" :d 'sl :c 1) => "3 hours" (awhile-rev "2h42m" :d 'sl :u"h") => "3 hours" (awhile-rev "-2h42m" :d 'sl :u"h") => "3 hours ago" (awhile-rev "+2h42m" :d 'sl :u"h") => "3 hours from now" (awhile-rev "+2h42m" :d 'sl :u"m") => "162 minutes from now" ;;;;;; to 'lv format (awhile-rev "+2h42m" :d 'lv) => '(+ 0 0 0 0 2 42 0) (awhile-rev "2h42m" :d 'lv :c 1) => '(nil 0 0 0 0 3 0 0) (awhile-rev "2h42m" :d 'lv :u"h") => '(nil 0 0 0 0 3 0 0) (awhile-rev "-2h42m" :d 'lv :u"h") => '(- 0 0 0 0 3 0 0) (awhile-rev "+2h42m" :d 'lv :u"h") => '(+ 0 0 0 0 3 0 0) (awhile-rev "+2h42m" :d 'lv :u"m") => '(+ 0 0 0 0 0 162 0) ;;;;;; to 'lp format (awhile-rev "+2h42m" :d 'lp) => '(S + Y 0 M 0 W 0 D 0 h 2 m 42 s 0) (awhile-rev "2h42m" :d 'lp :c 1) => '(S nil Y 0 M 0 W 0 D 0 h 3 m 0 s 0) (awhile-rev "2h42m" :d 'lp :u"h") => '(S nil Y 0 M 0 W 0 D 0 h 3 m 0 s 0) (awhile-rev "-2h42m" :d 'lp :u"h") => '(S - Y 0 M 0 W 0 D 0 h 3 m 0 s 0) (awhile-rev "+2h42m" :d 'lp :u"h") => '(S + Y 0 M 0 W 0 D 0 h 3 m 0 s 0) (awhile-rev "+2h42m" :d 'lp :u"m") => '(S + Y 0 M 0 W 0 D 0 h 0 m 162 s 0) ;;;; From ISO 8601 durations and intervals ;;;;; From duration after or before the Epoch to seconds ;; (month length may change going back vs. forward) (awhile-secs-durn "PT1H1M1S") => 3661 (awhile-secs-durn "PT1H1M1S" t) => -3661 ;;;;; From duration after or before now to seconds ;; (will not necessarily match function above: month lengths differ) ;; (month length may change going back vs. forward) (awhile-diff-durn "PT1H1M1S") => 3661 (awhile-diff-durn "PT1H1M1S" t) => -3661 ;;;;; From interval after or before now to seconds (awhile-diff-ival "2042-10-10/P1D") => 86400 (awhile-diff-ival "P1D/2042-10-10") => -86400 (awhile-diff-ival "2042-10-10/2042-10-11") => 86400 (awhile-diff-ival "2042-10-11/2042-10-10") => -86400 ;;;;; From duration after or before the Epoch to display formats ;; (month length may change going back vs. forward) (awhile-abs-durn "PT1H1M1S") => "1 hour, 1 minute, 1 second" (awhile-abs-durn "PT1H1M1S" t) => "1 hour, 1 minute, 1 second" (awhile-abs-durn "PT1H1M1S" nil :d 'ss) => "1h1m1s" (awhile-abs-durn "PT1H1M1S" t :d 'ss) => "1h1m1s" ;;;;; From duration after or before now to display formats ;; (month length may change going back vs. forward) (awhile-now-durn "PT1H42M") => "1 hour, 42 minutes from now" (awhile-now-durn "PT1H42M" t) => "1 hour, 42 minutes ago" (awhile-now-durn "PT1H42M" t :S 0) => "1 hour, 42 minutes" (awhile-now-durn "PT1H42M" nil :d 'ss) => "+1h42m" (awhile-now-durn "PT1H42M" t :d 'ss) => "-1h42m" (awhile-now-durn "PT1H42M" t :S 0 :d 'ss) => "1h42m" ;;;;; From interval after or before now to display formats ;; (month length may change going back vs. forward) (awhile-now-ival "2042-10-10/2042-10-12") => "2 days from now" (awhile-now-ival "2042-10-10/P2D") => "2 days from now" (awhile-now-ival "P2D/2042-10-10") => "2 days ago" (awhile-now-ival "2042-10-12/2042-10-10") => "2 days ago" (awhile-now-ival "2042-10-12/P2D" :S 0) => "2 days" (awhile-now-ival "2042-10-10/2042-10-12" :d 'ss) => "+2D" (awhile-now-ival "2042-10-10/P2D" :d 'ss) => "+2D" (awhile-now-ival "P2D/2042-10-10" :d 'ss) => "-2D" (awhile-now-ival "2042-10-12/2042-10-10" :d 'ss) => "-2D" (awhile-now-ival "2042-10-10/P2D" :S 0 :d 'ss) => "2D"
Conversions
Conversion from time T1 can be summarized based on the other time used (T2)
and the format of the output:
| T2 | Seconds (integer) | Units (string or list) |
|---|---|---|
| epoch | awhile-secs | awhile-abs |
| now or other | awhile-diff | awhile-now |
The awhile-rev function converts from a units output to either seconds
or other variation of units output.
The ISO 8601 duration and intervals functions follow a similar logic:
| T2 | Seconds (integer) | Units (string or list) |
|---|---|---|
| epoch | awhile-secs-durn | awhile-abs-durn |
| now or other | awhile-diff-durn | awhile-now-durn |
| awhile-diff-ival | awhile-now-ival |
From times to their difference in seconds
These functions calculate time differences in seconds.
The Unix epoch referenced by functions is 1970-01-01T00:00:00 UTC.
awhile-secs (&optional time)
Convert TIME to seconds since the Epoch, rounded to integer.
If TIME is:
- a string, convert it assuming common timestamp format.
- nil, convert current time.
- a list, convert it after ensuring it's in Lisp timestamp
format; this may involve encoding TIME. - a number, assume it's already Epoch seconds.
;;;; Local time is used if no timezones are mentioned ;; (so specify timezone for predictable result) (awhile-secs "2042-12-12TZ") => 2301955200 (awhile-secs "2042-12-12T+00") => 2301955200 (awhile-secs "2042-12-12T00Z") => 2301955200 (awhile-secs "2042-12-12T00+00") => 2301955200 (awhile-secs "2042-12-12T01:00Z") => 2301958800 (awhile-secs "2042-12-12T01:00+00") => 2301958800 (awhile-secs "2041-11-04T11:24+00") => 2267177040 (awhile-secs "2041-11-04T11:24:42Z") => 2267177082 (awhile-secs "2041-11-04T11:24:42+00") => 2267177082 (awhile-secs "2041-11-04T11:24:42 UTC") => 2267177082 (awhile-secs "2041-11-04T11:24:42+00:00") => 2267177082 (awhile-secs "Mon, 04 Nov 2041 11:24:42 +0000") => 2267177082 ;;;;; An 8-hour difference is expected here (/ (- (awhile-secs "2042-12-12T11:24 PST") (awhile-secs "2042-12-12T11:24 UTC")) 3600) => 8 (/ (- (awhile-secs "2042-12-12T11:24-08") (awhile-secs "2042-12-12T11:24+00")) 3600) => 8 ;;;;; Same as (awhile-now :d 'is TIME1 :n 0) examples — but faster (awhile-secs "1969-12-31T23+00") => -3600 (awhile-secs "1970-01-01T+00") => 0 (awhile-secs "1970-01-01T+00:00") => 0 (awhile-secs "1970-01-01T00:00:01+00") => 1 (awhile-secs "1970-01-01T00:01:00+00") => 60 (awhile-secs "1970-01-01T00:01:01+00") => 61 (awhile-secs "1970-01-01T01:00:00+00") => 3600 (awhile-secs "1970-01-02T+00") => 86400 ;;;;; Different formats are accepted ;; (if no time is passed, the current time is used) ;;;;;; Timestamp string (awhile-secs "2042-12-12T00Z") => 2301955200 (awhile-secs "2042-12-12T00+00") => 2301955200 ;;;;;; Integer seconds since the Epoch (awhile-secs 2301955200) => 2301955200 ;;;;;; Float seconds since the Epoch (awhile-secs 2301955200.780340717) => 2301955201 ;;;;;; Encoded time (HIGH LOW MICRO PICO) (awhile-secs '(35125 3200)) => 2301955200 (awhile-secs '(35125 3200 780340)) => 2301955201 (awhile-secs '(35125 3200 780340 717000)) => 2301955201 ;;;;;; Encoded time (TICKS . HZ) (awhile-secs '(2301955200780340717 . 1000000000)) => 2301955201 ;;;;;; Decoded time (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF) (awhile-secs '(0 0 0 12 12 2042 5 nil 0)) => 2301955200 (awhile-secs '(0 0 0 12 12 2042 nil -1 nil)) => 2301955200
awhile-diff (time1 &optional time2)
Subtract TIME2 from TIME1. Return Epoch seconds, rounded to integer.
If TIME2 is nil, use current time.
;;;; Local time is used if no timezones are mentioned ;; (it doesn't matter when both times are in the same TZ, of course) (awhile-diff "2042-12-12" "2042-12-12") => 0 (awhile-diff "2042-12-12" "2042-12-11") => 86400 (awhile-diff "2042-12-12" "1970-01-01") => 2301955200 (awhile-diff "2042-12-12" "2042-12-12T01:00") => -3600 (awhile-diff "2042-12-12T01:00" "2042-12-12") => 3600 (awhile-diff "2042-12-12T03-08" "2042-12-12T03+00") => 28800 ;;;;; Same as (awhile-now :d 'is TIME1 :n TIME2) examples — but faster (awhile-diff "2042-12-07" "2042-12-07") => 0 (awhile-diff "2042-12-07" "2042-12-06 23:59:59") => 1 (awhile-diff "2042-12-07" "2042-12-06 23:59") => 60 (awhile-diff "2042-12-07" "2042-12-06 23:00") => 3600 (awhile-diff "2042-12-07" "2042-12-06") => 86400 (awhile-diff "2042-12-07" "2042-11-30") => 604800 (awhile-diff "2042-12-07" "2042-12-07 01:00") => -3600 (awhile-diff "2042-12-07" "2042-12-07 01:00:42") => -3642 (awhile-diff "2042-12-07" "2042-12-02") => 432000 (awhile-diff "2042-12-07" "2042-12-02T08") => 403200 (awhile-diff "2042-12-07" "2042-12-12") => -432000 (awhile-diff "2042-12-07" "2042-12-12T08") => -460800 (awhile-diff "2042-12-07" "2041-11-04T11:24:42") => 34346118 ;;;;; You can also pass Epoch seconds directly (awhile-diff 2301523200 "2041-11-04T11:24:42+00") => 34346118 (awhile-diff "2042-12-07T00+00" 2267177082) => 34346118 (awhile-diff 2301523200 2267177082) => 34346118 ;; but in this last case you could just: (- 2301523200 2267177082) => 34346118
From times to their difference in time units — many formats
The main functions are:
- awhile-now: difference between a date and either now or other date
- awhile-abs: difference between a date and the beginning of the Epoch
Both offer the same variety of output formats.
The latter can also be used for conversion of seconds to these formats.
Let's talk about time units.
Minutes, hours, days, and weeks are precise units. We can agree that:
- 1 minute = 60 seconds
- 1 hour = 60 minutes
- 1 day = 24 hours
- 1 week = 7 days
Months and years are trickier.
- Months vary from 28 to 31 days.
- Years usually have 365 days. Except when they have 366.
How to deal with these?
One way is to follow the date. From 2042-05-07 to 2042-06-07 is one
month, and from 2042-06-07 to 2042-07-07 is one month again, and to
2043-07-07 is one year.
The advantage of this approach is that it matches our intuition about
dates. The disadvantage is that we end up with time units that are not
constant, as they depend on context: “2 months ago” when in March of a
non-leap year would mean 59 days ago, whereas “2 months ago” when in
September would mean 62 days. This is unsatisfactory.
Awhile averages the units over a four-year period:
- 1 year = (365×4 + 1) ÷ 4 = 365.25 days = 365 days, 6 hours
- 1 month = (365×4 + 1) ÷ 48 = 30.4375 days = 30 days, 10 hours, 30 minutes
Twelve months exactly fit into one year.
With this, fuzzy relative times will be more precise over longer
periods. And whenever you see “year” or “month”, the elapsed time that
they represent will be predictably the above. Think “average year” and
“average month”.
If you dislike this, avoid these units. Use :u "WDhms" or :u "Dhms".
The Unix epoch referenced by functions is 1970-01-01T00:00:00 UTC.
awhile-abs (&optional time &rest rest)
Display TIME in any of various output formats.
Similar to awhile-now, but instead of considering TIME relative to now
or to some selected reference date, it uses the beginning of the Epoch
as reference. Furthermore, the result has no sign information.
So this is awhile-now with :n 0 :S 0.
Passing :n or :S to it has no effect; any of the other keys can be used.
If TIME is nil, use current time.
And since TIME can be passed as seconds since the Epoch, this function
offers a convenient way to simply convert an amount of seconds to one of
the display formats offered by awhile-now.
;;; Display options ;;;; Display long string (awhile-abs "1970-01-07T00+00") => "6 days" (awhile-abs "1970-01-07T01+00") => "6 days, 1 hour" (awhile-abs "1970-01-07T01-08") => "6 days, 9 hours" (awhile-abs "1970-01-12T03-08") => "1 week, 4 days, 11 hours" (awhile-abs "1970-01-12T03:00 PST") => "1 week, 4 days, 11 hours" ;;;;; Note that our "year" and "month" units are averaged over 4 years: (awhile-abs 31557600) => "1 year" ; exactly 12 months (awhile-abs 2629800) => "1 month" (awhile-abs 31557600 :u "Dhms") => "365 days, 6 hours" (awhile-abs 2629800 :u "Dhms") => "30 days, 10 hours, 30 minutes" (awhile-abs "1970-01-01T00:00:01+00") => "1 second" (awhile-abs "1970-01-01T00:01+00") => "1 minute" (awhile-abs "1970-01-01T01+00") => "1 hour" (awhile-abs "1970-01-02T00+00") => "1 day" (awhile-abs "1970-01-08T00+00") => "1 week" (awhile-abs "1970-01-31T10:30+00") => "1 month" (awhile-abs "1971-01-01T06+00") => "1 year" (awhile-abs "1971-01-02T06:01:01+00") => "1 year, 1 day, 1 minute, 1 second" (awhile-abs "1971-01-02T06:01:01+00" :c 3) => "1 year, 1 day, 1 minute" (awhile-abs "1971-01-02T06:01:01+00" :u "YMD") => "1 year, 1 day" (awhile-abs "1971-01-02T06:01:01+00" :u "Yhm") => "1 year, 24 hours, 1 minute" (awhile-abs "1971-01-02T06:01:01+00" :c 2 :u "Yhm") => "1 year, 24 hours" ;;;;; Just convert some amount of seconds (awhile-abs 3661) => "1 hour, 1 minute, 1 second" (awhile-abs 7322) => "2 hours, 2 minutes, 2 seconds" (awhile-abs 73220) => "20 hours, 20 minutes, 20 seconds" (awhile-abs 100000) => "1 day, 3 hours, 46 minutes, 40 seconds" ;;;; Display short string (awhile-abs "1970-01-07T00+00" :d 'ss) => "6D" (awhile-abs "1970-01-07T01+00" :d 'ss) => "6D1h" (awhile-abs "1970-01-07T01-08" :d 'ss) => "6D9h" (awhile-abs "1970-01-12T03-08" :d 'ss) => "1W4D11h" (awhile-abs "1970-01-12T03:00 PST" :d 'ss) => "1W4D11h" ;;;;; Note that our "year" and "month" units are averaged over 4 years: (awhile-abs 31557600 :d 'ss) => "1Y" ; exactly 12 months (awhile-abs 2629800 :d 'ss) => "1M" (awhile-abs 31557600 :d 'ss :u "Dhms") => "365D6h" (awhile-abs 2629800 :d 'ss :u "Dhms") => "30D10h30m" (awhile-abs "1970-01-01T00:00:01+00" :d 'ss) => "1s" (awhile-abs "1970-01-01T00:01+00" :d 'ss) => "1m" (awhile-abs "1970-01-01T01+00" :d 'ss) => "1h" (awhile-abs "1970-01-02T00+00" :d 'ss) => "1D" (awhile-abs "1970-01-08T00+00" :d 'ss) => "1W" (awhile-abs "1970-01-31T10:30+00" :d 'ss) => "1M" (awhile-abs "1971-01-01T06+00" :d 'ss) => "1Y" (awhile-abs "1971-01-02T06:01:01+00" :d 'ss) => "1Y1D1m1s" (awhile-abs "1971-01-02T06:01:01+00" :d 'ss :c 3) => "1Y1D1m" (awhile-abs "1971-01-02T06:01:01+00" :d 'ss :u "YMD") => "1Y1D" (awhile-abs "1971-01-02T06:01:01+00" :d 'ss :u "Yhm") => "1Y24h1m" (awhile-abs "1971-01-02T06:01:01+00" :d 'ss :c 2 :u "Yhm") => "1Y24h" ;;;;; Just convert some amount of seconds (awhile-abs 3661 :d 'ss) => "1h1m1s" (awhile-abs 7322 :d 'ss) => "2h2m2s" (awhile-abs 73220 :d 'ss) => "20h20m20s" (awhile-abs 100000 :d 'ss) => "1D3h46m40s" ;;;;; Test timezones ;; UTC (awhile-abs "1970-01-01T10:17:42Z" :d 'ss) => "10h17m42s" (awhile-abs "1970-01-01T10:17:42+00" :d 'ss) => "10h17m42s" (awhile-abs "1970-01-01T10:17:42 UTC" :d 'ss) => "10h17m42s" (awhile-abs "1970-01-01 10:17:42 GMT" :d 'ss) => "10h17m42s" ;; PST (awhile-abs "1970-01-01T10:17:42-08" :d 'ss) => "18h17m42s" (awhile-abs "1970-01-01T10:17:42 PST" :d 'ss) => "18h17m42s" (awhile-abs "1970-01-01 10:17:42 PST" :d 'ss) => "18h17m42s" ;;;;; Adding :n or :S have no effect (awhile-abs "1970-01-08T00+00" :d 'ss) => "1W" (awhile-abs "1970-01-08T00+00" :d 'ss :S t) => "1W" (awhile-abs "1970-01-08T00+00" :d 'ss :n 86400) => "1W" (awhile-abs "1970-01-08T00+00" :d 'ss :n 86400 :S t) => "1W" (awhile-abs "1970-01-08T00+00" :d 'ss :n "1970-01-02T00+00") => "1W" (awhile-abs "1970-01-08T00+00" :d 'ss :n "1970-01-02T00+00" :S t) => "1W" ;;;;; Input times can be in different formats ;;;;;; Timestamp string (awhile-abs "1970-01-31T10:17:42Z" :d 'ss) => "4W2D10h17m42s" (awhile-abs "1970-01-31T10:17:42+00" :d 'ss) => "4W2D10h17m42s" ;;;;;; Integer seconds since the Epoch (awhile-abs 2629062 :d 'ss) => "4W2D10h17m42s" ;;;;;; Float seconds since the Epoch (awhile-abs 2629062.4242424 :d 'ss) => "4W2D10h17m42s" ;;;;;; Encoded time (HIGH LOW MICRO PICO) (awhile-abs '(40 7622) :d 'ss) => "4W2D10h17m42s" (awhile-abs '(40 7622 424242) :d 'ss) => "4W2D10h17m42s" (awhile-abs '(40 7622 424242 400098) :d 'ss) => "4W2D10h17m42s" ;;;;;; Encoded time (TICKS . HZ) (awhile-abs '(26290624242424 . 10000000) :d 'ss) => "4W2D10h17m42s" ;;;;;; Decoded time (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF) (awhile-abs '(42 17 10 31 1 1970 6 nil 0) :d 'ss) => "4W2D10h17m42s" ;;;; Display list of values (awhile-abs "1970-01-07T00+00" :d 'lv) => '(nil 0 0 0 6 0 0 0) (awhile-abs "1970-01-07T01+00" :d 'lv) => '(nil 0 0 0 6 1 0 0) (awhile-abs "1970-01-07T01-08" :d 'lv) => '(nil 0 0 0 6 9 0 0) (awhile-abs "1970-01-12T03-08" :d 'lv) => '(nil 0 0 1 4 11 0 0) (awhile-abs "1970-01-12T03:00 PST" :d 'lv) => '(nil 0 0 1 4 11 0 0) ;;;;; Note that our "year" and "month" units are averaged over 4 years: (awhile-abs 31557600 :d 'lv) => '(nil 1 0 0 0 0 0 0) (awhile-abs 2629800 :d 'lv) => '(nil 0 1 0 0 0 0 0) (awhile-abs 31557600 :d 'lv :u "Dhms") => '(nil 0 0 0 365 6 0 0) (awhile-abs 2629800 :d 'lv :u "Dhms") => '(nil 0 0 0 30 10 30 0) (awhile-abs "1970-01-01T00:00:01+00" :d 'lv) => '(nil 0 0 0 0 0 0 1) (awhile-abs "1970-01-01T00:01+00" :d 'lv) => '(nil 0 0 0 0 0 1 0) (awhile-abs "1970-01-01T01+00" :d 'lv) => '(nil 0 0 0 0 1 0 0) (awhile-abs "1970-01-02T00+00" :d 'lv) => '(nil 0 0 0 1 0 0 0) (awhile-abs "1970-01-08T00+00" :d 'lv) => '(nil 0 0 1 0 0 0 0) (awhile-abs "1970-01-31T10:30+00" :d 'lv) => '(nil 0 1 0 0 0 0 0) (awhile-abs "1971-01-01T06+00" :d 'lv) => '(nil 1 0 0 0 0 0 0) (awhile-abs "1971-01-02T06:01:01+00" :d 'lv) => '(nil 1 0 0 1 0 1 1) (awhile-abs "1971-01-02T06:01:01+00" :d 'lv :c 3) => '(nil 1 0 0 1 0 1 0) (awhile-abs "1971-01-02T06:01:01+00" :d 'lv :u "YMD") => '(nil 1 0 0 1 0 0 0) (awhile-abs "1971-01-02T06:01:01+00" :d 'lv :u "Yhm") => '(nil 1 0 0 0 24 1 0) (awhile-abs "1971-01-02T06:01:01+00" :d 'lv :c 2 :u "Yhm") => '(nil 1 0 0 0 24 0 0) ;;;;; Just convert some amount of seconds (awhile-abs 3661 :d 'lv) => '(nil 0 0 0 0 1 1 1) (awhile-abs 7322 :d 'lv) => '(nil 0 0 0 0 2 2 2) (awhile-abs 73220 :d 'lv) => '(nil 0 0 0 0 20 20 20) (awhile-abs 100000 :d 'lv) => '(nil 0 0 0 1 3 46 40) ;;;; Display list of keys and values (awhile-abs "1970-01-07T00+00" :d 'lp) => '(S nil Y 0 M 0 W 0 D 6 h 0 m 0 s 0) (awhile-abs "1970-01-07T01+00" :d 'lp) => '(S nil Y 0 M 0 W 0 D 6 h 1 m 0 s 0) (awhile-abs "1970-01-07T01-08" :d 'lp) => '(S nil Y 0 M 0 W 0 D 6 h 9 m 0 s 0) (awhile-abs "1970-01-12T03-08" :d 'lp) => '(S nil Y 0 M 0 W 1 D 4 h 11 m 0 s 0) (awhile-abs "1970-01-12T03:00 PST" :d 'lp) => '(S nil Y 0 M 0 W 1 D 4 h 11 m 0 s 0) ;;;;; Note that our "year" and "month" units are averaged over 4 years: (awhile-abs 31557600 :d 'lp) => '(S nil Y 1 M 0 W 0 D 0 h 0 m 0 s 0) (awhile-abs 2629800 :d 'lp) => '(S nil Y 0 M 1 W 0 D 0 h 0 m 0 s 0) (awhile-abs 31557600 :d 'lp :u "Dhms") => '(S nil Y 0 M 0 W 0 D 365 h 6 m 0 s 0) (awhile-abs 2629800 :d 'lp :u "Dhms") => '(S nil Y 0 M 0 W 0 D 30 h 10 m 30 s 0) (awhile-abs "1970-01-01T00:00:01+00" :d 'lp) => '(S nil Y 0 M 0 W 0 D 0 h 0 m 0 s 1) (awhile-abs "1970-01-01T00:01+00" :d 'lp) => '(S nil Y 0 M 0 W 0 D 0 h 0 m 1 s 0) (awhile-abs "1970-01-01T01+00" :d 'lp) => '(S nil Y 0 M 0 W 0 D 0 h 1 m 0 s 0) (awhile-abs "1970-01-02T00+00" :d 'lp) => '(S nil Y 0 M 0 W 0 D 1 h 0 m 0 s 0) (awhile-abs "1970-01-08T00+00" :d 'lp) => '(S nil Y 0 M 0 W 1 D 0 h 0 m 0 s 0) (awhile-abs "1970-01-31T10:30+00" :d 'lp) => '(S nil Y 0 M 1 W 0 D 0 h 0 m 0 s 0) (awhile-abs "1971-01-01T06+00" :d 'lp) => '(S nil Y 1 M 0 W 0 D 0 h 0 m 0 s 0) (awhile-abs "1971-01-02T06:01:01+00" :d 'lp) => '(S nil Y 1 M 0 W 0 D 1 h 0 m 1 s 1) (awhile-abs "1971-01-02T06:01:01+00" :d 'lp :c 3) => '(S nil Y 1 M 0 W 0 D 1 h 0 m 1 s 0) (awhile-abs "1971-01-02T06:01:01+00" :d 'lp :u "YMD") => '(S nil Y 1 M 0 W 0 D 1 h 0 m 0 s 0) (awhile-abs "1971-01-02T06:01:01+00" :d 'lp :u "Yhm") => '(S nil Y 1 M 0 W 0 D 0 h 24 m 1 s 0) (awhile-abs "1971-01-02T06:01:01+00" :d 'lp :c 2 :u "Yhm") => '(S nil Y 1 M 0 W 0 D 0 h 24 m 0 s 0) ;;;;; Just convert some amount of seconds (awhile-abs 3661 :d 'lp) => '(S nil Y 0 M 0 W 0 D 0 h 1 m 1 s 1) (awhile-abs 7322 :d 'lp) => '(S nil Y 0 M 0 W 0 D 0 h 2 m 2 s 2) (awhile-abs 73220 :d 'lp) => '(S nil Y 0 M 0 W 0 D 0 h 20 m 20 s 20) (awhile-abs 100000 :d 'lp) => '(S nil Y 0 M 0 W 0 D 1 h 3 m 46 s 40)
awhile-now (time &rest rest)
Display the difference between TIME and either now or other date.
REST may contain any or all of these keys:
| Key | Description | Default |
|---|---|---|
| :d | format to display the results | String, long, English |
| :n | result will be relative to it | now |
| :u | a string of valid units to use | YMWDhms |
| :c | maximum number of units to use | 7 (all) |
| :S | whether to include the sign | t |
To exclude references to sign, use :S 0.
The valid options for key :d are:
- 'sl = String, long format, English
- 'ss = String, short format, one-letter units
- 'lv = List with only the values
- 'lp = Plist of units and their values
These output options change in form, but represent the same data.
The units “month” and “year” are averaged over a 4-year period:
- 1 year = 365 days, 6 hours
- 1 month = 30 days, 10 hours, 30 minutes
Twelve months exactly fit into one year.
TIME may be given in different formats.
Strings can be variously passed as, for example:
Fri Dec 12 11:24 2042
2042-12-12T11:24:42
2042-12-12 11:24:42
2042-12-12 11:24
2042-W50-5T11:24
2042-346T11:24
In all the above, local time is assumed. Otherwise, add timezone:
2042-12-12T11:24Z
2042-12-12T11:24+00
2042-12-12T11:24 UTC
2042-12-12 11:24 GMT
2042-12-12T11:24:42-08
2042-12-12T11:24:42 PST
2042-12-12 11:24:42 PST
Cons cells are assumed to be in a valid Lisp timestamp format, such as:
(35126 7546)
(35126 7546 543433)
(35126 7546 543433 703000)
(2302025082543433 . 1000000)
They can also be passed decoded:
(42 24 11 12 12 2042 5 nil -28800)
Numbers are assumed to be seconds since the Epoch:
2302025082
Floats are fine, too:
2302025082.5434337
This list is not exhaustive: a few other formats may be accepted.
;;; Display options ;; Note: current time is used when you omit :n TIME2 ;;;; Display long string (awhile-now "2042-12-07" :n "2042-12-02") => "5 days from now" (awhile-now "2042-12-07" :n "2042-12-02T08") => "4 days, 16 hours from now" (awhile-now "2042-12-07" :n "2042-12-12") => "5 days ago" (awhile-now "2042-12-07" :n "2042-12-12T08") => "5 days, 8 hours ago" (awhile-now "2042-12-12T03-08" :n "2042-12-12T03+00") => "8 hours from now" (awhile-now "2042-12-12T03 PST" :n "2042-12-12T03 UTC") => "8 hours from now" ;;;;; Note that our "year" and "month" units are averaged over 4 years: (awhile-now 31557600 :n 0 :S 0) => "1 year" ; exactly 12 months (awhile-now 2629800 :n 0 :S 0) => "1 month" (awhile-now 31557600 :n 0 :S 0 :u "Dhms") => "365 days, 6 hours" (awhile-now 2629800 :n 0 :S 0 :u "Dhms") => "30 days, 10 hours, 30 minutes" (awhile-now "2042-12-07" :n "2041-11-04T11:24:42") => "1 year, 1 month, 1 day, 20 hours, 5 minutes, 18 seconds from now" (awhile-now "2042-12-07" :n "2041-11-04T11:24:42" :c 4) => "1 year, 1 month, 1 day, 20 hours from now" (awhile-now "2042-12-07" :n "2041-11-04T11:24:42" :u "YMD") => "1 year, 1 month, 2 days from now" (awhile-now "2042-12-07" :n "2041-11-04T11:24:42" :u "YWD") => "1 year, 4 weeks, 4 days from now" (awhile-now "2042-12-07" :n "2041-11-04T11:24:42" :u "YD") => "1 year, 32 days from now" (awhile-now "2042-12-07" :n "2041-11-04T11:24" :c 2 :u "YMD") => "1 year, 1 month from now" ;;;;; With and without sign information (awhile-now "2042-12-07" :n "2042-12-04") => "3 days from now" (awhile-now "2042-12-07" :n "2042-12-07") => "now" (awhile-now "2042-12-07" :n "2042-12-10") => "3 days ago" (awhile-now "2042-12-07" :n "2042-12-04" :S 0) => "3 days" (awhile-now "2042-12-07" :n "2042-12-07" :S 0) => "0 seconds" (awhile-now "2042-12-07" :n "2042-12-10" :S 0) => "3 days" ;;;; Display short string (awhile-now "2042-12-07" :d 'ss :n "2042-12-07") => "+0s" (awhile-now "2042-12-07" :d 'ss :n "2042-12-02") => "+5D" (awhile-now "2042-12-07" :d 'ss :n "2042-12-02T08") => "+4D16h" (awhile-now "2042-12-07" :d 'ss :n "2042-12-12") => "-5D" (awhile-now "2042-12-07" :d 'ss :n "2042-12-12T08") => "-5D8h" (awhile-now "2042-12-07" :d 'ss :n "2041-11-04T11:24:42") => "+1Y1M1D20h5m18s" (awhile-now "2042-12-07" :d 'ss :n "2041-11-04T11:24:42" :c 4) => "+1Y1M1D20h" (awhile-now "2042-12-07" :d 'ss :n "2041-11-04T11:24:42" :u"YMD") => "+1Y1M2D" (awhile-now "2042-12-07" :d 'ss :n "2041-11-04T11:24:42" :u"YWD") => "+1Y4W4D" (awhile-now "2042-12-07" :d 'ss :n "2041-11-04T11:24:42" :u"YD") => "+1Y32D" (awhile-now "2042-12-07" :d 'ss :n "2041-11-04T11:24" :c 2 :u"YMD") => "+1Y1M" ;;;;; Input times can be in different formats ;;;;;; Timestamp string ;; (changing the "now time" here, but could have changed the other instead) (awhile-now "2042-12-07" :d 'ss :n "Mon Nov 4 11:24 2041") => "+1Y1M1D20h6m" (awhile-now "2042-12-07" :d 'ss :n "2041-11-04 11:24") => "+1Y1M1D20h6m" (awhile-now "2042-12-07" :d 'ss :n "<2041-11-04 Mon 11:24>") => "+1Y1M1D20h6m" (awhile-now "2042-12-07" :d 'ss :n "[2041-11-04 Mon 11:24]") => "+1Y1M1D20h6m" (awhile-now "2042-12-07" :d 'ss :n "2041-11-04T11:24") => "+1Y1M1D20h6m" (awhile-now "2042-12-07" :d 'ss :n "2041-W45-1T11:24") => "+1Y1M1D20h6m" (awhile-now "2042-12-07" :d 'ss :n "2041-308T11:24") => "+1Y1M1D20h6m" ;; (awhile-now "1970-01-31T10:17:42Z" :d 'ss :n 0) => "+4W2D10h17m42s" (awhile-now "1970-01-31T10:17:42+00" :d 'ss :n 0) => "+4W2D10h17m42s" ;;;;;; Integer seconds since the Epoch (awhile-now 2301523200 :d 'ss :n 2267177040) => "+1Y1M1D20h6m" (awhile-now 2629062 :d 'ss :n 0) => "+4W2D10h17m42s" ;;;;;; Float seconds since the Epoch (awhile-now 2629062.4242424 :d 'ss :n 0) => "+4W2D10h17m42s" ;;;;;; Encoded time (HIGH LOW MICRO PICO) (awhile-now '(40 7622) :d 'ss :n 0) => "+4W2D10h17m42s" (awhile-now '(40 7622 424242) :d 'ss :n 0) => "+4W2D10h17m42s" (awhile-now '(40 7622 424242 400098) :d 'ss :n 0) => "+4W2D10h17m42s" ;;;;;; Encoded time (TICKS . HZ) (awhile-now '(26290624242424 . 10000000) :d 'ss :n 0) => "+4W2D10h17m42s" ;;;;;; Decoded time (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF) (awhile-now '(42 17 10 31 1 1970 6 nil 0) :d 'ss :n 0) => "+4W2D10h17m42s" ;;;;; With and without sign information (awhile-now "2042-12-07" :d 'ss :n "2042-12-04") => "+3D" (awhile-now "2042-12-07" :d 'ss :n "2042-12-07") => "+0s" (awhile-now "2042-12-07" :d 'ss :n "2042-12-10") => "-3D" (awhile-now "2042-12-07" :d 'ss :n "2042-12-04" :S 0) => "3D" (awhile-now "2042-12-07" :d 'ss :n "2042-12-07" :S 0) => "0s" (awhile-now "2042-12-07" :d 'ss :n "2042-12-10" :S 0) => "3D" ;;;;; Test timezones ;; UTC (awhile-now "1970-01-01T10:17:42Z" :d 'ss :n 0) => "+10h17m42s" (awhile-now "1970-01-01T10:17:42+00" :d 'ss :n 0) => "+10h17m42s" (awhile-now "1970-01-01T10:17:42 UTC" :d 'ss :n 0) => "+10h17m42s" (awhile-now "1970-01-01 10:17:42 GMT" :d 'ss :n 0) => "+10h17m42s" ;; PST (awhile-now "1970-01-01T10:17:42-08" :d 'ss :n 0) => "+18h17m42s" (awhile-now "1970-01-01T10:17:42 PST" :d 'ss :n 0) => "+18h17m42s" (awhile-now "1970-01-01 10:17:42 PST" :d 'ss :n 0) => "+18h17m42s" ;;;; Display list of values (awhile-now "2042-12-07" :d 'lv :n "2042-12-07") => '(+ 0 0 0 0 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2042-12-02") => '(+ 0 0 0 5 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2042-12-02T08") => '(+ 0 0 0 4 16 0 0) (awhile-now "2042-12-07" :d 'lv :n "2042-12-12") => '(- 0 0 0 5 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2042-12-12T08") => '(- 0 0 0 5 8 0 0) (awhile-now "2042-12-07" :d 'lv :n "2041-11-04T11:24:42") => '(+ 1 1 0 1 20 5 18) (awhile-now "2042-12-07" :d 'lv :n "2041-11-04T11:24:42" :c 4) => '(+ 1 1 0 1 20 0 0) (awhile-now "2042-12-07" :d 'lv :n "2041-11-04T11:24:42" :u "YMD") => '(+ 1 1 0 2 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2041-11-04T11:24:42" :u "YWD") => '(+ 1 0 4 4 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2041-11-04T11:24:42" :u "YD") => '(+ 1 0 0 32 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2041-11-04T11:24" :c 2 :u "YMD") => '(+ 1 1 0 0 0 0 0) ;;;;; With and without sign information (awhile-now "2042-12-07" :d 'lv :n "2042-12-04") => '(+ 0 0 0 3 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2042-12-07") => '(+ 0 0 0 0 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2042-12-10") => '(- 0 0 0 3 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2042-12-04" :S 0) => '(nil 0 0 0 3 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2042-12-07" :S 0) => '(nil 0 0 0 0 0 0 0) (awhile-now "2042-12-07" :d 'lv :n "2042-12-10" :S 0) => '(nil 0 0 0 3 0 0 0) ;;;; Display list of keys and values (awhile-now "2042-12-07" :d 'lp :n "2042-12-07") => '(S + Y 0 M 0 W 0 D 0 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2042-12-02") => '(S + Y 0 M 0 W 0 D 5 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2042-12-02T08") => '(S + Y 0 M 0 W 0 D 4 h 16 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2042-12-12") => '(S - Y 0 M 0 W 0 D 5 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2042-12-12T08") => '(S - Y 0 M 0 W 0 D 5 h 8 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2041-11-04T11:24:42") => '(S + Y 1 M 1 W 0 D 1 h 20 m 5 s 18) (awhile-now "2042-12-07" :d 'lp :n "2041-11-04T11:24:42" :c 4) => '(S + Y 1 M 1 W 0 D 1 h 20 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2041-11-04T11:24:42" :u "YMD") => '(S + Y 1 M 1 W 0 D 2 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2041-11-04T11:24:42" :u "YWD") => '(S + Y 1 M 0 W 4 D 4 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2041-11-04T11:24:42" :u "YD") => '(S + Y 1 M 0 W 0 D 32 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2041-11-04T11:24" :c 2 :u "YMD") => '(S + Y 1 M 1 W 0 D 0 h 0 m 0 s 0) ;;;;; With and without sign information (awhile-now "2042-12-07" :d 'lp :n "2042-12-04") => '(S + Y 0 M 0 W 0 D 3 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2042-12-07") => '(S + Y 0 M 0 W 0 D 0 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2042-12-10") => '(S - Y 0 M 0 W 0 D 3 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2042-12-04" :S 0) => '(S nil Y 0 M 0 W 0 D 3 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2042-12-07" :S 0) => '(S nil Y 0 M 0 W 0 D 0 h 0 m 0 s 0) (awhile-now "2042-12-07" :d 'lp :n "2042-12-10" :S 0) => '(S nil Y 0 M 0 W 0 D 3 h 0 m 0 s 0)
From units to either other formats or back to seconds
The reverse operation.
awhile-rev (diff &rest rest)
Convert time difference DIFF to either seconds or an output format.
If REST is nil, return a cons cell whose car is the sign and the cdr is
the amount of seconds. If REST is non-nil, convert that cons cell to one
of the output formats accepted by awhile-now and awhile-abs.
Passing :n or :S to it has no effect; any of the other keys can be used.
DIFF must be in one of the four options of output formats offered by
awhile-now. So awhile-rev is a partial reverse of awhile-now:
- awhile-now: time1 time2 → seconds → diff
- awhile-rev: diff → seconds → (optional) diff
where any diff is in 'sl, 'ss, 'lv, or 'lp format.
;;;; Back to seconds ;;;;; from 'sl format (awhile-rev "now") => '(+ . 0) (awhile-rev "0 seconds") => '(nil . 0) (awhile-rev "2 hours") => '(nil . 7200) (awhile-rev "2 hours ago") => '(- . 7200) (awhile-rev "2 hours from now") => '(+ . 7200) (awhile-rev "2 hours, 46 minutes, 40 seconds") => '(nil . 10000) (awhile-rev "2 hours, 46 minutes, 40 seconds ago") => '(- . 10000) (awhile-rev "2 hours, 46 minutes, 40 seconds from now") => '(+ . 10000) ;;;;; from 'ss format (awhile-rev "+0s") => '(+ . 0) (awhile-rev "0s") => '(nil . 0) (awhile-rev "2h") => '(nil . 7200) (awhile-rev "-2h") => '(- . 7200) (awhile-rev "+2h") => '(+ . 7200) (awhile-rev "2h46m40s") => '(nil . 10000) (awhile-rev "-2h46m40s") => '(- . 10000) (awhile-rev "+2h46m40s") => '(+ . 10000) ;;;;; from 'lv format (awhile-rev '(+ 0 0 0 0 0 0 0)) => '(+ . 0) (awhile-rev '(nil 0 0 0 0 0 0 0)) => '(nil . 0) (awhile-rev '(nil 0 0 0 0 2 0 0)) => '(nil . 7200) (awhile-rev '(- 0 0 0 0 2 0 0)) => '(- . 7200) (awhile-rev '(+ 0 0 0 0 2 0 0)) => '(+ . 7200) (awhile-rev '(nil 0 0 0 0 2 46 40)) => '(nil . 10000) (awhile-rev '(- 0 0 0 0 2 46 40)) => '(- . 10000) (awhile-rev '(+ 0 0 0 0 2 46 40)) => '(+ . 10000) ;;;;; from 'lp format (awhile-rev '(S + Y 0 M 0 W 0 D 0 h 0 m 0 s 0)) => '(+ . 0) (awhile-rev '(S nil Y 0 M 0 W 0 D 0 h 0 m 0 s 0)) => '(nil . 0) (awhile-rev '(S nil Y 0 M 0 W 0 D 0 h 2 m 0 s 0 )) => '(nil . 7200) (awhile-rev '(S - Y 0 M 0 W 0 D 0 h 2 m 0 s 0 )) => '(- . 7200) (awhile-rev '(S + Y 0 M 0 W 0 D 0 h 2 m 0 s 0 )) => '(+ . 7200) (awhile-rev '(S nil Y 0 M 0 W 0 D 0 h 2 m 46 s 40)) => '(nil . 10000) (awhile-rev '(S - Y 0 M 0 W 0 D 0 h 2 m 46 s 40)) => '(- . 10000) (awhile-rev '(S + Y 0 M 0 W 0 D 0 h 2 m 46 s 40)) => '(+ . 10000) ;;;; To other formats ;;;;; to 'ss format (awhile-rev "+0s" :d 'ss) => "+0s" (awhile-rev "0s" :d 'ss) => "0s" (awhile-rev "2h" :d 'ss) => "2h" (awhile-rev "-2h" :d 'ss) => "-2h" (awhile-rev "+2h" :d 'ss) => "+2h" (awhile-rev "2h42m" :d 'ss) => "2h42m" (awhile-rev "-2h42m" :d 'ss) => "-2h42m" (awhile-rev "+2h42m" :d 'ss) => "+2h42m" (awhile-rev "2h42m" :d 'ss :c 1) => "3h" (awhile-rev "2h42m" :d 'ss :u"h") => "3h" (awhile-rev "-2h42m" :d 'ss :u"h") => "-3h" (awhile-rev "+2h42m" :d 'ss :u"h") => "+3h" (awhile-rev "+2h42m" :d 'ss :u"m") => "+162m" ;;;;; to 'sl format (awhile-rev "+0s" :d 'sl) => "now" (awhile-rev "0s" :d 'sl) => "0 seconds" (awhile-rev "2h" :d 'sl) => "2 hours" (awhile-rev "-2h" :d 'sl) => "2 hours ago" (awhile-rev "+2h" :d 'sl) => "2 hours from now" (awhile-rev "2h42m" :d 'sl) => "2 hours, 42 minutes" (awhile-rev "-2h42m" :d 'sl) => "2 hours, 42 minutes ago" (awhile-rev "+2h42m" :d 'sl) => "2 hours, 42 minutes from now" (awhile-rev "2h42m" :d 'sl :c 1) => "3 hours" (awhile-rev "2h42m" :d 'sl :u"h") => "3 hours" (awhile-rev "-2h42m" :d 'sl :u"h") => "3 hours ago" (awhile-rev "+2h42m" :d 'sl :u"h") => "3 hours from now" (awhile-rev "+2h42m" :d 'sl :u"m") => "162 minutes from now" ;;;;; to 'lv format (awhile-rev "+0s" :d 'lv) => '(+ 0 0 0 0 0 0 0) (awhile-rev "0s" :d 'lv) => '(nil 0 0 0 0 0 0 0) (awhile-rev "2h" :d 'lv) => '(nil 0 0 0 0 2 0 0) (awhile-rev "-2h" :d 'lv) => '(- 0 0 0 0 2 0 0) (awhile-rev "+2h" :d 'lv) => '(+ 0 0 0 0 2 0 0) (awhile-rev "2h42m" :d 'lv) => '(nil 0 0 0 0 2 42 0) (awhile-rev "-2h42m" :d 'lv) => '(- 0 0 0 0 2 42 0) (awhile-rev "+2h42m" :d 'lv) => '(+ 0 0 0 0 2 42 0) (awhile-rev "2h42m" :d 'lv :c 1) => '(nil 0 0 0 0 3 0 0) (awhile-rev "2h42m" :d 'lv :u"h") => '(nil 0 0 0 0 3 0 0) (awhile-rev "-2h42m" :d 'lv :u"h") => '(- 0 0 0 0 3 0 0) (awhile-rev "+2h42m" :d 'lv :u"h") => '(+ 0 0 0 0 3 0 0) (awhile-rev "+2h42m" :d 'lv :u"m") => '(+ 0 0 0 0 0 162 0) ;;;;; to 'lp format (awhile-rev "+0s" :d 'lp) => '(S + Y 0 M 0 W 0 D 0 h 0 m 0 s 0) (awhile-rev "0s" :d 'lp) => '(S nil Y 0 M 0 W 0 D 0 h 0 m 0 s 0) (awhile-rev "2h" :d 'lp) => '(S nil Y 0 M 0 W 0 D 0 h 2 m 0 s 0) (awhile-rev "-2h" :d 'lp) => '(S - Y 0 M 0 W 0 D 0 h 2 m 0 s 0) (awhile-rev "+2h" :d 'lp) => '(S + Y 0 M 0 W 0 D 0 h 2 m 0 s 0) (awhile-rev "2h42m" :d 'lp) => '(S nil Y 0 M 0 W 0 D 0 h 2 m 42 s 0) (awhile-rev "-2h42m" :d 'lp) => '(S - Y 0 M 0 W 0 D 0 h 2 m 42 s 0) (awhile-rev "+2h42m" :d 'lp) => '(S + Y 0 M 0 W 0 D 0 h 2 m 42 s 0) (awhile-rev "2h42m" :d 'lp :c 1) => '(S nil Y 0 M 0 W 0 D 0 h 3 m 0 s 0) (awhile-rev "2h42m" :d 'lp :u"h") => '(S nil Y 0 M 0 W 0 D 0 h 3 m 0 s 0) (awhile-rev "-2h42m" :d 'lp :u"h") => '(S - Y 0 M 0 W 0 D 0 h 3 m 0 s 0) (awhile-rev "+2h42m" :d 'lp :u"h") => '(S + Y 0 M 0 W 0 D 0 h 3 m 0 s 0) (awhile-rev "+2h42m" :d 'lp :u"m") => '(S + Y 0 M 0 W 0 D 0 h 0 m 162 s 0)
From ISO 8601 durations and intervals
These separate functions deal with ISO 8601 durations and intervals.
awhile-secs-durn (durn &optional before)
Convert ISO 8601 duration DURN to signed seconds relative to Epoch.
If BEFORE is nil, convert DURN to seconds since the Epoch.
If BEFORE is non-nil, convert DURN to seconds before the Epoch.
This is like awhile-diff-durn, but taking the duration from the Epoch
instead of from now. These results may differ: one month (P1M) from the
Epoch is 31 days, or 2678400 seconds; one month from now may be 28, 29,
30, or 31 days, depending on the date.
(awhile-secs-durn "P1M") => 2678400 ; 31 days ;; 1 hour, 1 minute, 1 second (awhile-secs-durn "PT1H1M1S") => 3661 (awhile-secs-durn "PT1H1M1S" t) => -3661 (awhile-secs-durn "P1D") => 86400 (awhile-secs-durn "P1D" t) => -86400 ;; That's right! The difference in absolute values below is 86400s, one day. ;; - To get to 1972-01-01, you add 730 days. ;; - To get to 1968-01-01, you subtract 731 days: it includes 1968-02-29. ;; ;; Remember: "Y" here refers to a change in the date's year number, whose ;; number of days depend on the reference year; whereas "Y" in awhile's ;; output format is fixed as 365.25 days. Same goes for month. (awhile-secs-durn "P2Y") => 63072000 (awhile-secs-durn "P2Y" t) => -63158400
awhile-diff-durn (durn &optional before)
Convert ISO 8601 duration DURN to signed seconds relative to now.
If BEFORE is nil, convert DURN to seconds from now.
If BEFORE is non-nil, convert DURN to seconds ago.
See also: awhile-secs-durn.
;; These tests might fail when run close to changes in DST. ;; Incrementing the date's day would then add 23h or 25h... (awhile-diff-durn "P1D") => 86400 (awhile-diff-durn "PT1h") => 3600 (awhile-diff-durn "PT1m") => 60 (awhile-diff-durn "PT1s") => 1
awhile-diff-ival (ival)
Convert ISO 8601 interval IVAL to signed seconds.
;;;; The three interval formats are accepted (awhile-diff-ival "2042-01-01T00Z/2042-01-02T00Z") => 86400 (awhile-diff-ival "2042-01-02T00Z/2042-01-01T00Z") => -86400 (awhile-diff-ival "2042-01-01T00Z/P1D") => 86400 (awhile-diff-ival "P1D/2042-01-01T00Z") => -86400 (awhile-diff-ival "2042-01-01T23Z/P1M") => 2678400 ; 31 days ;; 1 hour, 1 minute, 1 second (awhile-diff-ival "2042-01-01T23Z/PT1H1M1S") => 3661 (awhile-diff-ival "PT1H1M1S/2042-01-01T23Z") => -3661 ;; That's right! The difference in absolute values below is 86400s, one day. ;; - To get to 2044-01-01, you add 730 days. ;; - To get to 2040-01-01, you subtract 731 days: it includes 2040-02-29. ;; ;; Remember: "Y" here refers to a change in the date's year number, whose ;; number of days depend on the reference year; whereas "Y" in awhile's ;; output format is fixed as 365.25 days. Same goes for month. (awhile-diff-ival "2042-01-01T00Z/P2Y") => 63072000 (awhile-diff-ival "P2Y/2042-01-01T00Z") => -63158400
awhile-abs-durn (durn &optional before &rest rest)
Convert ISO 8601 duration DURN to awhile-abs format.
If BEFORE is nil, convert DURN to seconds since the Epoch.
If BEFORE is non-nil, convert DURN to seconds before the Epoch.
For the meaning of REST, see awhile-now.
(awhile-abs-durn "P1M" nil :u"D" :d 'ss) => "31D" (awhile-abs-durn "P1M" nil :u"D") => "31 days" ;; 1 hour, 1 minute, 1 second (awhile-abs-durn "PT1H1M1S") => "1 hour, 1 minute, 1 second" (awhile-abs-durn "PT1H1M1S" t) => "1 hour, 1 minute, 1 second" (awhile-abs-durn "P1D") => "1 day" (awhile-abs-durn "P1D" t) => "1 day" ;; That's right! ;; - To get to 1972-01-01, you add 730 days. ;; - To get to 1968-01-01, you subtract 731 days: it includes 1968-02-29. ;; ;; Remember: "Y" here refers to a change in the date's year number, whose ;; number of days depend on the reference year; whereas "Y" in awhile's ;; output format is fixed as 365.25 days. Same goes for month. (awhile-abs-durn "P2Y" nil :u"D") => "730 days" (awhile-abs-durn "P2Y" t :u"D") => "731 days"
awhile-now-durn (durn &optional before &rest rest)
Convert ISO 8601 duration DURN to awhile-now format.
If BEFORE is nil, convert DURN to seconds from now.
If BEFORE is non-nil, convert DURN to seconds ago.
For the meaning of REST, see awhile-now.
;; These tests might fail when run close to changes in DST. ;; Incrementing the date's day would then add 23h or 25h... ;;; Different output formats ;;;; Display long string (awhile-now-durn "P1D") => "1 day from now" (awhile-now-durn "PT1h") => "1 hour from now" (awhile-now-durn "PT1m") => "1 minute from now" (awhile-now-durn "PT1s") => "1 second from now" ;; (awhile-now-durn "P1D" t) => "1 day ago" (awhile-now-durn "PT1h" t) => "1 hour ago" (awhile-now-durn "PT1m" t) => "1 minute ago" (awhile-now-durn "PT1s" t) => "1 second ago" ;; (awhile-now-durn "P1D" nil :S 0) => "1 day" (awhile-now-durn "PT1h" nil :S 0) => "1 hour" (awhile-now-durn "PT1m" nil :S 0) => "1 minute" (awhile-now-durn "PT1s" nil :S 0) => "1 second" ;;;; Display short string (awhile-now-durn "P1D" nil :d 'ss) => "+1D" (awhile-now-durn "PT1h" nil :d 'ss) => "+1h" (awhile-now-durn "PT1m" nil :d 'ss) => "+1m" (awhile-now-durn "PT1s" nil :d 'ss) => "+1s" ;; (awhile-now-durn "P1D" t :d 'ss) => "-1D" (awhile-now-durn "PT1h" t :d 'ss) => "-1h" (awhile-now-durn "PT1m" t :d 'ss) => "-1m" (awhile-now-durn "PT1s" t :d 'ss) => "-1s" ;; (awhile-now-durn "P1D" nil :d 'ss :S 0) => "1D" (awhile-now-durn "PT1h" nil :d 'ss :S 0) => "1h" (awhile-now-durn "PT1m" nil :d 'ss :S 0) => "1m" (awhile-now-durn "PT1s" nil :d 'ss :S 0) => "1s" ;;;; Display list of values (awhile-now-durn "P1D" nil :d 'lv) => '(+ 0 0 0 1 0 0 0) (awhile-now-durn "PT1h" nil :d 'lv) => '(+ 0 0 0 0 1 0 0) (awhile-now-durn "PT1m" nil :d 'lv) => '(+ 0 0 0 0 0 1 0) (awhile-now-durn "PT1s" nil :d 'lv) => '(+ 0 0 0 0 0 0 1) ;; (awhile-now-durn "P1D" t :d 'lv) => '(- 0 0 0 1 0 0 0) (awhile-now-durn "PT1h" t :d 'lv) => '(- 0 0 0 0 1 0 0) (awhile-now-durn "PT1m" t :d 'lv) => '(- 0 0 0 0 0 1 0) (awhile-now-durn "PT1s" t :d 'lv) => '(- 0 0 0 0 0 0 1) ;; (awhile-now-durn "P1D" nil :d 'lv :S 0) => '(nil 0 0 0 1 0 0 0) (awhile-now-durn "PT1h" nil :d 'lv :S 0) => '(nil 0 0 0 0 1 0 0) (awhile-now-durn "PT1m" nil :d 'lv :S 0) => '(nil 0 0 0 0 0 1 0) (awhile-now-durn "PT1s" nil :d 'lv :S 0) => '(nil 0 0 0 0 0 0 1) ;;;; Display list of keys and values (awhile-now-durn "P1D" nil :d 'lp) => '(S + Y 0 M 0 W 0 D 1 h 0 m 0 s 0) (awhile-now-durn "PT1h" nil :d 'lp) => '(S + Y 0 M 0 W 0 D 0 h 1 m 0 s 0) (awhile-now-durn "PT1m" nil :d 'lp) => '(S + Y 0 M 0 W 0 D 0 h 0 m 1 s 0) (awhile-now-durn "PT1s" nil :d 'lp) => '(S + Y 0 M 0 W 0 D 0 h 0 m 0 s 1) ;; (awhile-now-durn "P1D" t :d 'lp) => '(S - Y 0 M 0 W 0 D 1 h 0 m 0 s 0) (awhile-now-durn "PT1h" t :d 'lp) => '(S - Y 0 M 0 W 0 D 0 h 1 m 0 s 0) (awhile-now-durn "PT1m" t :d 'lp) => '(S - Y 0 M 0 W 0 D 0 h 0 m 1 s 0) (awhile-now-durn "PT1s" t :d 'lp) => '(S - Y 0 M 0 W 0 D 0 h 0 m 0 s 1) ;; (awhile-now-durn "P1D" nil :d 'lp :S 0) => '(S nil Y 0 M 0 W 0 D 1 h 0 m 0 s 0) (awhile-now-durn"PT1h" nil :d 'lp :S 0) => '(S nil Y 0 M 0 W 0 D 0 h 1 m 0 s 0) (awhile-now-durn"PT1m" nil :d 'lp :S 0) => '(S nil Y 0 M 0 W 0 D 0 h 0 m 1 s 0) (awhile-now-durn"PT1s" nil :d 'lp :S 0) => '(S nil Y 0 M 0 W 0 D 0 h 0 m 0 s 1)
awhile-now-ival (ival &rest rest)
Convert ISO 8601 interval IVAL to awhile-now format.
For the meaning of REST, see awhile-now.
;;;; The three interval formats are accepted (awhile-now-ival "2042-01-01T00Z/2042-01-02T00Z") => "1 day from now" (awhile-now-ival "2042-01-02T00Z/2042-01-01T00Z") => "1 day ago" (awhile-now-ival "2042-01-01T00Z/2042-01-02T00Z" :S 0) => "1 day" (awhile-now-ival "2042-01-02T00Z/2042-01-01T00Z" :S 0) => "1 day" (awhile-now-ival "2042-01-01T00Z/P1D") => "1 day from now" (awhile-now-ival "P1D/2042-01-01T00Z") => "1 day ago" (awhile-now-ival "2042-01-01T00Z/P1D" :S 0) => "1 day" (awhile-now-ival "P1D/2042-01-01T00Z" :S 0) => "1 day" (awhile-now-ival "2042-01-01T23Z/P1M" :u"D") => "31 days from now" (awhile-now-ival "2042-01-01T23Z/P1M" :u"D" :S 0) => "31 days" (awhile-now-ival "2042-01-01/PT1H1M1S") => "1 hour, 1 minute, 1 second from now" (awhile-now-ival "PT1H1M1S/2042-01-01") => "1 hour, 1 minute, 1 second ago" (awhile-now-ival "2042-01-01/PT1H1M1S" :S 0) => "1 hour, 1 minute, 1 second" (awhile-now-ival "PT1H1M1S/2042-01-01" :S 0) => "1 hour, 1 minute, 1 second" ;; That's right! ;; - To get to 2044-01-01, you add 730 days. ;; - To get to 2040-01-01, you subtract 731 days: it includes 2040-02-29. ;; ;; Remember: "Y" here refers to a change in the date's year number, whose ;; number of days depend on the reference year; whereas "Y" in awhile's ;; output format is fixed as 365.25 days. Same goes for month. (awhile-now-ival "2042-01-01T00Z/P2Y" :u"Dhms") => "730 days from now" (awhile-now-ival "P2Y/2042-01-01T00Z" :u"Dhms") => "731 days ago" (awhile-now-ival "2042-01-01T00Z/P2Y" :u"Dhms" :S 0) => "730 days" (awhile-now-ival "P2Y/2042-01-01T00Z" :u"Dhms" :S 0) => "731 days" (awhile-now-ival "2042-01-01T00Z/P1D") => "1 day from now" (awhile-now-ival "P1D/2042-01-01T00Z") => "1 day ago" (awhile-now-ival "2042-01-01T00Z/P1D" :S 0) => "1 day" ;;; Different output formats ;;;; Display long string (awhile-now-ival "2042-01-01T00Z/P3D") => "3 days from now" (awhile-now-ival "P3D/2042-01-01T00Z") => "3 days ago" (awhile-now-ival "2042-01-01T00Z/P3D" :S 0) => "3 days" (awhile-now-ival "2042-01-01T00Z/P3D" :u"hm") => "72 hours from now" (awhile-now-ival "P3D/2042-01-01T00Z" :u"hm") => "72 hours ago" (awhile-now-ival "2042-01-01T00Z/P3D" :u"hm" :S 0) => "72 hours" ;;;; Display short string (awhile-now-ival "2042-01-01T00Z/P3D" :d 'ss) => "+3D" (awhile-now-ival "P3D/2042-01-01T00Z" :d 'ss) => "-3D" (awhile-now-ival "2042-01-01T00Z/P3D" :d 'ss :S 0) => "3D" (awhile-now-ival "2042-01-01T00Z/P3D" :d 'ss :u"hm") => "+72h" (awhile-now-ival "P3D/2042-01-01T00Z" :d 'ss :u"hm") => "-72h" (awhile-now-ival "2042-01-01T00Z/P3D" :d 'ss :u"hm" :S 0) => "72h" ;;;; Display list of values (awhile-now-ival "2042-01-01T00Z/P3D" :d 'lv) => '(+ 0 0 0 3 0 0 0) (awhile-now-ival "P3D/2042-01-01T00Z" :d 'lv) => '(- 0 0 0 3 0 0 0) (awhile-now-ival "2042-01-01T00Z/P3D" :d 'lv :S 0) => '(nil 0 0 0 3 0 0 0) (awhile-now-ival "2042-01-01T00Z/P3D" :d 'lv :u"hm") => '(+ 0 0 0 0 72 0 0) (awhile-now-ival "P3D/2042-01-01T00Z" :d 'lv :u"hm") => '(- 0 0 0 0 72 0 0) (awhile-now-ival "2042-01-01T00Z/P3D" :d 'lv :u"hm" :S 0) => '(nil 0 0 0 0 72 0 0) ;;;; Display list of keys and values (awhile-now-ival "2042-01-01T00Z/P3D" :d 'lp) => '(S + Y 0 M 0 W 0 D 3 h 0 m 0 s 0) (awhile-now-ival "P3D/2042-01-01T00Z" :d 'lp) => '(S - Y 0 M 0 W 0 D 3 h 0 m 0 s 0) (awhile-now-ival "2042-01-01T00Z/P3D" :d 'lp :S 0) => '(S nil Y 0 M 0 W 0 D 3 h 0 m 0 s 0) (awhile-now-ival "2042-01-01T00Z/P3D" :d 'lp :u"hm") => '(S + Y 0 M 0 W 0 D 0 h 72 m 0 s 0) (awhile-now-ival "P3D/2042-01-01T00Z" :d 'lp :u"hm") => '(S - Y 0 M 0 W 0 D 0 h 72 m 0 s 0) (awhile-now-ival "2042-01-01T00Z/P3D" :d 'lp :u"hm" :S 0) => '(S nil Y 0 M 0 W 0 D 0 h 72 m 0 s 0)
Commands
See README
Commands to open awhile's README.org. Optionally, find things in it.
awhile-see-readme (&optional heading narrow)
Open awhile's README.org file.
Search for the file in awhile.el's directory.
If found, open it read-only.
If optional argument HEADING is passed, try to navigate to the
heading after opening it. HEADING should be a string.
If optional argument NARROW is non-nil, narrow to that heading.
This argument has no effect if HEADING is nil or not found.
Contributing
See my page Software for information about how to contribute to any of my Emacs packages.
See also
Other packages
FromNow is my Bash implementation of the same idea. I wrote it first, then thought I might as well translate it and adapt it to Emacs Lisp — so here we are.
You can integrate awhile's examples into Help or Helpful buffers with Democratize, which also offers examples from xht, dash, s, f, and native Emacs libraries.
License
This project follows the REUSE Specification (FAQ), which in turn is built upon SPDX.
Therefore, license and copyright information can be found in:
- each file's comment header, or
- an adjacent file of the same name with the additional extension
.license, or - the
.reuse/dep5file
The full text of the licenses can be found in the LICENSES subdirectory.
awhile.el
Structure
;;; awhile.el --- Time difference from now in different formats -*- lexical-binding: t -*- ;;; Commentary: ;;;; See the README for more information ;;; Code: ;;;; Libraries ;;;; Package metadata ;;;; Customizable variables ;;;; Other variables ;;;;; Epoch ;;;;; Keys ;;;;; Values ;;;;; Plists ;;;;; Regular expressions ;;;; Functions ;;;;; Description macro ;;;;; Overview ;;;;; Conversions ;;;;;; From times to their difference in seconds ;;;;;; From times to their difference in time units — many formats ;;;;;; From units to either other formats or back to seconds ;;;;;; From ISO 8601 durations and intervals ;;;;; Internal ;;;;;; From times to their difference in seconds ;;;;;; From times to their difference in time units — many formats ;;;;;; From units to either other formats or back to seconds ;;;;;;; From units to seconds ;;;;;;; From string units to list of values ;;;;;;; Predicates ;;;;;; From ISO 8601 durations and intervals ;;;; Commands ;;;;; See README ;;;; Wrapping up ;;; awhile.el ends here
Contents
;;; awhile.el --- Time difference from now in different formats -*- lexical-binding: t -*- ;; SPDX-FileCopyrightText: © flandrew <https://flandrew.srht.site/listful> ;;--------------------------------------------------------------------------- ;; Author: flandrew ;; Created: 2025-12-07 ;; Updated: 2025-12-17 ;; Keywords: extensions, lisp ;; Homepage: <https://flandrew.srht.site/listful/software.html> ;;--------------------------------------------------------------------------- ;; Package-Version: 0.3.0 ;; Package-Requires: ((emacs "27.1") (dash "2.15")) ;;--------------------------------------------------------------------------- ;; SPDX-License-Identifier: GPL-3.0-or-later ;; This file is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published ;; by the Free Software Foundation, either version 3 of the License, ;; or (at your option) any later version. ;; ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; ;; Awhile calculates the difference between two moments in time and then ;; displays it in one of a few available formats. When a second moment is not ;; given, it's assumed to be "now". ;; ;; You can customize how you want to display a time difference: long or short, ;; exact or fuzzy, which units to use, etc. ;; ;; You can also directly convert seconds to one of these formats. ;; ;; And you can convert these formatted outputs back to seconds, or to one of ;; the other formats. ;; ;;;; See the README for more information ;; ;; Open it easily with: ;; (find-file-read-only "README.org") <--- C-x C-e here¹ ;; ;; or from any buffer: ;; M-x awhile-see-readme ;; ;; or read it online: ;; <https://flandrew.srht.site/listful/sw-emacs-awhile.html> ;; ;; ¹ or the key that ‘eval-last-sexp’ is bound to, if not C-x C-e. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Code: ;;;; Libraries (require 'rx) (require 'dash) (require 'iso8601) (require 'lisp-mnt) ; ‘lm-summary’, ‘lm-homepage’, ‘lm-version’, ‘lm-header’ (require 'time-date) ; ‘date-to-time’ ;;;; Package metadata (defvar awhile--name "Awhile") (defvar awhile--dot-el (format "%s.el" (file-name-sans-extension (eval-and-compile (or load-file-name buffer-file-name))))) (defvar awhile--readme-org (expand-file-name "README.org" (file-name-directory awhile--dot-el))) (defvar awhile--summary (lm-summary awhile--dot-el)) (defvar awhile--homepage (lm-homepage awhile--dot-el)) (defvar awhile--version (lm-with-file awhile--dot-el (or (lm-header "package-version") (lm-version)))) ;;;; Customizable variables (defgroup awhile nil (format "%s." awhile--summary) :group 'extensions :group 'lisp :link '(emacs-library-link :tag "Lisp file" "awhile.el") :link `(file-link :tag "README.org" ,awhile--readme-org) :link `(url-link :tag "Homepage" ,awhile--homepage)) ;;;; Other variables ;;;;; Epoch (defconst awhile-epoch "1970-01-01T00:00:00+00:00" "Unix Epoch as ISO 8601 string.") ;;;;; Keys (defvar awhile--units-lst '(Y M W D h m s) "All time units available, as list.") (defvar awhile--units-str (mapconcat #'symbol-name awhile--units-lst "") "All time units available, as string.") (defvar awhile--units-len (length awhile--units-lst) "Length of list of all time units available.") ;;;;; Values (defvar awhile--units-sec-lst (list (/ (* 60 60 24 (1+ (* 365 4))) 4) ; average over 4 years (/ (* 60 60 24 (1+ (* 365 4))) 48) ; average over 4 years ( * 60 60 24 7) ( * 60 60 24) ( * 60 60) ( * 60) ( * 1)) "Number of seconds in each time unit. A list.") (defvar awhile--units-names-en-lst '(year month week day hour minute second) "Names of each time unit, in English. A list.") ;;;;; Plists (defvar awhile--units-ini-pl (-interleave (cons 'S awhile--units-lst) (cons '+ (make-list awhile--units-len 0))) "Initialization amounts of 0 for each time unit. A plist. The key \\='S (sign) is also added, with initial value \\='+.") (defvar awhile--units-sec-pl (-interleave awhile--units-lst awhile--units-sec-lst) "Number of seconds in each time unit. A plist.") (defvar awhile--units-names-en-pl (-interleave awhile--units-lst awhile--units-names-en-lst) "Names of each time unit, in English. A plist.") ;;;;; Regular expressions (defvar awhile--sl-rx-1 (--> (regexp-opt (mapcar #'symbol-name awhile--units-names-en-lst)) (format "[0-9]+ [%s]s*" it) (format "\\(%s, \\)*%s%s?" it it (regexp-opt '(" ago" " from now"))) (format "^now$\\|%s" it)) "Regular expression to match \\='sl format. Quick, for triage.") (defvar awhile--ss-rx-1 (format "^[+-]*\\([0-9]+[%s]\\)+$" awhile--units-str) "Regular expression to match \\='ss format. Quick, for triage.") (defvar awhile--sl-rx-2 (rx (? (: (group (+ num)) " year" (? "s") (? ", "))) (? (: (group (+ num)) " month" (? "s") (? ", "))) (? (: (group (+ num)) " week" (? "s") (? ", "))) (? (: (group (+ num)) " day" (? "s") (? ", "))) (? (: (group (+ num)) " hour" (? "s") (? ", "))) (? (: (group (+ num)) " minute" (? "s") (? ", "))) (? (: (group (+ num)) " second" (? "s"))) (group (? (or " ago" " from now" "now")))) "Regular expression to match \\='sl format. For extraction of values.") (defvar awhile--ss-rx-2 (rx (group (? (or "+" "-"))) (? (: (group (+ num)) "Y")) (? (: (group (+ num)) "M")) (? (: (group (+ num)) "W")) (? (: (group (+ num)) "D")) (? (: (group (+ num)) "h")) (? (: (group (+ num)) "m")) (? (: (group (+ num)) "s"))) "Regular expression to match \\='ss format. For extraction of values.") ;;;; Functions ;;;;; Description macro (defmacro awhile--describe (str) "Describe with string STR the contents under this heading. Think of it as a docstring for the headings of your elisp files. For the full docstring, look for ‘orgreadme-fy-describe’ in the package ‘orgreadme-fy’." (declare (indent 0)) (unless (stringp str) (user-error "‘awhile--describe’ must receive a string"))) ;;;;; Overview (awhile--describe "Here's an overview of this library's functions through examples.") (defun awhile--overview-examples () "(This function does nothing, except facilitate the display of examples.)" (ignore)) ;;;;; Conversions (awhile--describe "Conversion from time T1 can be summarized based on the other time used (T2) and the format of the output: | T2 | Seconds (integer) | Units (string or list) | |--------------+-------------------+------------------------| | epoch | ‘awhile-secs’ | ‘awhile-abs’ | | now or other | ‘awhile-diff’ | ‘awhile-now’ | The ‘awhile-rev’ function converts from a units output to either seconds or other variation of units output. The ISO 8601 duration and intervals functions follow a similar logic: | T2 | Seconds (integer) | Units (string or list) | |--------------+--------------------+------------------------| | epoch | ‘awhile-secs-durn’ | ‘awhile-abs-durn’ | | now or other | ‘awhile-diff-durn’ | ‘awhile-now-durn’ | | | ‘awhile-diff-ival’ | ‘awhile-now-ival’ |") ;;;;;; From times to their difference in seconds (awhile--describe "These functions calculate time differences in seconds. The Unix epoch referenced by functions is 1970-01-01T00:00:00 UTC.") (defun awhile-secs (&optional time) "Convert TIME to seconds since the Epoch, rounded to integer. If TIME is: - a string, convert it assuming common timestamp format. - nil, convert current time. - a list, convert it after ensuring it's in Lisp timestamp format; this may involve encoding TIME. - a number, assume it's already Epoch seconds." (round (awhile--secs-float time))) (defun awhile-diff (time1 &optional time2) "Subtract TIME2 from TIME1. Return Epoch seconds, rounded to integer. If TIME2 is nil, use current time." (round (awhile--diff-float time1 time2))) ;;;;;; From times to their difference in time units — many formats (awhile--describe "The main functions are: - ‘awhile-now’: difference between a date and either now or other date - ‘awhile-abs’: difference between a date and the beginning of the Epoch Both offer the same variety of output formats. The latter can also be used for conversion of seconds to these formats. Let's talk about time units. Minutes, hours, days, and weeks are precise units. We can agree that: - 1 minute = 60 seconds - 1 hour = 60 minutes - 1 day = 24 hours - 1 week = 7 days Months and years are trickier. - Months vary from 28 to 31 days. - Years usually have 365 days. Except when they have 366. How to deal with these? One way is to follow the date. From 2042-05-07 to 2042-06-07 is one month, and from 2042-06-07 to 2042-07-07 is one month again, and to 2043-07-07 is one year. The advantage of this approach is that it matches our intuition about dates. The disadvantage is that we end up with time units that are not constant, as they depend on context: “2 months ago” when in March of a non-leap year would mean 59 days ago, whereas “2 months ago” when in September would mean 62 days. This is unsatisfactory. Awhile averages the units over a four-year period: - 1 year = (365×4 + 1) ÷ 4 = 365.25 days = 365 days, 6 hours - 1 month = (365×4 + 1) ÷ 48 = 30.4375 days = 30 days, 10 hours, 30 minutes Twelve months exactly fit into one year. With this, fuzzy relative times will be more precise over longer periods. And whenever you see “year” or “month”, the elapsed time that they represent will be predictably the above. Think “average year” and “average month”. If you dislike this, avoid these units. Use :u \"WDhms\" or :u \"Dhms\". The Unix epoch referenced by functions is 1970-01-01T00:00:00 UTC.") (defun awhile-abs (&optional time &rest rest) "Display TIME in any of various output formats. Similar to ‘awhile-now’, but instead of considering TIME relative to now or to some selected reference date, it uses the beginning of the Epoch as reference. Furthermore, the result has no sign information. So this is ‘awhile-now’ with :n 0 :S 0. Passing :n or :S to it has no effect; any of the other keys can be used. If TIME is nil, use current time. And since TIME can be passed as seconds since the Epoch, this function offers a convenient way to simply convert an amount of seconds to one of the display formats offered by ‘awhile-now’." (apply #'awhile-now (or time (current-time)) :n 0 :S 0 rest)) (defun awhile-now (time &rest rest) "Display the difference between TIME and either now or other date. REST may contain any or all of these keys: | Key | Description | Default | |-----+--------------------------------+-----------------------| | :d | format to display the results | String, long, English | | :n | result will be relative to it | now | | :u | a string of valid units to use | YMWDhms | | :c | maximum number of units to use | 7 (all) | | :S | whether to include the sign | t | To exclude references to sign, use :S 0. The valid options for key :d are: - \\='sl = String, long format, English - \\='ss = String, short format, one-letter units - \\='lv = List with only the values - \\='lp = Plist of units and their values These output options change in form, but represent the same data. The units “month” and “year” are averaged over a 4-year period: - 1 year = 365 days, 6 hours - 1 month = 30 days, 10 hours, 30 minutes Twelve months exactly fit into one year. TIME may be given in different formats. - Strings can be variously passed as, for example: <2042-12-12 Fri 11:24> [2042-12-12 Fri 11:24] Fri Dec 12 11:24 2042 2042-12-12T11:24:42 2042-12-12 11:24:42 2042-12-12 11:24 2042-W50-5T11:24 2042-346T11:24 In all the above, local time is assumed. Otherwise, add timezone: 2042-12-12T11:24Z 2042-12-12T11:24+00 2042-12-12T11:24 UTC 2042-12-12 11:24 GMT 2042-12-12T11:24:42-08 2042-12-12T11:24:42 PST 2042-12-12 11:24:42 PST - Cons cells are assumed to be in a valid Lisp timestamp format, such as: (35126 7546) (35126 7546 543433) (35126 7546 543433 703000) (2302025082543433 . 1000000) They can also be passed decoded: (42 24 11 12 12 2042 5 nil -28800) - Numbers are assumed to be seconds since the Epoch: 2302025082 Floats are fine, too: 2302025082.5434337 This list is not exhaustive: a few other formats may be accepted." (-let* (((&plist :d :u :c :n :S) rest) (secs (awhile-diff time n)) lp) (unless d (setq d 'sl)) (unless u (setq u awhile--units-str)) (unless c (setq c (length u))) (setq lp (awhile--dist secs u c)) (setq S (not (equal S 0))) (awhile--display d lp S))) ;;;;;; From units to either other formats or back to seconds (awhile--describe "The reverse operation.") (defun awhile-rev (diff &rest rest) "Convert time difference DIFF to either seconds or an output format. If REST is nil, return a cons cell whose car is the sign and the cdr is the amount of seconds. If REST is non-nil, convert that cons cell to one of the output formats accepted by ‘awhile-now’ and ‘awhile-abs’. Passing :n or :S to it has no effect; any of the other keys can be used. DIFF must be in one of the four options of output formats offered by ‘awhile-now’. So ‘awhile-rev’ is a partial reverse of ‘awhile-now’: - ‘awhile-now’: time1 time2 → seconds → diff - ‘awhile-rev’: diff → seconds → (optional) diff where any diff is in \\='sl, \\='ss, \\='lv, or \\='lp format." (--> (cond ((awhile--sl? diff) (awhile--sl-to-secs diff)) ((awhile--ss? diff) (awhile--ss-to-secs diff)) ((awhile--lv? diff) (awhile--lv-to-secs diff)) ((awhile--lp? diff) (awhile--lp-to-secs diff)) (:otherwise (error "Invalid input: %s" diff))) (if (not rest) it (-let* (((&plist :d :u :c) rest) ((S . secs) it) lp) (unless d (setq d 'sl)) (unless u (setq u awhile--units-str)) (unless c (setq c (length u))) (setq lp (awhile--dist secs u c)) (setf (cadr lp) S) (awhile--display d lp S))))) ;;;;;; From ISO 8601 durations and intervals (awhile--describe "These separate functions deal with ISO 8601 durations and intervals.") (defun awhile-secs-durn (durn &optional before) "Convert ISO 8601 duration DURN to signed seconds relative to Epoch. If BEFORE is nil, convert DURN to seconds since the Epoch. If BEFORE is non-nil, convert DURN to seconds before the Epoch. This is like ‘awhile-diff-durn’, but taking the duration from the Epoch instead of from now. These results may differ: one month (P1M) from the Epoch is 31 days, or 2678400 seconds; one month from now may be 28, 29, 30, or 31 days, depending on the date." (let ((ival (awhile--duration-to-interval awhile-epoch durn before))) (awhile-diff-ival ival))) (defun awhile-diff-durn (durn &optional before) "Convert ISO 8601 duration DURN to signed seconds relative to now. If BEFORE is nil, convert DURN to seconds from now. If BEFORE is non-nil, convert DURN to seconds ago. See also: ‘awhile-secs-durn’." (let ((ival (awhile--duration-to-interval nil durn before))) (awhile-diff-ival ival))) (defun awhile-diff-ival (ival) "Convert ISO 8601 interval IVAL to signed seconds." (-let [(t1 t2 _) (iso8601-parse-interval ival)] (if (string-match-p "\\`P" ival) (awhile-diff t1 t2) (awhile-diff t2 t1)))) (defun awhile-abs-durn (durn &optional before &rest rest) "Convert ISO 8601 duration DURN to ‘awhile-abs’ format. If BEFORE is nil, convert DURN to seconds since the Epoch. If BEFORE is non-nil, convert DURN to seconds before the Epoch. For the meaning of REST, see ‘awhile-now’." (let ((ival (awhile--duration-to-interval awhile-epoch durn before))) (apply #'awhile-now-ival ival :S 0 rest))) (defun awhile-now-durn (durn &optional before &rest rest) "Convert ISO 8601 duration DURN to ‘awhile-now’ format. If BEFORE is nil, convert DURN to seconds from now. If BEFORE is non-nil, convert DURN to seconds ago. For the meaning of REST, see ‘awhile-now’." (let ((ival (awhile--duration-to-interval nil durn before))) (apply #'awhile-now-ival ival rest))) (defun awhile-now-ival (ival &rest rest) "Convert ISO 8601 interval IVAL to ‘awhile-now’ format. For the meaning of REST, see ‘awhile-now’." (-let [(t1 t2 _) (iso8601-parse-interval ival)] (if (string-match-p "\\`P" ival) (apply #'awhile-now t1 :n t2 rest) (apply #'awhile-now t2 :n t1 rest)))) ;;;;; Internal ;;;;;; From times to their difference in seconds (defun awhile--secs-float (&optional time) "Convert TIME to seconds since the Epoch, as float. If TIME is: - a string, convert it assuming common timestamp format. - nil, convert current time. - a list, convert it after ensuring it's in Lisp timestamp format; this may involve encoding TIME. - a number, assume it's already Epoch seconds." (time-to-seconds (awhile--ensure-encoded time))) (defun awhile--ensure-encoded (&optional time) "Ensure TIME is encoded. Encode if not. Use a quick, cheap test. The goal is to decide whether TIME looks like an encoded object in one of the formats accepted by ‘format-time-string’; or if it looks like decoded time, in which case it must be encoded." (cond ((stringp time) (date-to-time time)) ((null time) (current-time)) ((listp time) (awhile--ensure-encoded-list time)) ((numberp time) time) (:otherwise (error "Invalid time: %s" time)))) (defun awhile--ensure-encoded-list (list) "Ensure non-empty LIST is time in encoded format. Encode if not." (let ((len (proper-list-p list))) (cond ;; Assume valid (TICKS . HZ) — already encoded ((null len) list) ;; Assume valid (HI LO US PS), (HI LO US), (HI LO) — already encoded ((<= 2 len 4) list) ;; Assume valid (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF) ;; or simpler (SEC MINUTE HOUR DAY MONTH YEAR) — must encode ((or (= 9 len) (= 6 len)) (apply #'encode-time (-replace 'nil 0 list))) (:otherwise (error "Invalid time: %s" list))))) (defun awhile--diff-float (time1 &optional time2) "Subtract TIME2 from TIME1. Return Epoch seconds, as float. If TIME2 is nil, use current time." (- (awhile--secs-float time1) (awhile--secs-float time2))) (defun awhile--dist (n &optional us cnt) "Distribute N seconds into the units given by time string US. Use no more than CNT units, starting at the highest one. If CNT is nil, use as many units as needed. If US is nil, use ‘awhile--units-str’. In the resulting list, \\='S is the sign: positive or negative." (let* ((se awhile--units-sec-pl) (pl (copy-sequence awhile--units-ini-pl)) (ul (awhile--normalize-us us)) (le (length ul)) (mx (if cnt (min cnt le) le)) (cn 0) (u) (v)) (when (< n 0) (plist-put pl 'S '-) (setq n (abs n))) (while (and ul (< cn mx)) (setq u (pop ul) v (plist-get se u)) (--> (prog1 (/ n v) (setq n (% n v))) (when (> it 0) (setq cn (1+ cn)) ;; Round the value of the last non-zero unit (and (or (null ul) (= cn mx)) (>= (* 2 n) v) (setq it (1+ it))) (plist-put pl u it)))) pl)) (defun awhile--normalize-us (&optional us) "Convert units strings US into a sorted list of valid units symbols. The list is uniq-ified, and then sorted from highest to lowest unit. Only units that are also present in ‘awhile--units-str’ will remain. Is US is nil, use ‘awhile--units-str’." (let ((aus (string-to-list awhile--units-str))) (--map (read (string it)) (if us (-intersection aus (string-to-list us)) aus)))) ;;;;;; From times to their difference in time units — many formats (defun awhile--display (d lp &optional S) "Convert plist LP into D display format. Unless S is nil, include sign information." (pcase-exhaustive d ('sl (awhile--display-sl lp S)) ('ss (awhile--display-ss lp S)) ('lv (awhile--display-lv lp S)) ('lp (awhile--display-lp lp S)))) (defun awhile--display-sl (lp &optional S) "Convert plist LP into SL display format. Unless S is nil, include sign information." (!cdr lp) (--> (pop lp) (when S (setq S it))) (let (k v) (--> (with-output-to-string (while lp (-setq (k v . lp) lp) (when (> v 0) (setq k (plist-get awhile--units-names-en-pl k)) (princ (format "%s %s%s, " v k (if (> v 1) "s" "")))))) (if (string-empty-p it) (if S "now" "0 seconds") (replace-regexp-in-string ", $" (pcase-exhaustive S ('+ " from now") ('- " ago") ('nil "")) it))))) (defun awhile--display-ss (lp &optional S) "Convert plist LP into SS display format. Unless S is nil, include sign information." (!cdr lp) (setq S (--> (pop lp) (if S it ""))) (let (k v) (--> (with-output-to-string (while lp (-setq (k v . lp) lp) (when (> v 0) (princ v) (princ k)))) (if (string-empty-p it) "0s" it) (format "%s%s" S it)))) (defun awhile--display-lv (lp &optional S) "Convert plist LP into LV display format. Unless S is nil, include sign information." (unless S (setf (cadr lp) nil)) (let (vs) (while lp (!cdr lp) (push (pop lp) vs)) (nreverse vs))) (defun awhile--display-lp (lp &optional S) "Convert plist LP into LP display format. Unless S is nil, include sign information." (unless S (setf (cadr lp) nil)) lp) ;;;;;; From units to either other formats or back to seconds ;;;;;;; From units to seconds (defun awhile--sl-to-secs (sl) "Convert long string SL into seconds. Return a cons whose car is the sign and cdr the amount of seconds." (-> sl awhile--sl-to-lv awhile--lv-to-secs)) (defun awhile--ss-to-secs (ss) "Convert short string SS into seconds. Return a cons whose car is the sign and cdr the amount of seconds." (-> ss awhile--ss-to-lv awhile--lv-to-secs)) (defun awhile--lv-to-secs (lv) "Convert list of values LV into seconds. Return a cons whose car is the sign and cdr the amount of seconds." (cons (car lv) (-sum (-zip-with #'* (cdr lv) awhile--units-sec-lst)))) (defun awhile--lp-to-secs (lp) "Convert plist LP into seconds. Return a cons whose car is the sign and cdr the amount of seconds." (cons (cadr lp) (-sum (--zip-with (if (numberp it) (* it other) 0) (cddr lp) awhile--units-sec-pl)))) ;;;;;;; From string units to list of values (defun awhile--sl-to-lv (sl) "Convert long string SL into LV." (--> (when (string-match awhile--sl-rx-2 sl) (--map (read (or (match-string it sl) "0")) (number-sequence 1 7))) (let ((S (pcase-exhaustive (match-string 8 sl) (" from now" '+) (" ago" '-) ("now" '+) ("" 'nil)))) (cons S it)))) (defun awhile--ss-to-lv (ss) "Convert short string SS into LV." (--> (when (string-match awhile--ss-rx-2 ss) (--map (read (or (match-string it ss) "0")) (number-sequence 2 8))) (let ((S (match-string 1 ss))) (cons (if (string-empty-p S) 'nil (read S)) it)))) ;;;;;;; Predicates (defun awhile--sl? (diff) "Does DIFF match \\='sl format?" (and (stringp diff) (string-match-p awhile--sl-rx-1 diff nil))) (defun awhile--ss? (diff) "Does DIFF match \\='ss format?" (and (stringp diff) (string-match-p awhile--ss-rx-1 diff nil))) (defun awhile--lv? (diff) "Does DIFF match \\='lv format?" (and (consp diff) (= (length diff) (1+ awhile--units-len)) (memq (car diff) '(nil + -)) (-all? #'natnump (cdr diff)))) (defun awhile--lp? (diff) "Does DIFF match \\='lp format?" (and (consp diff) (= (length diff) (* 2 (1+ awhile--units-len))) (let (ks vs) (while diff (push (pop diff) ks) (push (pop diff) vs)) (and (equal (nreverse ks) (cons 'S awhile--units-lst)) (awhile--lv? (nreverse vs)))))) ;;;;;; From ISO 8601 durations and intervals (defun awhile--duration-to-interval (time durn &optional before) "Convert ISO 8601 duration DURN to an interval. TIME can be in any format accepted by ‘awhile-now’. As in there, if nil, current time is used. The time will be converted to UTC. If BEFORE is non-nil, DURN comes before TIME in the string: subtracted. If BEFORE is nil, DURN comes after TIME in the string: added. The resulting interval is not validated." (let ((time (format-time-string "%FT%T%z" (awhile-secs time) 0))) (if before (concat durn "/" time) (concat time "/" durn)))) ;;;; Commands ;;;;; See README (awhile--describe "Commands to open awhile's README.org. Optionally, find things in it.") ;;;###autoload (defun awhile-see-readme (&optional heading narrow) "Open awhile's README.org file. Search for the file in awhile.el's directory. If found, open it read-only. If optional argument HEADING is passed, try to navigate to the heading after opening it. HEADING should be a string. If optional argument NARROW is non-nil, narrow to that heading. This argument has no effect if HEADING is nil or not found." (interactive) (let ((readme awhile--readme-org)) (if (file-exists-p readme) (let ((pr (make-progress-reporter (format "Opening %s ... " (abbreviate-file-name readme))))) (find-file-read-only readme) (when heading (awhile--goto-org-heading heading narrow)) (progress-reporter-done pr)) (message "Couldn't find %s's README.org" awhile--name)))) ;;;###autoload (defun awhile-see-news () "See the News in awhile's README.org file." (interactive) (awhile-see-readme "News" 'narrow) (awhile--display-org-subtree)) (defun awhile--display-org-subtree () "Selectively display org subtree." (let ((cmds '( outline-hide-subtree outline-show-children outline-next-heading outline-show-branches))) (and (equal (mapcar #'fboundp cmds) '(t t t t)) (mapc #'funcall cmds)))) (defun awhile--goto-org-heading (heading &optional narrow) "Navigate to org HEADING and optionally NARROW to it." (let* ((hrx (format "^[*]+ %s" heading)) (pos (save-match-data (save-excursion (save-restriction (widen) (goto-char (point-max)) (re-search-backward hrx nil t 1)))))) (when pos (widen) (goto-char pos) (recenter-top-bottom 1) (and (fboundp 'org-narrow-to-subtree) narrow (org-narrow-to-subtree)) (and (fboundp 'outline-show-subtree) (outline-show-subtree)) (and (fboundp 'org-flag-drawer) (save-excursion (forward-line 1) (org-flag-drawer t)))))) ;;;; Wrapping up (provide 'awhile) ;; Local Variables: ;; coding: utf-8 ;; indent-tabs-mode: nil ;; sentence-end-double-space: nil ;; outline-regexp: ";;;;* " ;; End: ;;; awhile.el ends here
📆 2025-W51-4📆 2025-12-18