tvl-depot/lisp/klatre/klatre.lisp
sterni f60d174591 feat(klatre): expose utility to format dot time offset
Turns out this is an annoying thingy you sometimes to implement
independently from formatting an entire timestamp, so we expose it for
reuse.

Change-Id: I11de2823eb03849ea78fc79e2f546e413882930f
Reviewed-on: https://cl.tvl.fyi/c/depot/+/3163
Tested-by: BuildkiteCI
Reviewed-by: grfn <grfn@gws.fyi>
Reviewed-by: tazjin <mail@tazj.in>
2021-05-29 19:13:11 +00:00

117 lines
3.6 KiB
Common Lisp

(in-package #:klatre)
(declaim (optimize (safety 3)))
(defmacro comment (&rest _))
(defun posp (n) (> n 0))
;;; Sequence utilities
(defun slice (vector start end)
(make-array (- end start)
:element-type (array-element-type vector)
:displaced-to vector
:displaced-index-offset start))
(defun chunk-vector (size vector &key start end sharedp)
(check-type size (integer 1))
(loop
with slicer = (if sharedp #'slice #'subseq)
and low = (or start 0)
and high = (or end (length vector))
for s from low below high by size
for e from (+ low size) by size
collect (funcall slicer vector s (min e high))))
(defun chunk-list/unbounded (size list)
(loop
for front = list then next
for next = (nthcdr size front)
collect (ldiff front next)
while next))
(defun chunk-list/bounded (size list upper-limit)
(loop
for front = list then next
for next = (nthcdr (min size upper-limit) front)
collect (ldiff front next)
do (decf upper-limit size)
while (and next (plusp upper-limit))))
(defun chunk-list (size list &key (start 0) end)
"Returns successive chunks of list of size SIZE, starting at START and ending
at END."
(declare (inline check-list/bounded check-list/simple))
(check-type size (integer 1))
(let ((list (nthcdr start list)))
(when list
(if end
(chunk-list/bounded size list (- end start))
(chunk-list/unbounded size list)))))
(defun mapconcat (func lst sep)
"Apply FUNC to each element of LST, and concat the results as strings,
separated by SEP."
(check-type lst cons)
(check-type sep (simple-array character (*)))
(let ((vs (make-array 0
:element-type 'character
:fill-pointer 0
:adjustable t))
(lsep (length sep)))
(mapcar #'(lambda (str)
(let ((nstr (the (simple-array character (*))
(funcall func str))))
(dotimes (j (length nstr) j)
(vector-push-extend (char nstr (the fixnum j)) vs))
(dotimes (k lsep k)
(vector-push-extend (char sep (the fixnum k)) vs))))
lst)
vs))
;;;
;;; String handling
;;;
(defparameter dottime-format
'((:year 4) #\- (:month 2) #\- (:day 2)
#\T
(:hour 2) #\· (:min 2))
"`:LOCAL-TIME' format specifier for dottime")
(defun format-dottime (timestamp &optional (offset 0))
"Return TIMESTAMP formatted as dottime, with a specified offset or +00"
(check-type timestamp local-time:timestamp)
(concatenate 'string
(local-time:format-timestring nil timestamp
:format dottime-format
:timezone local-time:+utc-zone+)
(format-dottime-offset offset)))
(defun format-dottime-offset (offset)
"Render OFFSET in hours in the format specified by dottime."
(check-type offset integer)
(concatenate 'string
; render sign manually since format prints it after padding
(if (>= offset 0) "+" "-")
(format nil "~2,'0D" (abs offset))))
(comment
(format-dottime (local-time:now))
(format-dottime (local-time:now) 2))
(defun try-parse-integer (str)
"Attempt to parse STR as an integer, returning nil if it is invalid."
(check-type str string)
(handler-case (parse-integer str)
(sb-int:simple-parse-error (_) nil)))
;;;
;;; Function utilities
;;;
(defun partial (f &rest args)
"Return a function that calls F with ARGS prepended to any remaining
arguments"
(lambda (&rest more-args)
(apply f (append args more-args))))