(define-module (vcomponent datetime) :use-module (srfi srfi-1) :use-module ((srfi srfi-41) :select (stream-filter)) :use-module ((srfi srfi-41 util) :select (get-stream-interval)) :use-module (vcomponent base) :use-module (datetime) :use-module (datetime timespec) :use-module (datetime zic) :use-module (hnh util) :use-module ((vcomponent recurrence generate) :select (final-event-occurence)) :use-module (ice-9 curried-definitions) :export (#;parse-datetime event-overlaps? overlapping? event-contains? event-zero-length? ev-timevtimezone )) ;;; date time pointer #; (define (parse-datetime dtime) "Parse the given date[time] string into a date object." (string->date dtime (case (string-length dtime) ((8) "~Y~m~d") ; All day ((15) "~Y~m~dT~H~M~S") ; "local" or TZID-param ((16) "~Y~m~dT~H~M~S~z")))) ; UTC-time (define (event-overlaps? event begin end) "Returns if the event overlaps the timespan. Event must have the DTSTART and DTEND protperty set." (timespan-overlaps? (prop event 'DTSTART) (or (prop event 'DTEND) (prop event 'DTSTART)) begin end)) (define (overlapping? event-a event-b) (timespan-overlaps? (prop event-a 'DTSTART) (or (prop event-a 'DTEND) (if (date? (prop event-a 'DTSTART)) (date+ (prop event-a 'DTSTART) (date day: 1)) (prop event-a 'DTSTART))) (prop event-b 'DTSTART) (or (prop event-b 'DTEND) (if (date? (prop event-b 'DTSTART)) (date+ (prop event-b 'DTSTART) (date day: 1)) (prop event-b 'DTSTART))))) (define (event-contains? ev date/-time) "Does event overlap the date that contains time." (let* ((start (as-date date/-time)) (end (date+ start (date day: 1)))) (event-overlaps? ev start end))) (define (event-zero-length? ev) (and (datetime? (prop ev 'DTSTART)) (not (prop ev 'DTEND)))) (define (ev-timevtimezone zoneinfo zone-name event) (define last-until (datetime date: (date month: 1 day: 1))) (define last-offset (timespec-zero)) (fold (lambda (zone-entry vtimezone) (cond [(zone-entry-rule zone-entry) timespec? => (lambda (inline-rule) (let ((component (vcomponent type: 'DAYLIGHT)) (new-timespec (timespec-add (zone-entry-stdoff zone-entry) inline-rule))) (let ((component (set-properties component (cons 'DTSTART last-until) (cons 'TZOFFSETFROM last-offset) (cons 'TZOFFSETTO new-timespec) (cons 'TZNAME (zone-entry-format zone-entry))))) (set! last-until (zone-entry-until zone-entry) last-offset new-timespec) (add-child vtimezone component))))] [(zone-entry-rule zone-entry) => (lambda (rule-name) (fold (lambda (rule vtimezone) (let ((component (vcomponent type: ;; NOTE the zoneinfo database doesn't ;; come with information if a given ;; rule is in standard or daylight time, ;; since that's mostly nonsencical ;; (e.g. war- and peacetime). ;; But the ical standard requires that, ;; so this is a fair compromize. (if (string-null? (rule-letters rule)) 'STANDARD 'DAYLIGHT))) (new-timespec (timespec-add (zone-entry-stdoff zone-entry) (rule-save rule)))) (let ((component (set-properties component (cons 'DTSTART (rule->dtstart rule)) (cons 'TZOFFSETFROM last-offset) (cons 'TZOFFSETTO new-timespec) (cons 'TZNAME (zone-format (zone-entry-format zone-entry) (rule-letters rule)))))) (set! ;; NOTE this can both be a number or the ;; symbol 'maximum last-until (zone-entry-until zone-entry) last-offset new-timespec) (add-child vtimezone (cond ((rule->rrule rule) => (lambda (it) (prop component 'RRULE it))) (else component)))))) vtimezone ;; some of the rules might not apply to us since we only ;; started using that rule set later. It's also possible ;; that we stopped using a ruleset which continues existing. ;; ;; Both these are filtered here. (filter (relevant-zone-rule? event) (get-rule zoneinfo rule-name))))] [else ; no rule ;; DTSTART MUST be a datetime in local time (let ((component (set-properties (vcomponent type: 'STANDARD) (cons 'DTSTART last-until) (cons 'TZOFFSETFROM last-offset) (cons 'TZOFFSETTO (zone-entry-stdoff zone-entry)) (cons 'TZNAME (zone-entry-format zone-entry))))) (set! last-until (zone-entry-until zone-entry) last-offset (zone-entry-stdoff zone-entry)) (add-child vtimezone component)) ]) ) (prop (vcomponent type: 'VTIMEZONE) 'TZID zone-name) (filter (relevant-zone-entry? event) (get-zone zoneinfo zone-name)) ))