+;;;--------------------------------------------------------------------------
+;;; Various random utilities.
+
+(defun to-integer (x)
+ "Convert X to an integer in the most straightforward way."
+ (floor (rational x)))
+
+(defun from-mixed-base (base val)
+ "BASE is a list of the ranges for the `digits' of a mixed-base
+representation. Convert VAL, a list of digits, into an integer."
+ (do ((base base (cdr base))
+ (val (cdr val) (cdr val))
+ (a (car val) (+ (* a (car base)) (car val))))
+ ((or (null base) (null val)) a)))
+
+(defun to-mixed-base (base val)
+ "BASE is a list of the ranges for the `digits' of a mixed-base
+representation. Convert VAL, an integer, into a list of digits."
+ (let ((base (reverse base))
+ (a nil))
+ (loop
+ (unless base
+ (push val a)
+ (return a))
+ (multiple-value-bind (q r) (floor val (pop base))
+ (push r a)
+ (setf val q)))))
+
+(defun timespec-seconds (ts)
+ "Convert a timespec TS to seconds. A timespec may be a real count of
+seconds, or a list (COUNT UNIT): UNIT may be any of a number of obvious time
+units."
+ (cond ((null ts) 0)
+ ((realp ts) (floor ts))
+ ((atom ts)
+ (error "Unknown timespec format ~A" ts))
+ ((null (cdr ts))
+ (timespec-seconds (car ts)))
+ (t (+ (to-integer (* (car ts)
+ (case (intern (string-upcase
+ (stringify (cadr ts)))
+ '#:zone)
+ ((s sec secs second seconds) 1)
+ ((m min mins minute minutes) 60)
+ ((h hr hrs hour hours) #.(* 60 60))
+ ((d dy dys day days) #.(* 24 60 60))
+ ((w wk wks week weeks) #.(* 7 24 60 60))
+ ((y yr yrs year years) #.(* 365 24 60 60))
+ (t (error "Unknown time unit ~A"
+ (cadr ts))))))
+ (timespec-seconds (cddr ts))))))
+
+(defun hash-table-keys (ht)
+ "Return a list of the keys in hashtable HT."
+ (collecting ()
+ (maphash (lambda (key val) (declare (ignore val)) (collect key)) ht)))
+
+(defun iso-date (&optional time &key datep timep (sep #\ ))
+ "Construct a textual date or time in ISO format. The TIME is the universal
+time to convert, which defaults to now; DATEP is whether to emit the date;
+TIMEP is whether to emit the time, and SEP (default is space) is how to
+separate the two."
+ (multiple-value-bind
+ (sec min hr day mon yr dow dstp tz)
+ (decode-universal-time (if (or (null time) (eq time :now))
+ (get-universal-time)
+ time))
+ (declare (ignore dow dstp tz))
+ (with-output-to-string (s)
+ (when datep
+ (format s "~4,'0D-~2,'0D-~2,'0D" yr mon day)
+ (when timep
+ (write-char sep s)))
+ (when timep
+ (format s "~2,'0D:~2,'0D:~2,'0D" hr min sec)))))
+
+;;;--------------------------------------------------------------------------
+;;; Simple messing with IP addresses.
+