[[https://melpa.org/#/ts][file:https://melpa.org/packages/ts-badge.svg]] [[https://stable.melpa.org/#/ts][file:https://stable.melpa.org/packages/ts-badge.svg]]
~ts~ is a date and time library for Emacs. It aims to be more convenient than patterns like ~(string-to-number (format-time-string "%Y"))~ by providing easy accessors, like ~(ts-year (ts-now))~.
To improve performance (significantly), formatted date parts are computed lazily rather than when a timestamp object is instantiated, and the computed parts are then cached for later access without recomputing. Behind the scenes, this avoids unnecessary ~(string-to-number (format-time-string...~ calls, which are surprisingly expensive.
Contents :PROPERTIES: :TOC: :include siblings :ignore this :END: :CONTENTS:
[[#examples][Examples]]
[[#usage][Usage]]
[[#tips][Tips]]
[[#changelog][Changelog]] :END:
Examples
Get parts of the current date:
;; When the current date is 2018-12-08 23:09:14 -0600: (ts-year (ts-now)) ;=> 2018 (ts-month (ts-now)) ;=> 12 (ts-day (ts-now)) ;=> 8 (ts-hour (ts-now)) ;=> 23 (ts-minute (ts-now)) ;=> 9 (ts-second (ts-now)) ;=> 14 (ts-tz-offset (ts-now)) ;=> "-0600"
(ts-dow (ts-now)) ;=> 6 (ts-day-abbr (ts-now)) ;=> "Sat" (ts-day-name (ts-now)) ;=> "Saturday"
(ts-month-abbr (ts-now)) ;=> "Dec" (ts-month-name (ts-now)) ;=> "December"
(ts-tz-abbr (ts-now)) ;=> "CST"
Increment the current date:
;; By 10 years: (list :now (ts-format) :future (ts-format (ts-adjust 'year 10 (ts-now)))) ;;=> ( :now "2018-12-15 22:00:34 -0600" ;; :future "2028-12-15 22:00:34 -0600")
;; By 10 years, 2 months, 3 days, 5 hours, and 4 seconds: (list :now (ts-format) :future (ts-format (ts-adjust 'year 10 'month 2 'day 3 'hour 5 'second 4 (ts-now)))) ;;=> ( :now "2018-12-15 22:02:31 -0600" ;; :future "2029-02-19 03:02:35 -0600")
What day of the week was 2 days ago?
(ts-day-name (ts-dec 'day 2 (ts-now))) ;=> "Thursday"
;; Or, with threading macros: (thread-last (ts-now) (ts-dec 'day 2) ts-day-name) ;=> "Thursday" (->> (ts-now) (ts-dec 'day 2) ts-day-name) ;=> "Thursday"
Get timestamp for this time last week:
(ts-unix (ts-adjust 'day -7 (ts-now))) ;;=> 1543728398.0
;; To confirm that the difference really is 7 days: (/ (- (ts-unix (ts-now)) (ts-unix (ts-adjust 'day -7 (ts-now)))) 86400) ;;=> 7.000000567521762
;; Or human-friendly as a list: (ts-human-duration (ts-difference (ts-now) (ts-dec 'day 7 (ts-now)))) ;;=> (:years 0 :days 7 :hours 0 :minutes 0 :seconds 0)
;; Or as a string: (ts-human-format-duration (ts-difference (ts-now) (ts-dec 'day 7 (ts-now)))) ;;=> "7 days"
;; Or confirm by formatting: (list :now (ts-format) :last-week (ts-format (ts-dec 'day 7 (ts-now)))) ;;=> ( :now "2018-12-08 23:31:37 -0600" ;; :last-week "2018-12-01 23:31:37 -0600")
Some accessors have aliases similar to ~format-time-string~ constructors:
(ts-hour (ts-now)) ;=> 0 (ts-H (ts-now)) ;=> 0
(ts-minute (ts-now)) ;=> 56 (ts-min (ts-now)) ;=> 56 (ts-M (ts-now)) ;=> 56
(ts-second (ts-now)) ;=> 38 (ts-sec (ts-now)) ;=> 38 (ts-S (ts-now)) ;=> 38
(ts-year (ts-now)) ;=> 2018 (ts-Y (ts-now)) ;=> 2018
(ts-month (ts-now)) ;=> 12 (ts-m (ts-now)) ;=> 12
(ts-day (ts-now)) ;=> 9 (ts-d (ts-now)) ;=> 9
Parse a string into a timestamp object and reformat it:
(ts-format (ts-parse "sat dec 8 2018 12:12:12")) ;=> "2018-12-08 12:12:12 -0600"
;; With a threading macro: (->> "sat dec 8 2018 12:12:12" ts-parse ts-format) ;;=> "2018-12-08 12:12:12 -0600"
Format the difference between two timestamps:
(ts-human-format-duration (ts-difference (ts-now) (ts-adjust 'day -400 'hour -2 'minute -1 'second -5 (ts-now)))) ;; => "1 years, 35 days, 2 hours, 1 minutes, 5 seconds"
;; Abbreviated: (ts-human-format-duration (ts-difference (ts-now) (ts-adjust 'day -400 'hour -2 'minute -1 'second -5 (ts-now))) 'abbr) ;; => "1y35d2h1m5s"
Parse an Org timestamp element directly from ~org-element-context~ and find the difference between it and now:
(with-temp-buffer (org-mode) (save-excursion (insert "<2015-09-24 Thu .+1d>")) (ts-human-format-duration (ts-difference (ts-now) (ts-parse-org-element (org-element-context))))) ;;=> "3 years, 308 days, 2 hours, 24 minutes, 21 seconds"
Parse an Org timestamp string (which has a repeater) and format the year and month:
;; Note the use of format' rather than
concat', because `ts-year'
;; returns the year as a number rather than a string.
(let* ((ts (ts-parse-org "<2015-09-24 Thu .+1d>"))) (format "%s, %s" (ts-month-name ts) (ts-year ts))) ;;=> "September, 2015"
;; Or, using dash.el:
(--> (ts-parse-org "<2015-09-24 Thu .+1d>") (format "%s, %s" (ts-month-name it) (ts-year it))) ;;=> "September, 2015"
;; Or, if you remember the format specifiers:
(ts-format "%B, %Y" (ts-parse-org "<2015-09-24 Thu .+1d>")) ;;=> "September, 2015"
How long ago was this date in 1970?
(let* ((now (ts-now)) (then (ts-apply :year 1970 now))) (list (ts-format then) (ts-human-format-duration (ts-difference now then)))) ;;=> ("1970-08-04 07:07:10 -0500" ;; "49 years, 12 days")
How long ago did the epoch begin?
(ts-human-format-duration (ts-diff (ts-now) (make-ts :unix 0))) ;;=> "49 years, 227 days, 12 hours, 12 minutes, 30 seconds"
In which of the last 100 years was Christmas on a Saturday?
(let ((ts (ts-parse "2019-12-25")) (limit (- (ts-year (ts-now)) 100))) (cl-loop while (>= (ts-year ts) limit) when (string= "Saturday" (ts-day-name ts)) collect (ts-year ts) do (ts-decf (ts-year ts)))) ;;=> (2010 2004 1999 1993 1982 1976 1971 1965 1954 1948 1943 1937 1926 1920)
For a more interesting example, does a timestamp fall within the previous calendar week?
;; First, define a function to return the range of the previous calendar week.
(defun last-week-range ()
"Return timestamps (BEG . END) spanning the previous calendar week."
(let* (;; Bind now' to the current timestamp to ensure all calculations ;; begin from the same timestamp. (In the unlikely event that ;; the execution of this code spanned from one day into the next, ;; that would cause a wrong result.) (now (ts-now)) ;; We start by calculating the offsets for the beginning and ;; ending timestamps using the current day of the week. Note ;; that the
ts-dow' slot uses the "%w" format specifier, which
;; counts from Sunday to Saturday as a number from 0 to 6.
(adjust-beg-day (- (+ 7 (ts-dow now))))
(adjust-end-day (- (- 7 (- 6 (ts-dow now)))))
;; Make beginning/end timestamps based on now', with adjusted ;; day and hour/minute/second values. These functions return ;; new timestamps, so
now' is unchanged.
(beg (thread-last now
;; ts-adjust' makes relative adjustments to timestamps. (ts-adjust 'day adjust-beg-day) ;;
ts-apply' applies absolute values to timestamps.
(ts-apply :hour 0 :minute 0 :second 0)))
(end (thread-last now
(ts-adjust 'day adjust-end-day)
(ts-apply :hour 23 :minute 59 :second 59))))
(cons beg end)))
(-let* (;; Bind the default format string for `ts-format', so the ;; results are easy to understand. (ts-default-format "%a, %Y-%m-%d %H:%M:%S %z") ;; Get the timestamp for 3 days before now. (check-ts (ts-adjust 'day -3 (ts-now))) ;; Get the range for the previous week from the function we defined. ((beg . end) (last-week-range))) (list :last-week-beg (ts-format beg) :check-ts (ts-format check-ts) :last-week-end (ts-format end) :in-range-p (ts-in beg end check-ts))) ;;=> (:last-week-beg "Sun, 2019-08-04 00:00:00 -0500" ;; :check-ts "Fri, 2019-08-09 10:00:34 -0500" ;; :last-week-end "Sat, 2019-08-10 23:59:59 -0500" ;; :in-range-p t)
** Accessors
** Adjustors
~ts-adjust (&rest ADJUSTMENTS)~ :: Return new timestamp having applied ~ADJUSTMENTS~ to ~TS~. ~ADJUSTMENTS~ should be a series of alternating ~SLOTS~ and ~VALUES~ by which to adjust them. For example, this form returns a new timestamp that is 47 hours into the future:
~(ts-adjust ’hour -1 ’day +2 (ts-now))~
Since the timestamp argument is last, it’s suitable for use in a threading macro.
Destructive
~ts-adjustf (TS &rest ADJUSTMENTS)~ :: Return timestamp ~TS~ having applied ~ADJUSTMENTS~. This function is destructive, as it calls ~setf~ on ~TS~.
~ADJUSTMENTS~ should be a series of alternating ~SLOTS~ and ~VALUES~ by which to adjust them. For example, this form adjusts a timestamp to 47 hours into the future:
~(let ((ts (ts-now))) (ts-adjustf ts ’hour -1 ’day +2))~
** Comparators
** Duration
** Formatting
** Parsing
** Misc.
~copy-ts (TS)~ :: Return copy of timestamp struct ~TS~.
~ts-difference (A B)~ :: Return difference in seconds between timestamps ~A~ and ~B~.
~ts-diff~ :: Alias for ~ts-difference~.
~ts-fill (TS &optional ZONE)~ :: Return ~TS~ having filled all slots from its Unix timestamp. This is non-destructive. ~ZONE~ is passed to ~format-time-string~, which see.
~ts-now~ :: Return ~ts~ struct set to now.
~ts-p (STRUCT)~ ::
~ts-reset (TS)~ :: Return ~TS~ with all slots cleared except ~unix~. Non-destructive. The same as:
~(make-ts :unix (ts-unix ts))~
~ts-defstruct (&rest ARGS)~ :: Like ~cl-defstruct~, but with additional slot options.
Additional slot options and values:
~:accessor-init~: a sexp that initializes the slot in the accessor if the slot is nil. The symbol ~struct~ will be bound to the current struct. The accessor is defined after the struct is fully defined, so it may refer to the struct definition (e.g. by using the ~cl-struct~ ~pcase~ macro).
~:aliases~: ~A~ list of symbols which will be aliased to the slot accessor, prepended with the struct name (e.g. a struct ~ts~ with slot ~year~ and alias ~y~ would create an alias ~ts-y~).
Tips :PROPERTIES: :TOC: :depth 0 :END:
** ~ts-human-format-duration~ vs. ~format-seconds~
Emacs has a built-in function, ~format-seconds~, that produces output similar to that of ~ts-human-format-duration~. Its output is also controllable with a format string. However, if the output of ~ts-human-format-duration~ is sufficient, it performs much better than ~format-seconds~. This simple benchmark, run 100,000 times, shows that it runs much faster and generates less garbage:
(bench-multi-lexical :times 100000 :forms (("ts-human-format-duration" (ts-human-format-duration 15780.910933971405 t)) ("format-seconds" (format-seconds "%yy%dd%hh%mm%ss%z" 15780.910933971405))))
| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | |--------------------------+--------------------+---------------+----------+------------------| | ts-human-format-duration | 5.82 | 0.832945 | 3 | 0.574929 | | format-seconds | slowest | 4.848253 | 17 | 3.288799 |
(See the [[https://github.com/alphapapa/emacs-package-dev-handbook][Emacs Package Developer's Handbook]] for the ~bench-multi-lexical~ macro.)
** 0.3
Added
** 0.2.1
Fixed
** 0.2
Added
Changed
Fixed
Documentation
** 0.1
First tagged release. Published to MELPA.
GPLv3