[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
bug#34315: [PATCH] icalendar.el: DURATION fix + more robust timezone han
From: |
Lars Ingebrigtsen |
Subject: |
bug#34315: [PATCH] icalendar.el: DURATION fix + more robust timezone handling |
Date: |
Thu, 01 Oct 2020 03:45:59 +0200 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux) |
thunk2@arcor.de (Thomas Plass) writes:
> - in celebration, I'd like to submit yet another patch that extends
> icalendar.el's timezone handling, this one intended to support
> regions that at some point in the past made a permanent switch from
> a DST/STD scheme to STD-only/DST-only. Cases in point are China
> and Turkey,
> cf. https://en.wikipedia.org/wiki/Daylight_saving_time_in_Asia.
>
> Ulf, would you care to review the code and the included test cases?
Thanks for the code. It was in slightly inconvenient format -- we
prefer just simple patches, so I've reformatted it as such below.
However, the code makes two tests fail:
2 unexpected results:
FAILED icalendar--convert-tz-offset
FAILED icalendar--parse-vtimezone
I haven't actually looked at the failing cases, though.
In addition, the test cases included aren't actually used? Could you
propose some code to use them? (It should go in
test/lisp/calendar/icalendar-tests.el.)
--
(domestic pets only, the antidote for overdose, milk.)
bloggy blog: http://lars.ingebrigtsen.no
diff --git a/lisp/calendar/icalendar.el b/lisp/calendar/icalendar.el
index dab277487e..e4760db3e4 100644
--- a/lisp/calendar/icalendar.el
+++ b/lisp/calendar/icalendar.el
@@ -519,46 +519,56 @@ icalendar--convert-tz-offset
(dtstart (car (cddr (assq 'DTSTART alist))))
(no-dst (or rdate-p (equal offsetto offsetfrom))))
;; FIXME: the presence of an RDATE is assumed to denote the first day of
the year
- (when (and offsetto dtstart (or rrule-value no-dst))
- (let* ((rrule (icalendar--split-value rrule-value))
- (freq (cadr (assq 'FREQ rrule)))
- (bymonth (cadr (assq 'BYMONTH rrule)))
- (byday (cadr (assq 'BYDAY rrule))))
- ;; FIXME: we don't correctly handle WKST here.
- (if (or no-dst (and (string= freq "YEARLY") bymonth))
- (cons
- (concat
- ;; Fake a name.
- (if dst-p "DST" "STD")
- ;; For TZ, OFFSET is added to the local time. So,
- ;; invert the values.
- (if (eq (aref offsetto 0) ?-) "+" "-")
- (substring offsetto 1 3)
- ":"
- (substring offsetto 3 5))
- ;; The start time.
- (let* ((day (if no-dst
- 1
- (icalendar--get-weekday-number (substring byday
-2))))
- (week (if no-dst
- "1"
- (if (eq day -1)
- byday
- (substring byday 0 -2)))))
- ;; "Translate" the iCalendar way to specify the last
- ;; (sun|mon|...)day in month to the tzset way.
- (if (string= week "-1") ; last day as iCalendar calls it
+ (if (and offsetto dtstart (or rrule-value no-dst))
+ (let* ((rrule (icalendar--split-value rrule-value))
+ (freq (cadr (assq 'FREQ rrule)))
+ (bymonth (cadr (assq 'BYMONTH rrule)))
+ (byday (cadr (assq 'BYDAY rrule))))
+ ;; FIXME: we don't correctly handle WKST here.
+ (if (or no-dst (and (string= freq "YEARLY") bymonth))
+ (cons
+ (concat
+ ;; Fake a name.
+ (if dst-p "DST" "STD")
+ ;; For TZ, OFFSET is added to the local time. So,
+ ;; invert the values.
+ (if (eq (aref offsetto 0) ?-) "+" "-")
+ (substring offsetto 1 3)
+ ":"
+ (substring offsetto 3 5))
+ ;; The start time.
+ (let* ((day (if no-dst
+ 1
+ (icalendar--get-weekday-number (substring byday
-2))))
+ (week (if no-dst
+ "1"
+ (if (eq day -1)
+ byday
+ (substring byday 0 -2)))))
+ ;; "Translate" the iCalendar way to specify the last
+ ;; (sun|mon|...)day in month to the tzset way.
+ (if (string= week "-1") ; last day as iCalendar calls it
(setq week "5")) ; last day as tzset calls it
(when no-dst (setq bymonth "1"))
(concat "M" bymonth "." week "." (if (eq day -1) "0"
(int-to-string day))
- ;; Start time.
- "/"
- (substring dtstart -6 -4)
- ":"
- (substring dtstart -4 -2)
- ":"
- (substring dtstart -2)))))))))
+ ;; Start time.
+ "/"
+ (substring dtstart -6 -4)
+ ":"
+ (substring dtstart -4 -2)
+ ":"
+ (substring dtstart -2))))))
+ ;; neither RRULE nor RDATE present: return the offset and a placeholder
+ (cons
+ (concat
+ ;; Fake a name.
+ (if dst-p "DST" "STD")
+ (if (eq (aref offsetto 0) ?-) "+" "-")
+ (substring offsetto 1 3)
+ ":"
+ (substring offsetto 3 5))
+ dtstart))))
(defun icalendar--parse-vtimezone (alist)
"Turn a VTIMEZONE ALIST into a cons (ID . TZ-STRING).
@@ -571,34 +581,59 @@ icalendar--parse-vtimezone
(standard (cadr (cdar (icalendar--get-most-recent-observance alist
'STANDARD))))
(std (and standard (icalendar--convert-tz-offset standard nil))))
(if (and tz-id std)
- (cons tz-id
- (if day
- (concat (car std) (car day)
- "," (cdr day) "," (cdr std))
- (car std))))))
+ (cons tz-id
+ (if (and (not (assq 'RRULE daylight))
+ (not (assq 'RRULE standard)))
+ (let ((daylight-rdate (and (assq 'RDATE daylight)
+
(icalendar--get-most-recent-observance-from-sub-comp
+ daylight
+ '(RDATE))))
+ (standard-rdate (and (assq 'RDATE standard)
+
(icalendar--get-most-recent-observance-from-sub-comp
+ standard
+ '(RDATE)))))
+ (if (and daylight-rdate
+ standard-rdate
+ (string-greaterp daylight-rdate standard-rdate))
+ (car day)
+ (car std)))
+ (if day
+ (concat (car std) (car day)
+ "," (cdr day) "," (cdr std))
+ (car std)))))))
(defun icalendar--get-most-recent-observance (alist sub-comp)
- "Return the latest observance for SUB-COMP DAYLIGHT or STANDARD.
+ "Return the latest observance of all SUB-COMPs DAYLIGHT or STANDARD.
ALIST is a VTIMEZONE potentially containing historical records."
-;FIXME?: "most recent" should be relative to a given date
+;FIXME: "most recent" should be relative to a given date and
+; avoid selecting a SUB-COMP valid for dates in the future.
(let ((components (icalendar--get-children alist sub-comp)))
(list
(car
(sort components
#'(lambda (a b)
- (let* ((get-recent (lambda (n)
- (car
- (sort
- (delq nil
- (mapcar (lambda (p)
- (and (memq (car p)
'(DTSTART RDATE))
- (car (cddr p))))
- n))
- 'string-greaterp))))
- (a-recent (funcall get-recent (car (cddr a))))
- (b-recent (funcall get-recent (car (cddr b)))))
+ (let* ((a-recent
(icalendar--get-most-recent-observance-from-sub-comp
+ (car (cddr a))
+ '(DTSTART RDATE)))
+ (b-recent
(icalendar--get-most-recent-observance-from-sub-comp
+ (car (cddr b))
+ '(DTSTART RDATE))))
(string-greaterp a-recent b-recent))))))))
+(defun icalendar--get-most-recent-observance-from-sub-comp (alist sym-list)
+ "Return the latest observance for ALIST DAYLIGHT or STANDARD.
+ALIST is an individual DAYLIGHT or STANDARD.
+SYM-LIST is a list of property names DTSTART and/or RDATE
+for filtering ALIST."
+ (car
+ (sort
+ (delq nil
+ (mapcar (lambda (p)
+ (when (memq (car p) sym-list)
+ (car (cddr p))))
+ alist))
+ 'string-greaterp)))
+
(defun icalendar--convert-all-timezones (icalendar)
"Convert all timezones in the ICALENDAR into an alist.
Each element of the alist is a cons (ID . TZ-STRING),
diff --git
a/test/lisp/calendar/icalendar-resources/Asia_Istanbul_20200924T120000_in-calendar_VTIMEZONE_tzurl_org.ics
b/test/lisp/calendar/icalendar-resources/Asia_Istanbul_20200924T120000_in-calendar_VTIMEZONE_tzurl_org.ics
new file mode 100644
index 0000000000..6425909be6
--- /dev/null
+++
b/test/lisp/calendar/icalendar-resources/Asia_Istanbul_20200924T120000_in-calendar_VTIMEZONE_tzurl_org.ics
@@ -0,0 +1,219 @@
+BEGIN:VCALENDAR
+PRODID:manual
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Asia/Istanbul
+TZURL:http://tzurl.org/zoneinfo/Asia/Istanbul
+X-LIC-LOCATION:Asia/Istanbul
+BEGIN:STANDARD
+TZOFFSETFROM:+015552
+TZOFFSETTO:+015656
+TZNAME:IMT
+DTSTART:18800101T000000
+RDATE:18800101T000000
+END:STANDARD
+BEGIN:STANDARD
+TZOFFSETFROM:+015656
+TZOFFSETTO:+0200
+TZNAME:EET
+DTSTART:19101001T000000
+RDATE:19101001T000000
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0300
+TZNAME:EEST
+DTSTART:19160501T000000
+RDATE:19160501T000000
+RDATE:19200328T000000
+RDATE:19210403T000000
+RDATE:19220326T000000
+RDATE:19240513T000000
+RDATE:19250501T000000
+RDATE:19400630T000000
+RDATE:19401201T000000
+RDATE:19420401T000000
+RDATE:19450402T000000
+RDATE:19460601T000000
+RDATE:19470420T000000
+RDATE:19480418T000000
+RDATE:19490410T000000
+RDATE:19500419T000000
+RDATE:19510422T000000
+RDATE:19620715T000000
+RDATE:19640515T000000
+RDATE:19700503T000000
+RDATE:19710502T000000
+RDATE:19720507T000000
+RDATE:19730603T010000
+RDATE:19740331T020000
+RDATE:19750330T000000
+RDATE:19760601T000000
+RDATE:19770403T000000
+RDATE:19780402T000000
+RDATE:19860330T010000
+RDATE:19870329T010000
+RDATE:19880327T010000
+RDATE:19890326T010000
+RDATE:19900325T010000
+RDATE:19910331T010000
+RDATE:19920329T010000
+RDATE:19930328T010000
+RDATE:19940320T010000
+RDATE:19950326T010000
+RDATE:19960331T010000
+RDATE:19970330T010000
+RDATE:19980329T010000
+RDATE:19990328T010000
+RDATE:20000326T010000
+RDATE:20010325T010000
+RDATE:20020331T010000
+RDATE:20030330T010000
+RDATE:20040328T010000
+RDATE:20050327T010000
+RDATE:20060326T010000
+RDATE:20070325T030000
+RDATE:20080330T030000
+RDATE:20090329T030000
+RDATE:20100328T030000
+RDATE:20110328T030000
+RDATE:20120325T030000
+RDATE:20130331T030000
+RDATE:20140331T030000
+RDATE:20150329T030000
+RDATE:20160327T030000
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0300
+TZOFFSETTO:+0200
+TZNAME:EET
+DTSTART:19161001T000000
+RDATE:19161001T000000
+RDATE:19201025T000000
+RDATE:19211003T000000
+RDATE:19221008T000000
+RDATE:19241001T000000
+RDATE:19251001T000000
+RDATE:19401005T000000
+RDATE:19410921T000000
+RDATE:19421101T000000
+RDATE:19451008T000000
+RDATE:19461001T000000
+RDATE:19471005T000000
+RDATE:19481003T000000
+RDATE:19491002T000000
+RDATE:19501008T000000
+RDATE:19511008T000000
+RDATE:19621008T000000
+RDATE:19641001T000000
+RDATE:19701004T000000
+RDATE:19711003T000000
+RDATE:19721008T000000
+RDATE:19731104T030000
+RDATE:19741103T050000
+RDATE:19751026T000000
+RDATE:19761031T000000
+RDATE:19771016T000000
+RDATE:19850928T000000
+RDATE:19860928T020000
+RDATE:19870927T020000
+RDATE:19880925T020000
+RDATE:19890924T020000
+RDATE:19900930T020000
+RDATE:19910929T020000
+RDATE:19920927T020000
+RDATE:19930926T020000
+RDATE:19940925T020000
+RDATE:19950924T020000
+RDATE:19961027T020000
+RDATE:19971026T020000
+RDATE:19981025T020000
+RDATE:19991031T020000
+RDATE:20001029T020000
+RDATE:20011028T020000
+RDATE:20021027T020000
+RDATE:20031026T020000
+RDATE:20041031T020000
+RDATE:20051030T020000
+RDATE:20061029T020000
+RDATE:20071028T040000
+RDATE:20081026T040000
+RDATE:20091025T040000
+RDATE:20101031T040000
+RDATE:20111030T040000
+RDATE:20121028T040000
+RDATE:20131027T040000
+RDATE:20141026T040000
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0300
+TZOFFSETTO:+0400
+TZNAME:+04
+DTSTART:19781015T000000
+RDATE:19781015T000000
+RDATE:19800406T030000
+RDATE:19810329T030000
+RDATE:19820328T030000
+RDATE:19830731T000000
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0400
+TZOFFSETTO:+0400
+TZNAME:+04
+DTSTART:19790401T030000
+RDATE:19790401T030000
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0400
+TZOFFSETTO:+0300
+TZNAME:+03
+DTSTART:19791015T000000
+RDATE:19791015T000000
+RDATE:19801013T000000
+RDATE:19811012T000000
+RDATE:19821011T000000
+RDATE:19831002T000000
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0300
+TZOFFSETTO:+0300
+TZNAME:EEST
+DTSTART:19850420T000000
+RDATE:19850420T000000
+RDATE:20151025T040000
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0200
+TZNAME:EET
+DTSTART:20070101T000000
+RDATE:20070101T000000
+RDATE:20110327T030000
+RDATE:20140330T030000
+END:STANDARD
+BEGIN:STANDARD
+TZOFFSETFROM:+0300
+TZOFFSETTO:+0200
+DTSTART:20151108T040000
+RDATE:20151108T040000
+END:STANDARD
+BEGIN:STANDARD
+TZOFFSETFROM:+0300
+TZOFFSETTO:+0300
+TZNAME:+03
+DTSTART:20160907T000000
+RDATE:20160907T000000
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CLASS:PUBLIC
+DTSTART;TZID=Asia/Istanbul:20200924T120000
+DTEND;TZID=Asia/Istanbul:20200924T130000
+UID:Asia_Istanbul_20200924T120000_in-calendar_VTIMEZONE_tzurl_org.ics
+DTSTAMP:20190127T140400
+DESCRIPTION:date 2020-09-24, Istanbul local time 12:00 UTC+3, in-calendar
+ VTIMEZONE as returned by http://tzurl.org/zoneinfo/Asia/Istanbul
+SUMMARY:date 2020-09-24, Istanbul local time 12:00 UTC+3, in-calendar
+ VTIMEZONE as returned by http://tzurl.org/zoneinfo/Asia/Istanbul
+END:VEVENT
+END:VCALENDAR
diff --git
a/test/lisp/calendar/icalendar-resources/Asia_Shanghai_20200916T070000_in-calendar_VTIMEZONE_multi_DAYLIGHT_STANDARD.ics
b/test/lisp/calendar/icalendar-resources/Asia_Shanghai_20200916T070000_in-calendar_VTIMEZONE_multi_DAYLIGHT_STANDARD.ics
new file mode 100644
index 0000000000..26dd3cbfb8
--- /dev/null
+++
b/test/lisp/calendar/icalendar-resources/Asia_Shanghai_20200916T070000_in-calendar_VTIMEZONE_multi_DAYLIGHT_STANDARD.ics
@@ -0,0 +1,116 @@
+BEGIN:VCALENDAR
+PRODID:manual
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:PUBLISH
+BEGIN:VTIMEZONE
+TZID:Asia/Shanghai
+X-LIC-LOCATION:Asia/Shanghai
+BEGIN:STANDARD
+TZNAME:CST
+DTSTART:19411001T000000
+TZOFFSETFROM:+0900
+TZOFFSETTO:+0800
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CDT
+DTSTART:19860504T000000
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0900
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CST
+DTSTART:19860914T000000
+TZOFFSETFROM:+0900
+TZOFFSETTO:+0800
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CDT
+DTSTART:19870412T000000
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0900
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CST
+DTSTART:19870913T000000
+TZOFFSETFROM:+0900
+TZOFFSETTO:+0800
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CDT
+DTSTART:19880410T000000
+TZOFFSETFROM:+0800
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0900
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CST
+DTSTART:19880911T000000
+TZOFFSETFROM:+0900
+TZOFFSETTO:+0800
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CDT
+DTSTART:19890416T000000
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0900
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CST
+DTSTART:19890917T000000
+TZOFFSETFROM:+0900
+TZOFFSETTO:+0800
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CDT
+DTSTART:19900415T000000
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0900
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CST
+DTSTART:19900916T000000
+TZOFFSETFROM:+0900
+TZOFFSETTO:+0800
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CDT
+DTSTART:19910414T000000
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0900
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CST
+DTSTART:19910915T000000
+TZOFFSETFROM:+0900
+TZOFFSETTO:+0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VTIMEZONE
+TZID:Etc/UTC
+X-LIC-LOCATION:Etc/UTC
+BEGIN:STANDARD
+TZNAME:UTC
+DTSTART:19700101T000000
+TZOFFSETFROM:+0000
+TZOFFSETTO:+0000
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CLASS:PUBLIC
+DTSTART;TZID=Asia/Shanghai:20200916T070000
+DTEND;TZID=Asia/Shanghai:20200916T080000
+UID:Asia_Shanghai_20200916T070000_in-calendar_VTIMEZONE_multi_DAYLIGHT_STANDARD.ics
+DTSTAMP:20190127T140400
+DESCRIPTION:date 2020-09-16, Shanghai local time 07:00 UTC+8, in-calendar
+ VTIMEZONE with multiple DAYLIGHT and STANDARD sub-components, cf.
+ https://techcommunity.microsoft.com/t5/office-365/import-ics-to-office-365
+ -calendar-but-the-event-time-is-wrong/td-p/215332
+SUMMARY:date 2020-09-16, Shanghai local time 07:00 UTC+8, in-calendar
+ VTIMEZONE with multiple DAYLIGHT and STANDARD sub-components, cf.
+ https://techcommunity.microsoft.com/t5/office-365/import-ics-to-office-365
+ -calendar-but-the-event-time-is-wrong/td-p/215332
+END:VEVENT
+END:VCALENDAR
+
+
diff --git
a/test/lisp/calendar/icalendar-resources/Asia_Shanghai_20200916T070000_in-calendar_VTIMEZONE_tzurl_org.ics
b/test/lisp/calendar/icalendar-resources/Asia_Shanghai_20200916T070000_in-calendar_VTIMEZONE_tzurl_org.ics
new file mode 100644
index 0000000000..ccd39bd114
--- /dev/null
+++
b/test/lisp/calendar/icalendar-resources/Asia_Shanghai_20200916T070000_in-calendar_VTIMEZONE_tzurl_org.ics
@@ -0,0 +1,65 @@
+BEGIN:VCALENDAR
+PRODID:manual
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Asia/Shanghai
+TZURL:http://tzurl.org/zoneinfo/Asia/Shanghai
+X-LIC-LOCATION:Asia/Shanghai
+BEGIN:STANDARD
+TZOFFSETFROM:+080543
+TZOFFSETTO:+0800
+TZNAME:CST
+DTSTART:19010101T000000
+RDATE:19010101T000000
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0900
+TZNAME:CDT
+DTSTART:19400601T000000
+RDATE:19400601T000000
+RDATE:19410315T000000
+RDATE:19420131T000000
+RDATE:19460515T000000
+RDATE:19470415T000000
+RDATE:19480501T000000
+RDATE:19490501T000000
+RDATE:19860504T020000
+RDATE:19870412T020000
+RDATE:19880417T020000
+RDATE:19890416T020000
+RDATE:19900415T020000
+RDATE:19910414T020000
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0900
+TZOFFSETTO:+0800
+TZNAME:CST
+DTSTART:19401012T235959
+RDATE:19401012T235959
+RDATE:19411101T235959
+RDATE:19450901T235959
+RDATE:19460930T235959
+RDATE:19471031T235959
+RDATE:19480930T235959
+RDATE:19490528T000000
+RDATE:19860914T020000
+RDATE:19870913T020000
+RDATE:19880911T020000
+RDATE:19890917T020000
+RDATE:19900916T020000
+RDATE:19910915T020000
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CLASS:PUBLIC
+DTSTART;TZID=Asia/Shanghai:20200916T070000
+DTEND;TZID=Asia/Shanghai:20200916T080000
+UID:Asia_Shanghai_20200916T070000_in-calendar_VTIMEZONE_tzurl_org.ics
+DTSTAMP:20190127T140400
+DESCRIPTION:date 2020-09-16, Shanghai local time 07:00 UTC+8, in-calendar
+ VTIMEZONE as returned by http://tzurl.org/zoneinfo/Asia/Shanghai
+SUMMARY:date 2020-09-16, Shanghai local time 07:00 UTC+8, in-calendar
+ VTIMEZONE as returned by http://tzurl.org/zoneinfo/Asia/Shanghai
+END:VEVENT
+END:VCALENDAR