212 lines
6.4 KiB
Scheme
212 lines
6.4 KiB
Scheme
;;
|
|
;; cal-month.scm
|
|
;;
|
|
;; Month processing support.
|
|
;;
|
|
;; ISC License
|
|
;;
|
|
;; Copyright 2023 Brmlab, z.s.
|
|
;; Dominik Pantůček <dominik.pantucek@trustica.cz>
|
|
;;
|
|
;; Permission to use, copy, modify, and/or distribute this software
|
|
;; for any purpose with or without fee is hereby granted, provided
|
|
;; that the above copyright notice and this permission notice appear
|
|
;; in all copies.
|
|
;;
|
|
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
|
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
;;
|
|
|
|
(declare (unit cal-month))
|
|
|
|
(module
|
|
cal-month
|
|
(
|
|
make-cal-month
|
|
|
|
cal-month?
|
|
|
|
cal-month-year
|
|
cal-month-month
|
|
|
|
string->cal-month
|
|
cal-month->string
|
|
iso-date->cal-month
|
|
|
|
cal-month=?
|
|
cal-month<?
|
|
cal-month<=?
|
|
cal-month>=?
|
|
cal-month>?
|
|
cal-month-diff
|
|
cal-month-add
|
|
|
|
cal-month-tests!
|
|
)
|
|
|
|
(import scheme
|
|
(chicken base)
|
|
(chicken string)
|
|
(chicken format)
|
|
util-tag
|
|
testing)
|
|
|
|
;; Type tag
|
|
(define TAG-CAL-MONTH (make-tag CAL-MONTH))
|
|
|
|
;; Simple wrapper for creating month representation as a list. Can
|
|
;; construct invalid months.
|
|
(define (make-cal-month y m)
|
|
(list TAG-CAL-MONTH y m))
|
|
|
|
;; Returns true if given value is a valid calendar month.
|
|
(define (cal-month? v)
|
|
(and (list? v)
|
|
(not (null? v))
|
|
(eq? TAG-CAL-MONTH (car v))
|
|
(not (null? (cdr v)))
|
|
(integer? (cadr v))
|
|
(not (null? (cddr v)))
|
|
(integer? (caddr v))
|
|
(>= (cadr v) 1000)
|
|
(<= (cadr v) 9999)
|
|
(>= (caddr v) 1)
|
|
(<= (caddr v) 12)))
|
|
|
|
;; Accessors
|
|
(define cal-month-year cadr)
|
|
(define cal-month-month caddr)
|
|
|
|
;; Converts string in a format YYYY-MM to valid month. Returns #f if
|
|
;; the conversion fails.
|
|
(define (string->cal-month s)
|
|
(let ((l (string-split s "-")))
|
|
(if (or (not l)
|
|
(null? l)
|
|
(null? (cdr l))
|
|
(not (null? (cddr l))))
|
|
#f
|
|
(let ((y (string->number (car l)))
|
|
(m (string->number (cadr l))))
|
|
(if (and y m)
|
|
(let ((M (make-cal-month y m)))
|
|
(if (cal-month? M)
|
|
M
|
|
#f))
|
|
#f)))))
|
|
|
|
;; Formats (valid) month as YYYY-MM string
|
|
(define (cal-month->string M)
|
|
(if M
|
|
(if (cal-month? M)
|
|
(let ((y (cadr M))
|
|
(m (caddr M)))
|
|
(sprintf "~A-~A~A"
|
|
y
|
|
(if (< m 10) "0" "")
|
|
m))
|
|
(error 'string->month "Invalid month" M))
|
|
"____-__"))
|
|
|
|
;; Converts ISO date YYYY-MM-DD to single month
|
|
(define (iso-date->cal-month str)
|
|
(let ((lst (string-split str "-")))
|
|
(if (or (not lst)
|
|
(null? lst)
|
|
(null? (cdr lst))
|
|
(null? (cddr lst))
|
|
(not (null? (cdddr lst))))
|
|
#f
|
|
(let ((year (string->number (car lst)))
|
|
(mon (string->number (cadr lst))))
|
|
(if (and year mon)
|
|
(let ((M (make-cal-month year mon)))
|
|
(if (cal-month? M)
|
|
M
|
|
#f))
|
|
#f)))))
|
|
|
|
;; Returns true if both arguments are a valid month and are equal
|
|
(define (cal-month=? m n)
|
|
(and (cal-month? m)
|
|
(cal-month? n)
|
|
(eq? (cadr m) (cadr n))
|
|
(eq? (caddr m) (caddr n))))
|
|
|
|
;; Returns true if the first argument is a month in the past of the
|
|
;; second argument month
|
|
(define (cal-month<? m n)
|
|
(and (cal-month? m)
|
|
(cal-month? n)
|
|
(or (< (cadr m) (cadr n))
|
|
(and (= (cadr m) (cadr n))
|
|
(< (caddr m) (caddr n))))))
|
|
|
|
;; Returns true if m is less than or equal n
|
|
(define (cal-month<=? m n)
|
|
(or (cal-month<? m n)
|
|
(cal-month=? m n)))
|
|
|
|
;; Returns true if m is greater than or equal to n
|
|
(define (cal-month>=? m n)
|
|
(not (cal-month<? m n)))
|
|
|
|
;; Returns true if m is greater than n
|
|
(define (cal-month>? m n)
|
|
(not (cal-month<=? m n)))
|
|
|
|
;; Returns the number of months between from f and to t. The first
|
|
;; month is included in the count, the last month is not.
|
|
(define (cal-month-diff f t)
|
|
(if (cal-month? f)
|
|
(if (cal-month? t)
|
|
(let ((F (+ (* (cadr f) 12) (caddr f) -1))
|
|
(T (+ (* (cadr t) 12) (caddr t) -1)))
|
|
(- T F))
|
|
(error 'month-diff "Second argument is not a valid month" t))
|
|
(error 'month-diff "First argument is not a valid month" f)))
|
|
|
|
;; Returns a month n months after the month m. The number n defaults
|
|
;; to 1.
|
|
(define (cal-month-add m . ns)
|
|
(let* ((n (if (null? ns)
|
|
1
|
|
(car ns)))
|
|
(mi (+ (* 12 (cadr m)) (caddr m) n -1)))
|
|
(make-cal-month (quotient mi 12)
|
|
(+ (remainder mi 12) 1))))
|
|
|
|
;; Performs self-tests of the month module.
|
|
(define (cal-month-tests!)
|
|
(run-tests
|
|
cal-month
|
|
(test-true cal-month? (cal-month? `(,TAG-CAL-MONTH 2023 5)))
|
|
(test-false cal-month? (cal-month? `(,TAG-CAL-MONTH 999 8)))
|
|
(test-false cal-month? (cal-month? `(,TAG-CAL-MONTH 2023 -5)))
|
|
(test-equal? string->cal-month (string->cal-month "2023-01") `(,TAG-CAL-MONTH 2023 1))
|
|
(test-false string->cal-month (string->cal-month "2023-13"))
|
|
(test-false string->cal-month (string->cal-month "YYYY-01"))
|
|
(test-false string->cal-month (string->cal-month "2023-MMM"))
|
|
(test-equal? cal-month->string (cal-month->string `(,TAG-CAL-MONTH 2023 1)) "2023-01")
|
|
(test-exn cal-month->string (cal-month->string `(,TAG-CAL-MONTH 999 12)))
|
|
(test-exn cal-month->string (cal-month->string `(,TAG-CAL-MONTH 2023 13)))
|
|
(test-true cal-month<? (cal-month<? `(,TAG-CAL-MONTH 2023 5) `(,TAG-CAL-MONTH 2023 6)))
|
|
(test-true cal-month<? (cal-month<? `(,TAG-CAL-MONTH 2022 12) `(,TAG-CAL-MONTH 2023 1)))
|
|
(test-false cal-month<? (cal-month<? `(,TAG-CAL-MONTH 2023 1) `(,TAG-CAL-MONTH 2023 1)))
|
|
(test-false cal-month<? (cal-month<? `(,TAG-CAL-MONTH 2023 1) `(,TAG-CAL-MONTH 2023 1)))
|
|
(test-true cal-month=? (cal-month=? `(,TAG-CAL-MONTH 2023 4) `(,TAG-CAL-MONTH 2023 4)))
|
|
(test-false cal-month=? (cal-month=? `(,TAG-CAL-MONTH 2023 4) `(,TAG-CAL-MONTH 2023 5)))
|
|
(test-eq? cal-month-diff (cal-month-diff `(,TAG-CAL-MONTH 2023 1) `(,TAG-CAL-MONTH 2023 2)) 1)
|
|
(test-eq? cal-month-diff (cal-month-diff `(,TAG-CAL-MONTH 2023 1) `(,TAG-CAL-MONTH 2023 12)) 11)
|
|
(test-eq? cal-month-diff (cal-month-diff `(,TAG-CAL-MONTH 2023 1) `(,TAG-CAL-MONTH 2022 2)) -11)
|
|
(test-eq? cal-month-add (cal-month-add `(,TAG-CAL-MONTH 2023 1) 2) `(,TAG-CAL-MONTH 2023 3))
|
|
(test-equal? iso-date->cal-month (iso-date->cal-month "2023-04-03") `(,TAG-CAL-MONTH 2023 4))
|
|
))
|
|
|
|
)
|