(Arne Babenhauserheide)
2015-06-22: create the first guildhall package: newbase60 create the first guildhall package: newbase60
diff --git a/guildhall-packages/newbase60.scm b/guildhall-packages/newbase60.scm new file mode 100644 --- /dev/null +++ b/guildhall-packages/newbase60.scm @@ -0,0 +1,151 @@ +#!/usr/bin/env sh +# -*- scheme -*- +exec guile -e '(@@ (examples newbase60) main)' -s "$0" "$@" +; !# + +;; Encoding and decoding numbers in New Base 60 as defined by Tantek: +;; http://tantek.pbworks.com/NewBase60 + +;; Based on the very elegant implementation from Kevin Marks licensed under CC0: +;; https://github.com/indieweb/newBase60py/blob/master/newbase60.py + +(define-module (examples newbase60) + #:export (integer->sxg sxg->integer date->sxg sxg->date date->sxgepochdays sxgepochdays->yeardays yeardays->sxgepochdays) + #:use-module (srfi srfi-1)) + +(define base60letters "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz") +(define base60numbers + (append + (map cons (string->list base60letters) (iota (string-length base60letters))) + (quote + ((#\l . 1 ); typo lowercase l to 1 + (#\I . 1 ); typo capital I to 1 + (#\O . 0 ))))); typo capital O to 0 + +(define (positive-integer->sxg num) + "Convert a positive integer to Tanteks new base 60." + (if (equal? 0 num) + "0" + (let loop + ((s '()) + (n num)) + (if (equal? n 0) + (list->string s) + (loop + (cons (string-ref base60letters (remainder n 60)) s) + (quotient n 60)))))) + +(define (positive-sxg->integer string) + "Convert a positive new base 60 string into a positive integer." + (let loop + ((n 0) + (s string)) + (cond + ((equal? "" s) + n) + (else + (loop + (+ (assoc-ref base60numbers (string-ref s 0)) + (* n 60)) + (string-drop s 1)))))) + +(define (integer->sxg num) + "Convert an integer to Tanteks new base 60." + (if (>= num 0) + (positive-integer->sxg num) + (string-append "-" (positive-integer->sxg (- num))))) + +(define (sxg->integer str) + "Convert a new base 60 string into an integer." + (if (and (>= (string-length str) 1) (equal? #\- (string-ref str 0))) + (- (positive-sxg->integer (string-drop str 1))) + (positive-sxg->integer str))) + +(define (date->sxgepochdays year month day hour minute second) + (let + ((tm (gmtime 0 ))); initialize + (set-tm:year tm (- year 1900)) + (set-tm:mon tm month) + (set-tm:mday tm day) + (set-tm:hour tm hour) + (set-tm:min tm minute) + (set-tm:sec tm second) + (let* + ((epochseconds (car (mktime tm "+0" ))); 0: UTC + (epochdays (quotient epochseconds (* 24 60 60)))) + (integer->sxg epochdays)))) + +(define (yeardays->sxgepochdays year yeardays) + (let + ((tm (car (strptime "%Y %j" (string-join (map number->string (list year yeardays))))))) + (let* + ((epochseconds (car (mktime tm "+0" ))); 0: UTC + (epochdays (quotient epochseconds (* 24 60 60)))) + (integer->sxg epochdays)))) + +(define (sxgepochdays->yeardays str) + "Turn sexagesimal days since epoch into year (YYYY) and day of year (DDD)." + (let* + ((epochdays (sxg->integer str)) + (epochseconds (* epochdays 24 60 60)) + (tm (gmtime epochseconds)) + (year (+ 1900 (tm:year tm))) + (yeardays (tm:yday tm))) + (list year (+ yeardays 1)))) + +(define (date->sxg year month day hour minute second) + "Convert a date into new base 60 format: + yyyymmdd hhmmss -> YYMD-hms (can extend till 3599) + " + (format #f "~A-~A" + (apply string-append + (map integer->sxg + (list year month day))) + (apply string-append + (map integer->sxg + (list hour minute second))))) + +(define (sxg->date str) + "Convert a new base 60 date into a list: + YYMD-hms -> (year month day hour minute second) + " + (let* + ((centeridx (string-rindex str #\- )); rindex because the year could be negative + (getstr (lambda (s di) (string (string-ref str (+ centeridx di)))))) + (let + ((year (substring/read-only str 0 (- centeridx 2))) + (month (getstr str -2)) + (day (getstr str -1)) + (hour (getstr str 1)) + (minute (getstr str 2)) + (second (getstr str 3))) + (map sxg->integer + (list year month day hour minute second))))) + +(define (main args) + (let + ((help (lambda () (format #t "usage: ~A [integer | -d string | --datetime | --datetime year month day hour minute second | --help]\n" (list-ref args 0))))) + (cond + ((or (= 1 (length args)) (member "--help" args)) + (help)) + ((and (= 8 (length args)) (equal? "--datetime" (list-ref args 1))) + (format #t "~A\n" (apply date->sxg (map string->number (drop args 2))))) + ((and (= 8 (length args)) (equal? "--sxgepochdays" (list-ref args 1))) + (format #t "~A\n" (apply date->sxgepochdays (map string->number (drop args 2))))) + ((and (= 4 (length args)) (equal? "--sxgepochdays-from-yearday" (list-ref args 1))) + (format #t "~A\n" (apply yeardays->sxgepochdays (map string->number (drop args 2))))) + ((and (= 2 (length args)) (equal? "--datetime" (list-ref args 1))) + (let ((tm (localtime (current-time)))) + (format #t "~A\n" (apply date->sxg (list (+ 1900 (tm:year tm)) (+ 1 (tm:mon tm)) (tm:mday tm) (tm:hour tm) (tm:min tm) (tm:sec tm)))))) + ((and (= 3 (length args)) (equal? "--decode-datetime" (list-ref args 1))) + (format #t "~A\n" (sxg->date (list-ref args 2)))) + ((and (= 3 (length args)) (equal? "--decode-sxgepochdays" (list-ref args 1))) + (format #t "~A\n" (sxgepochdays->yeardays (list-ref args 2)))) + ((and (= 3 (length args)) (equal? "-d" (list-ref args 1))) + (format #t "~A\n" (sxg->integer (list-ref args 2)))) + ((= 2 (length args)) + (format #t "~A\n" (integer->sxg (string->number (list-ref args 1))))) + (else + (help))))) + + diff --git a/guildhall-packages/pkg-list.scm b/guildhall-packages/pkg-list.scm new file mode 100644 --- /dev/null +++ b/guildhall-packages/pkg-list.scm @@ -0,0 +1,7 @@ +(package (newbase60 (0)) + (synopsis "Implementation of Tanteks New Base 60") + (depends (srfi) (ice-9)) + (libraries + (scm -> "newbase60")) + (programs + (("newbase60.scm") -> "newbase60")))