unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION
@ 2022-04-14 13:44 John Hamelink
  2022-04-14 14:53 ` Lars Ingebrigtsen
  2022-04-17 18:00 ` Paul Eggert
  0 siblings, 2 replies; 7+ messages in thread
From: John Hamelink @ 2022-04-14 13:44 UTC (permalink / raw)
  To: 54939

[-- Attachment #1: Type: text/plain, Size: 7222 bytes --]

Hi there,

As part of my ongoing efforts to move entirely to Emacs for email, I've
discovered an incompatibility with calendar invites sent by Fastmail.

After triggering `mu4e-headers-view-message' from `mu4e-headers-mode', I
receive the following:

    Debugger entered--Lisp error: (wrong-type-argument consp nil)
    gnus-icalendar-event--decode-datefield((VEVENT nil ((UID nil
    "064fa1da-e6f1-4463-9f5d-97217cde2c17") (SEQUENCE nil "1") (DTSTAMP
    nil "20220414T130209Z") (CREATED nil "20220414T130209Z") (DTSTART
    (TZID "Europe/London") "20220415T090000") (DURATION nil "PT1H")
    (PRIORITY nil "0") (SUMMARY nil "Demo Event") (STATUS nil "CONFIRMED")
    (TRANSP nil "OPAQUE") (ORGANIZER (X-JMAP-ID "bWVAam9obmhhbWUubGluaw"
    CN "John Hamelink" EMAIL "me@johnhame.link")
    "mailto:me@johnhame.link") (ATTENDEE (X-JMAP-ID
    "bWVAam9obmhhbWUubGluaw" CN "John Hamelink" EMAIL "me@johnhame.link"
    CUTYPE "INDIVIDUAL" X-JMAP-ROLE "owner" X-JMAP-ROLE "attendee"
    PARTSTAT "ACCEPTED" RSVP "FALSE") "mailto:me@johnhame.link") (ATTENDEE
    (X-JMAP-ID "am9obkBtZWRpY2NyZWF0aW9ucy5jb20" CN "John Hamelink" EMAIL
    "john@work.com" CUTYPE "INDIVIDUAL" X-JMAP-ROLE "attendee" PARTSTAT
    "NEEDS-ACTION" RSVP "TRUE") "mailto:john@work.com")) nil) DTEND
    (("Europe/London"
    . "STD-00:00DST-01:00,M3.5.0/01:00:00,M10.5.0/02:00:00")))

This makes sense, because on inspection of the ics file, we see that
DTEND is not part of the file (I added indentation to clarify scope):

    BEGIN:VCALENDAR
    VERSION:2.0
    PRODID:-//CyrusIMAP.org/Cyrus=20
    3.7.0-alpha0-387-g7ea99c4045-fm-20220413.002-g7ea99c40//EN
    METHOD:REQUEST
    CALSCALE:GREGORIAN
    BEGIN:VTIMEZONE
        TZID:Europe/London
        LAST-MODIFIED:20210816T175139Z
        X-LIC-LOCATION:Europe/London
        TZUNTIL:20220415T090000Z
        BEGIN:DAYLIGHT
            TZNAME:BST
            TZOFFSETFROM:+0000
            TZOFFSETTO:+0100
            DTSTART:19810329T010000
            RRULE:FREQ=3DYEARLY;BYMONTH=3D3;BYDAY=3D-1SU
        END:DAYLIGHT
        BEGIN:STANDARD
            TZNAME:GMT
            TZOFFSETFROM:+0100
            TZOFFSETTO:+0000
            DTSTART:19961027T020000
            RRULE:FREQ=3DYEARLY;BYMONTH=3D10;BYDAY=3D-1SU
        END:STANDARD
    END:VTIMEZONE
    BEGIN:VEVENT
        UID:064fa1da-e6f1-4463-9f5d-97217cde2c17
        SEQUENCE:1
        DTSTAMP:20220414T130209Z
        CREATED:20220414T130209Z
        DTSTART;TZID=3DEurope/London:20220415T090000
        DURATION:PT1H
        PRIORITY:0
        SUMMARY:Demo Event
        STATUS:CONFIRMED
        TRANSP:OPAQUE
        ORGANIZER;X-JMAP-ID=3DbWVAam9obmhhbWUubGluaw;CN=3DJohn Hamelink;
        EMAIL=3Dme@johnhame.link:mailto:me@johnhame.link
        ATTENDEE;X-JMAP-ID=3DbWVAam9obmhhbWUubGluaw;CN=3DJohn Hamelink;
        EMAIL=3Dme@johnhame.link;CUTYPE=3DINDIVIDUAL;X-JMAP-ROLE=3Downer;
        X-JMAP-ROLE=3Dattendee;PARTSTAT=3DACCEPTED;RSVP=3DFALSE:mailto:
        me@johnhame.link
        ATTENDEE;X-JMAP-ID=3Dam9obkBtZWRpY2NyZWF0aW9ucy5jb20;CN=3DJohn Hamelink;
        EMAIL=3Djohn@work.com;CUTYPE=3DINDIVIDUAL;X-JMAP-ROLE=3Dattendee;
        PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailto:john@work.com
    END:VEVENT
    END:VCALENDAR

However, it seems that from the ics file we did receive, we should be
able to infer the DTEND value by simply adding DURATION to DTSTART.

I've been able to setup a minimal configuration which replicates the
problem using the icalendar file above (and without involving mu4e):

    (package-install 'gnus)
    (package-install 'org)

    (require 'gnus)
    (require 'gnus-icalendar)

    (setq-default gnus-icalendar-org-capture-file "/tmp/agenda.org"
                gnus-icalendar-org-capture-headline '("Calendar"))

    (gnus-icalendar-org-setup)

    (gnus-icalendar-event-from-ical
    '((VCALENDAR nil
            ((VERSION nil "2.0")
            (PRODID nil "-//CyrusIMAP.org/Cyrus 3.7.0-alpha0-387-g7ea99c4045-fm-20220413.002-g7ea99c40//EN")
            (METHOD nil "REQUEST")
            (CALSCALE nil "GREGORIAN"))
            ((VTIMEZONE nil
                        ((TZID nil "Europe/London")
                        (LAST-MODIFIED nil "20210816T175139Z")
                        (X-LIC-LOCATION nil "Europe/London")
                        (TZUNTIL nil "20220415T090000Z"))
                        ((DAYLIGHT nil (... ... ... ... ...) nil)
                        (STANDARD nil (... ... ... ... ...) nil)))
            (VEVENT nil (
                    (UID nil "064fa1da-e6f1-4463-9f5d-97217cde2c17")
                    (SEQUENCE nil "1")
                    (DTSTAMP nil "20220414T130209Z")
                    (CREATED nil "20220414T130209Z")
                    (DTSTART (TZID "Europe/London") "20220415T090000")
                    (DURATION nil "PT1H")
                    (PRIORITY nil "0")
                    (SUMMARY nil "Demo Event")
                    (STATUS nil "CONFIRMED")
                    (TRANSP nil "OPAQUE")
                    (ORGANIZER
                        (X-JMAP-ID "bWVAam9obmhhbWUubGluaw"
                         CN "John Hamelink"
                         EMAIL "me@johnhame.link") "mailto:me@johnhame.link")
                    (ATTENDEE
                        (X-JMAP-ID "bWVAam9obmhhbWUubGluaw"
                         CN "John Hamelink"
                         EMAIL "me@johnhame.link"
                         CUTYPE "INDIVIDUAL"
                         X-JMAP-ROLE "owner"
                         X-JMAP-ROLE "attendee"
                         PARTSTAT "ACCEPTED"
                         RSVP "FALSE") "mailto:me@johnhame.link")
                    (ATTENDEE
                        (X-JMAP-ID "am9obkBtZWRpY2NyZWF0aW9ucy5jb20"
                         CN "John Hamelink"
                         EMAIL "john@work.com"
                         CUTYPE "INDIVIDUAL"
                         X-JMAP-ROLE "attendee"
                         PARTSTAT "NEEDS-ACTION"
                         RSVP "TRUE") "mailto:john@work.com")) nil)))))


The part of the code which handles the end time is gnus-icalendar.el
line 246, where the code simply assumes that the DTEND property exists:

    :end-time (gnus-icalendar-event--decode-datefield event 'DTEND zone-map)

RFC5545 Section 3.6.1 (the iCalendar spec) says:

    ; Either 'dtend' or 'duration' MAY appear in
    ; a 'eventprop', but 'dtend' and 'duration'
    ; MUST NOT occur in the same 'eventprop'.

And further on:

    For cases where a "VEVENT" calendar component
    specifies a "DTSTART" property with a DATE value type but no
    "DTEND" nor "DURATION" property, the event's duration is taken to
    be one day.  For cases where a "VEVENT" calendar component
    specifies a "DTSTART" property with a DATE-TIME value type but no
    "DTEND" property, the event ends on the same calendar date and
    time of day specified by the "DTSTART" property.

So, I propose we build a function that either returns the end-time using
DTEND as is currently the case, or else to add DTSTART + DURATION and
use that as DTEND if one doesn't exist, or else to use DTSTART + 1 Day
if DTSTART is a date, or else to use DTSTART if DTSTART is a datetime.

How does that sound?

Best,
JH

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 857 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

* bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION
  2022-04-14 13:44 bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION John Hamelink
@ 2022-04-14 14:53 ` Lars Ingebrigtsen
  2022-04-14 19:09   ` John Hamelink
  2022-04-17 18:00 ` Paul Eggert
  1 sibling, 1 reply; 7+ messages in thread
From: Lars Ingebrigtsen @ 2022-04-14 14:53 UTC (permalink / raw)
  To: John Hamelink; +Cc: 54939

John Hamelink <me@johnhame.link> writes:

> So, I propose we build a function that either returns the end-time using
> DTEND as is currently the case, or else to add DTSTART + DURATION and
> use that as DTEND if one doesn't exist, or else to use DTSTART + 1 Day
> if DTSTART is a date, or else to use DTSTART if DTSTART is a datetime.
>
> How does that sound?

Makes sense to me.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 7+ messages in thread

* bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION
  2022-04-14 14:53 ` Lars Ingebrigtsen
@ 2022-04-14 19:09   ` John Hamelink
  2022-04-15  9:39     ` John Hamelink
  0 siblings, 1 reply; 7+ messages in thread
From: John Hamelink @ 2022-04-14 19:09 UTC (permalink / raw)
  To: 54939


[-- Attachment #1.1.1: Type: text/plain, Size: 181 bytes --]

>> How does that sound?
>
> Makes sense to me.

Great, then I'll have a crack at writing such a patch. I'm new to
Emacs lisp, but this seems to me like a manageable first task!

JH

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 857 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

* bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION
  2022-04-14 19:09   ` John Hamelink
@ 2022-04-15  9:39     ` John Hamelink
  0 siblings, 0 replies; 7+ messages in thread
From: John Hamelink @ 2022-04-15  9:39 UTC (permalink / raw)
  To: 54939


[-- Attachment #1.1.1: Type: text/plain, Size: 2051 bytes --]

John Hamelink <me@johnhame.link> writes:
> Great, then I'll have a crack at writing such a patch. I'm new to
> Emacs lisp, but this seems to me like a manageable first task!

OK, so I've implemented something that's approaching spec - but not
ready for full review yet: particularly because I have a problem I
need some guidance on. I have introduced the following tests:

`gnus-icalendar-dtstart-only-date' (Failing)
- `DTSTART' is `DTSTART;TZID=Europe/Berlin:20200915'
- `DTEND' is undefined
- `DURATION' is undefined
- Fails because the `gnus-icalendar--datetimep' returns a truthy
  value.

`gnus-icalendar-dtstart-only-datetime' (Passing)
- `DTSTART' is `DTSTART;TZID=Europe/Berlin:20200915T140000'
- `DTEND' is undefined
- `DURATION' is undefined

`gnus-icalendar-dtstart-duration' (Passing)
- `DTSTART' is `DTSTART;TZID=Europe/Berlin:20200915T140000'
- `DTEND' is undefined
- `DURATION' is `PT3H'

The reason `gnus-icalendar-dtstart-only-date' currently fails is
because I haven't found a good way to differentiate between a date and
a datetime - as the spec requires.

In an attempt to unblock the rest of the implementation, I used the
following:

(defun gnus-icalendar--datep (date)
"return t if DATE matches a date list."
(and (length= date 9)
    (length=
        (seq-filter 'integerp (seq-take date 6))
        3)))

(defun gnus-icalendar--datetimep (datetime)
"Return t if DATETIME matches a date-time list."
(and (length= datetime 9)
(length=
    (seq-filter 'integerp (seq-take datetime 6))
    6)))

This strategy doesn't work. In `icalendar--decode-isodatetime', hours,
minutes and seconds are set to 0 by default, effectively normalising
date to datetime. I wonder if there's a function already implemented
for this that I don't know about yet?

I've included all my patches so far, but I do plan on refactoring more
and then checking my contribution against the contributing guidelines
before formally submitting a patch for review.

I've also sent an email to assign@gnu.org pre-emptively, in case that
is necessary.

Thanks!
JH

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.1.2: 0005-lisp-gnus-gnus-icalendar-Add-RFC5545-DTEND-calculati.patch --]
[-- Type: text/x-patch, Size: 12434 bytes --]

From c8e460cc0657cb9ddf9e0fbc9abbca32c9344414 Mon Sep 17 00:00:00 2001
From: John Hamelink <me@johnhame.link>
Date: Fri, 15 Apr 2022 03:35:18 +0100
Subject: [PATCH 5/5] lisp/gnus/gnus-icalendar: Add RFC5545 DTEND calculation
 function

`gnus-icalendar-event--calculate-end-time' returns:
 - DTEND if it is set
 - or DTSTART + DURATION if DURATION is set
 - or DTSTART + 1 Day if DTSTART is a DATE
 - or DTSTART if DTSTART is a DATETIME
---
 lisp/gnus/gnus-icalendar.el            |  27 ++-
 test/lisp/gnus/gnus-icalendar-tests.el | 238 +++++++++++++++++++++++++
 2 files changed, 263 insertions(+), 2 deletions(-)

diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index 036832e1d5..64b21794cb 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -237,6 +237,29 @@ gnus-icalendar--datetimep
     (seq-filter 'integerp (seq-take datetime 6))
     6)))
 
+(defun gnus-icalendar-event--calculate-end-time (event zone-map)
+  "Return DTEND, or calculate it as per RFC5545 3.6.1."
+  (let* ((dtstart (decode-time (gnus-icalendar-event--decode-datefield
+                                event 'DTSTART zone-map)))
+         (dtend (gnus-icalendar-event--decode-datefield
+                 event 'DTEND zone-map))
+         (duration (gnus-icalendar-event--decode-duration
+                    event 'DURATION)))
+    (cond
+     ;; Either Return DTEND
+     ((not (null dtend)) dtend)
+     ;; or DTSTART + DURATION if DURATION exists
+     ((not (null duration))
+      (encode-time
+       (icalendar--add-decoded-times dtstart duration)))
+     ;; or DTSTART + 1DAY if DTSTART is a DATE
+     ((gnus-icalendar--datep dtstart)
+      (encode-time (icalendar--add-decoded-times
+                    dtstart (icalendar--decode-isoduration "1D"))))
+     ;; or DSTART if DTSTART is a DATE-TIME
+     ((gnus-icalendar--datetimep dtstart)
+      (encode-time dtstart)))))
+
 (defun gnus-icalendar-event-from-ical (ical &optional attendee-name-or-email)
   (let* ((event (car (icalendar--all-events ical)))
          (organizer (replace-regexp-in-string
@@ -266,8 +289,8 @@ gnus-icalendar-event-from-ical
                 :organizer organizer
                 :start-time (gnus-icalendar-event--decode-datefield
                              event 'DTSTART zone-map)
-                :end-time (gnus-icalendar-event--decode-datefield
-                           event 'DTEND zone-map)
+                :end-time (gnus-icalendar-event--calculate-end-time
+                           event zone-map)
                 :rsvp (string= (plist-get (cadr attendee) 'RSVP) "TRUE")
                 :participation-type participation-type
                 :req-participants (car attendee-names)
diff --git a/test/lisp/gnus/gnus-icalendar-tests.el b/test/lisp/gnus/gnus-icalendar-tests.el
index 5fecfd3773..c26ddaf44c 100644
--- a/test/lisp/gnus/gnus-icalendar-tests.el
+++ b/test/lisp/gnus/gnus-icalendar-tests.el
@@ -281,5 +281,243 @@ gnus-icalendary-weekly-byday
 <2020-09-21 14:00-14:30 +1w>")))
       (setenv "TZ" tz))))
 
+(ert-deftest gnus-icalendar-dtstart-duration ()
+  ""
+
+  (let ((tz (getenv "TZ"))
+        (event (gnus-icalendar-tests--get-ical-event "\
+BEGIN:VCALENDAR
+PRODID:-//Google Inc//Google Calendar 70.9054//EN
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+X-LIC-LOCATION:Europe/Berlin
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTART;TZID=Europe/Berlin:20200915T140000
+DURATION:PT3H
+RRULE:FREQ=WEEKLY;BYDAY=FR,MO,TH,TU,WE
+DTSTAMP:20200915T120627Z
+ORGANIZER;CN=anon@anoncompany.com:mailto:anon@anoncompany.com
+UID:7b6g3m7iftuo90ei4ul00feqn_R20200915T120000@google.com
+ATTENDEE;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;RSVP=TRUE
+ ;CN=participant@anoncompany.com;X-NUM-GUESTS=0:mailto:participant@anoncompany.com
+CREATED:20200325T095723Z
+DESCRIPTION:Coffee talk
+LAST-MODIFIED:20200915T120623Z
+LOCATION:
+SEQUENCE:0
+STATUS:CONFIRMED
+SUMMARY:Casual coffee talk
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR" (list "participant@anoncompany.com"))))
+
+    (unwind-protect
+        (progn
+          ;; Use this form so as not to rely on system tz database.
+          ;; Eg hydra.nixos.org.
+          (setenv "TZ" "CET-1CEST,M3.5.0/2,M10.5.0/3")
+          (should (eq (eieio-object-class event) 'gnus-icalendar-event-request))
+          (should (gnus-icalendar-event:recurring-p event))
+          (should (string= (gnus-icalendar-event:recurring-interval event) "1"))
+          (should (string= (gnus-icalendar-event:start event) "2020-09-15 14:00"))
+          (should (string= (gnus-icalendar-event:end event) "2020-09-15 17:00"))
+          (with-slots (organizer summary description location end-time uid rsvp participation-type) event
+            (should (string= organizer "anon@anoncompany.com"))
+            (should (string= summary "Casual coffee talk"))
+            (should (string= description "Coffee talk"))
+            (should (string= location ""))
+            (should (string= (format-time-string "%Y-%m-%d %H:%M" end-time) "2020-09-15 17:00"))
+            (should (string= uid "7b6g3m7iftuo90ei4ul00feqn_R20200915T120000@google.com"))
+            (should rsvp)
+            (should (eq participation-type 'required)))
+          (should (equal (sort (gnus-icalendar-event:recurring-days event) #'<) '(1 2 3 4 5)))
+          (should (string= (gnus-icalendar-event:org-timestamp event) "<2020-09-15 14:00-17:00 +1w>
+<2020-09-16 14:00-17:00 +1w>
+<2020-09-17 14:00-17:00 +1w>
+<2020-09-18 14:00-17:00 +1w>
+<2020-09-21 14:00-17:00 +1w>"))
+
+          )
+      (setenv "TZ" tz))))
+
+
+(ert-deftest gnus-icalendar-dtstart-only-datetime ()
+  ""
+
+  (let ((tz (getenv "TZ"))
+        (event (gnus-icalendar-tests--get-ical-event "\
+BEGIN:VCALENDAR
+PRODID:-//Google Inc//Google Calendar 70.9054//EN
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+X-LIC-LOCATION:Europe/Berlin
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTART;TZID=Europe/Berlin:20200915T140000
+RRULE:FREQ=WEEKLY;BYDAY=FR,MO,TH,TU,WE
+DTSTAMP:20200915T120627Z
+ORGANIZER;CN=anon@anoncompany.com:mailto:anon@anoncompany.com
+UID:7b6g3m7iftuo90ei4ul00feqn_R20200915T120000@google.com
+ATTENDEE;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;RSVP=TRUE
+ ;CN=participant@anoncompany.com;X-NUM-GUESTS=0:mailto:participant@anoncompany.com
+CREATED:20200325T095723Z
+DESCRIPTION:Coffee talk
+LAST-MODIFIED:20200915T120623Z
+LOCATION:
+SEQUENCE:0
+STATUS:CONFIRMED
+SUMMARY:Casual coffee talk
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR" (list "participant@anoncompany.com"))))
+
+    (unwind-protect
+        (progn
+          ;; Use this form so as not to rely on system tz database.
+          ;; Eg hydra.nixos.org.
+          (setenv "TZ" "CET-1CEST,M3.5.0/2,M10.5.0/3")
+          (should (eq (eieio-object-class event) 'gnus-icalendar-event-request))
+          (should (gnus-icalendar-event:recurring-p event))
+          (should (string= (gnus-icalendar-event:recurring-interval event) "1"))
+          (should (string= (gnus-icalendar-event:start event) "2020-09-15 14:00"))
+          ;; For cases where a "VEVENT" calendar component specifies a
+          ;; "DTSTART" property with a DATE-TIME value type but no
+          ;; "DTEND" property, the event ends on the same calendar
+          ;; date and time of day specified by the "DTSTART" property.
+          (should (string= (gnus-icalendar-event:end event) "2020-09-15 14:00"))
+          (with-slots (organizer summary description location end-time uid rsvp participation-type) event
+            (should (string= organizer "anon@anoncompany.com"))
+            (should (string= summary "Casual coffee talk"))
+            (should (string= description "Coffee talk"))
+            (should (string= location ""))
+            (should (string= (format-time-string "%Y-%m-%d %H:%M" end-time) "2020-09-15 14:00"))
+            (should (string= uid "7b6g3m7iftuo90ei4ul00feqn_R20200915T120000@google.com"))
+            (should rsvp)
+            (should (eq participation-type 'required)))
+          (should (equal (sort (gnus-icalendar-event:recurring-days event) #'<) '(1 2 3 4 5)))
+          (should (string= (gnus-icalendar-event:org-timestamp event) "<2020-09-15 14:00-14:00 +1w>
+<2020-09-16 14:00-14:00 +1w>
+<2020-09-17 14:00-14:00 +1w>
+<2020-09-18 14:00-14:00 +1w>
+<2020-09-21 14:00-14:00 +1w>"))
+
+          )
+      (setenv "TZ" tz))))
+
+(ert-deftest gnus-icalendar-dtstart-only-date ()
+  ""
+
+  (let ((tz (getenv "TZ"))
+        (event (gnus-icalendar-tests--get-ical-event "\
+BEGIN:VCALENDAR
+PRODID:-//Google Inc//Google Calendar 70.9054//EN
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+X-LIC-LOCATION:Europe/Berlin
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTART;TZID=Europe/Berlin:20200915
+RRULE:FREQ=WEEKLY;BYDAY=FR,MO,TH,TU,WE
+DTSTAMP:20200915T120627Z
+ORGANIZER;CN=anon@anoncompany.com:mailto:anon@anoncompany.com
+UID:7b6g3m7iftuo90ei4ul00feqn_R20200915T120000@google.com
+ATTENDEE;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;RSVP=TRUE
+ ;CN=participant@anoncompany.com;X-NUM-GUESTS=0:mailto:participant@anoncompany.com
+CREATED:20200325T095723Z
+DESCRIPTION:Coffee talk
+LAST-MODIFIED:20200915T120623Z
+LOCATION:
+SEQUENCE:0
+STATUS:CONFIRMED
+SUMMARY:Casual coffee talk
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR" (list "participant@anoncompany.com"))))
+
+    (unwind-protect
+        (progn
+          ;; Use this form so as not to rely on system tz database.
+          ;; Eg hydra.nixos.org.
+          (setenv "TZ" "CET-1CEST,M3.5.0/2,M10.5.0/3")
+          (should (eq (eieio-object-class event) 'gnus-icalendar-event-request))
+          (should (gnus-icalendar-event:recurring-p event))
+          (should (string= (gnus-icalendar-event:recurring-interval event) "1"))
+          (should (string= (gnus-icalendar-event:start event) "2020-09-15 00:00"))
+          ;; For cases where a "VEVENT" calendar component
+          ;; specifies a "DTSTART" property with a DATE value type but no
+          ;; "DTEND" nor "DURATION" property, the event's duration is taken to
+          ;; be one day.
+          (should (string= (gnus-icalendar-event:end event) "2020-09-16 00:00"))
+          (with-slots (organizer summary description location end-time uid rsvp participation-type) event
+            (should (string= organizer "anon@anoncompany.com"))
+            (should (string= summary "Casual coffee talk"))
+            (should (string= description "Coffee talk"))
+            (should (string= location ""))
+            (should (string= (format-time-string "%Y-%m-%d %H:%M" end-time) "2020-09-16 00:00"))
+            (should (string= uid "7b6g3m7iftuo90ei4ul00feqn_R20200915T120000@google.com"))
+            (should rsvp)
+            (should (eq participation-type 'required)))
+          (should (equal (sort (gnus-icalendar-event:recurring-days event) #'<) '(1 2 3 4 5)))
+          (should (string= (gnus-icalendar-event:org-timestamp event) "<2020-09-15 00:00-00:00 +1w>
+<2020-09-16 00:00-00:00 +1w>
+<2020-09-17 00:00-00:00 +1w>
+<2020-09-18 00:00-00:00 +1w>
+<2020-09-21 00:00-00:00 +1w>"))
+
+          )
+      (setenv "TZ" tz))))
+
 (provide 'gnus-icalendar-tests)
 ;;; gnus-icalendar-tests.el ends here
-- 
2.35.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.1.3: 0004-lisp-gnus-gnus-icalendar-Add-gnus-icalendar-event-en.patch --]
[-- Type: text/x-patch, Size: 1033 bytes --]

From 7134ca9ecadf60efbe703ffd2203d25aada7fdfa Mon Sep 17 00:00:00 2001
From: John Hamelink <me@johnhame.link>
Date: Fri, 15 Apr 2022 02:51:11 +0100
Subject: [PATCH 4/5] lisp/gnus/gnus-icalendar: Add gnus-icalendar-event:end

---
 lisp/gnus/gnus-icalendar.el | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index 221d98c63a..036832e1d5 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -159,6 +159,9 @@ gnus-icalendar-event:recurring-days
 (cl-defmethod gnus-icalendar-event:start ((event gnus-icalendar-event))
   (format-time-string "%Y-%m-%d %H:%M" (gnus-icalendar-event:start-time event)))
 
+(cl-defmethod gnus-icalendar-event:end ((event gnus-icalendar-event))
+  (format-time-string "%Y-%m-%d %H:%M" (gnus-icalendar-event:end-time event)))
+
 (defun gnus-icalendar-event--decode-duration (event field)
   (let ((duration (icalendar--get-event-property event field)))
     (unless (null duration)
-- 
2.35.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.1.4: 0003-lisp-gnus-gnus-icalendar-Add-gnus-icalendar-event-de.patch --]
[-- Type: text/x-patch, Size: 1136 bytes --]

From 6245118d273eb7672849fa31e3eb4f3992a6237b Mon Sep 17 00:00:00 2001
From: John Hamelink <me@johnhame.link>
Date: Fri, 15 Apr 2022 02:49:55 +0100
Subject: [PATCH 3/5] lisp/gnus/gnus-icalendar: Add
 gnus-icalendar-event--decode-duration

---
 lisp/gnus/gnus-icalendar.el | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index 8eb3172e01..221d98c63a 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -159,6 +159,11 @@ gnus-icalendar-event:recurring-days
 (cl-defmethod gnus-icalendar-event:start ((event gnus-icalendar-event))
   (format-time-string "%Y-%m-%d %H:%M" (gnus-icalendar-event:start-time event)))
 
+(defun gnus-icalendar-event--decode-duration (event field)
+  (let ((duration (icalendar--get-event-property event field)))
+    (unless (null duration)
+      (icalendar--decode-isoduration duration))))
+
 (defun gnus-icalendar-event--decode-datefield (event field zone-map)
   (let* ((dtdate (icalendar--get-event-property event field))
          (dtdate-zone (icalendar--find-time-zone
-- 
2.35.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.1.5: 0003-lisp-gnus-gnus-icalendar-Add-gnus-icalendar-event-de.patch --]
[-- Type: text/x-patch, Size: 1136 bytes --]

From 6245118d273eb7672849fa31e3eb4f3992a6237b Mon Sep 17 00:00:00 2001
From: John Hamelink <me@johnhame.link>
Date: Fri, 15 Apr 2022 02:49:55 +0100
Subject: [PATCH 3/5] lisp/gnus/gnus-icalendar: Add
 gnus-icalendar-event--decode-duration

---
 lisp/gnus/gnus-icalendar.el | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index 8eb3172e01..221d98c63a 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -159,6 +159,11 @@ gnus-icalendar-event:recurring-days
 (cl-defmethod gnus-icalendar-event:start ((event gnus-icalendar-event))
   (format-time-string "%Y-%m-%d %H:%M" (gnus-icalendar-event:start-time event)))
 
+(defun gnus-icalendar-event--decode-duration (event field)
+  (let ((duration (icalendar--get-event-property event field)))
+    (unless (null duration)
+      (icalendar--decode-isoduration duration))))
+
 (defun gnus-icalendar-event--decode-datefield (event field zone-map)
   (let* ((dtdate (icalendar--get-event-property event field))
          (dtdate-zone (icalendar--find-time-zone
-- 
2.35.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.1.6: 0002-lisp-gnus-gnus-icalendar-Return-nil-if-datefield-cou.patch --]
[-- Type: text/x-patch, Size: 1015 bytes --]

From d46a3e9982eb771255aad802d401c540995c79ac Mon Sep 17 00:00:00 2001
From: John Hamelink <me@johnhame.link>
Date: Fri, 15 Apr 2022 02:40:45 +0100
Subject: [PATCH 2/5] lisp/gnus/gnus-icalendar: Return nil if datefield couldnt
 be decoded

---
 lisp/gnus/gnus-icalendar.el | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index 7d4a98e034..8eb3172e01 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -165,7 +165,8 @@ gnus-icalendar-event--decode-datefield
                        (icalendar--get-event-property-attributes
                         event field) zone-map))
          (dtdate-dec (icalendar--decode-isodatetime dtdate nil dtdate-zone)))
-    (encode-time dtdate-dec)))
+    (unless (null dtdate-dec)
+      (encode-time dtdate-dec))))
 
 (defun gnus-icalendar-event--find-attendee (ical name-or-email)
   (let* ((event (car (icalendar--all-events ical)))
-- 
2.35.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.1.7: 0001-lisp-gnus-gnus-icalendar-Added-date-and-datetime-pre.patch --]
[-- Type: text/x-patch, Size: 2588 bytes --]

From 8de7d6f33199d136d05226548db776fc7b64f59e Mon Sep 17 00:00:00 2001
From: John Hamelink <me@johnhame.link>
Date: Fri, 15 Apr 2022 02:35:05 +0100
Subject: [PATCH 1/5] lisp/gnus/gnus-icalendar: Added date and datetime
 predicates

RFC5545 states that the end-time should be calculated differently
depending on whether DTSTART is a date or a date-time.
---
 lisp/gnus/gnus-icalendar.el            | 14 ++++++++++++++
 test/lisp/gnus/gnus-icalendar-tests.el | 26 ++++++++++++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
index 1bffdf3513..7d4a98e034 100644
--- a/lisp/gnus/gnus-icalendar.el
+++ b/lisp/gnus/gnus-icalendar.el
@@ -214,6 +214,20 @@ gnus-icalendar-event--get-attendee-names
        (attendee-names-by-type "REQ-PARTICIPANT")
        (attendee-names-by-type "OPT-PARTICIPANT")))))
 
+(defun gnus-icalendar--datep (date)
+  "return t if DATE matches a date list."
+  (and (length= date 9)
+       (length=
+        (seq-filter 'integerp (seq-take date 6))
+        3)))
+
+(defun gnus-icalendar--datetimep (datetime)
+  "Return t if DATETIME matches a date-time list."
+  (and (length= datetime 9)
+   (length=
+    (seq-filter 'integerp (seq-take datetime 6))
+    6)))
+
 (defun gnus-icalendar-event-from-ical (ical &optional attendee-name-or-email)
   (let* ((event (car (icalendar--all-events ical)))
          (organizer (replace-regexp-in-string
diff --git a/test/lisp/gnus/gnus-icalendar-tests.el b/test/lisp/gnus/gnus-icalendar-tests.el
index 348ddf9f05..5fecfd3773 100644
--- a/test/lisp/gnus/gnus-icalendar-tests.el
+++ b/test/lisp/gnus/gnus-icalendar-tests.el
@@ -38,6 +38,32 @@ gnus-icalendar-tests--get-ical-event
       (setq event (gnus-icalendar-event-from-buffer (buffer-name) participant)))
     event))
 
+(ert-deftest gnus-icalendar-datetimep ()
+  "Can differentiate between dates and datetimes."
+  (should
+   (equal
+    (gnus-icalendar--datetimep
+     (parse-time-string "2020-09-15 14:00"))
+    t))
+  (should
+   (equal
+    (gnus-icalendar--datetimep
+     (iso8601-parse-date "2020-09-15"))
+    nil)))
+
+(ert-deftest gnus-icalendar-datep ()
+  "Can differentiate between dates and datetimes."
+  (should
+   (equal
+    (gnus-icalendar--datep
+     (iso8601-parse-date "2020-09-15"))
+    t))
+  (should
+   (equal
+    (gnus-icalendar--datep
+     (parse-time-string "2020-09-15 14:00"))
+    nil)))
+
 (ert-deftest gnus-icalendar-parse ()
   "test"
   (let ((tz (getenv "TZ"))
-- 
2.35.2


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 857 bytes --]

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION
  2022-04-14 13:44 bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION John Hamelink
  2022-04-14 14:53 ` Lars Ingebrigtsen
@ 2022-04-17 18:00 ` Paul Eggert
  2022-04-18 19:50   ` John Hamelink
  1 sibling, 1 reply; 7+ messages in thread
From: Paul Eggert @ 2022-04-17 18:00 UTC (permalink / raw)
  To: John Hamelink; +Cc: 54939

> In `icalendar--decode-isodatetime', hours,
> minutes and seconds are set to 0 by default, effectively normalising
> date to datetime. I wonder if there's a function already implemented
> for this that I don't know about yet?

I don't know the answer to your question. That being said, the 
"icalendar--" prefix means this is a private function, so you can change 
it to do what you want, so long as its callers in icalendar.el are 
adjusted accordingly.





^ permalink raw reply	[flat|nested] 7+ messages in thread

* bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION
  2022-04-17 18:00 ` Paul Eggert
@ 2022-04-18 19:50   ` John Hamelink
  2022-04-25 10:50     ` John Hamelink
  0 siblings, 1 reply; 7+ messages in thread
From: John Hamelink @ 2022-04-18 19:50 UTC (permalink / raw)
  To: 54939


[-- Attachment #1.1.1: Type: text/plain, Size: 929 bytes --]

Paul Eggert <eggert@cs.ucla.edu> writes:

>> In `icalendar--decode-isodatetime', hours,
>> minutes and seconds are set to 0 by default, effectively normalising
>> date to datetime. I wonder if there's a function already implemented
>> for this that I don't know about yet?
>
> I don't know the answer to your question.

No problem :)

> That being said, the "icalendar--" prefix means this is a private
> function, so you can change it to do what you want, so long as its
> callers in icalendar.el are adjusted accordingly.

OK, in that case I'll try to fix the function responsibly, and I'll
try to find all usages of the function and change them accordingly.

I've also sent my side of the copy-assignment paperwork back, and I've
asked my employer to sign their side. I already have a clause in my
contract which says something similar to what is said in the
disclaimer, so things should move quickly on that front.

Best,
JH

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 857 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

* bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION
  2022-04-18 19:50   ` John Hamelink
@ 2022-04-25 10:50     ` John Hamelink
  0 siblings, 0 replies; 7+ messages in thread
From: John Hamelink @ 2022-04-25 10:50 UTC (permalink / raw)
  To: 54939


[-- Attachment #1.1.1: Type: text/plain, Size: 883 bytes --]

John Hamelink <me@johnhame.link> writes:

> OK, in that case I'll try to fix the function responsibly, and I'll
> try to find all usages of the function and change them accordingly.

Just wanted to send a holding-pattern update: I'm currently very busy
in the run-up to a local election on the 5th May here in Scotland, so
I probably won't be able to come back to this for a few weeks.

> I've also sent my side of the copy-assignment paperwork back, and I've
> asked my employer to sign their side. I already have a clause in my
> contract which says something similar to what is said in the
> disclaimer, so things should move quickly on that front.

I haven't heard back from FSF on this - which is fine since I'm so
busy - but FWIW I will try to be as responsive as I can if any further
requirements are discovered before the copyright assignment process
has completed.

Best
JH

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 857 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2022-04-25 10:50 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-14 13:44 bug#54939: 29.0.50; icalendar cannot infer the DTEND from DTSTART + DURATION John Hamelink
2022-04-14 14:53 ` Lars Ingebrigtsen
2022-04-14 19:09   ` John Hamelink
2022-04-15  9:39     ` John Hamelink
2022-04-17 18:00 ` Paul Eggert
2022-04-18 19:50   ` John Hamelink
2022-04-25 10:50     ` John Hamelink

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).