Add per-entry timezone support for icalendar export

* lisp/ox-icalendar.el (org-icalendar-entry): Look for a "TIMEZONE"
  property.
(org-icalendar--vevent, org-icalendar--vtodo): Accept additional
timezone argument.
(org-icalendar-convert-timestamp): Change parameter name to "tz", and
accept a wider variety of values.

* doc/org.texi: Mention in manual.
This commit is contained in:
Eric Abrahamsen 2017-08-20 00:11:18 +02:00 committed by Nicolas Goaziou
parent 6a590738b1
commit 42458e682d
3 changed files with 41 additions and 21 deletions

View File

@ -14162,9 +14162,10 @@ and write it to @code{org-icalendar-combined-agenda-file} file name.
@cindex property, SUMMARY
@cindex property, DESCRIPTION
@cindex property, LOCATION
The iCalendar export back-end includes SUMMARY, DESCRIPTION and LOCATION
properties from the Org entries when exporting. To force the back-end to
inherit the LOCATION property, configure the
@cindex property, TIMEZONE
The iCalendar export back-end includes SUMMARY, DESCRIPTION, LOCATION and
TIMEZONE properties from the Org entries when exporting. To force the
back-end to inherit the LOCATION property, configure the
@code{org-use-property-inheritance} variable.
When Org entries do not have SUMMARY, DESCRIPTION and LOCATION properties,
@ -14173,6 +14174,12 @@ derives the description from the body of the Org item. The
@code{org-icalendar-include-body} variable limits the maximum number of
characters of the content are turned into its description.
The TIMEZONE property can be used to specify a per-entry time zone, and will
be applied to any entry with timestamp information. Time zones should be
specified as per the IANA time zone database format, e.g.@: ``Asia/Almaty''.
Alternately, the property value can be ``UTC'', to force UTC time for this
entry only.
Exporting to iCalendar format depends in large part on the capabilities of
the destination application. Some are more lenient than others. Consult the
Org mode FAQ for advice on specific applications.

View File

@ -106,11 +106,14 @@ You can use =ob-scala.el= as packaged in scala-mode, available from the
MELPA repository.
** New features
*** iCalendar export respects a TIMEZONE property
Set the TIMEZONE property on an entry to specify a time zone for that
entry only during iCalendar export. The property value should be
specified as in "Europe/London".
*** ~org-attach~ can move directory contents
When setting a new directory for an entry, org-attach offers to move
files over from the old directory. Using a prefix arg will reset the
directory to old, ID based one.
*** New Org duration library
This new library implements tools to read and print time durations in
various formats (e.g., "H:MM", or "1d 2h 3min"...).

View File

@ -341,7 +341,7 @@ A headline is blocked when either
(1- (length org-icalendar-date-time-format))) ?Z))
(defvar org-agenda-default-appointment-duration) ; From org-agenda.el.
(defun org-icalendar-convert-timestamp (timestamp keyword &optional end utc)
(defun org-icalendar-convert-timestamp (timestamp keyword &optional end tz)
"Convert TIMESTAMP to iCalendar format.
TIMESTAMP is a timestamp object. KEYWORD is added in front of
@ -352,8 +352,11 @@ Also increase the hour by two (if time string contains a time),
or the day by one (if it does not contain a time) when no
explicit ending time is specified.
When optional argument UTC is non-nil, time will be expressed in
Universal Time, ignoring `org-icalendar-date-time-format'."
When optional argument TZ is non-nil, timezone data time will be
added to the timestamp. It can be the string \"UTC\", to use UTC
time, or a string in the IANA TZ database
format (e.g. \"Europe/London\"). In either case, the value of
`org-icalendar-date-time-format' will be ignored."
(let* ((year-start (org-element-property :year-start timestamp))
(year-end (org-element-property :year-end timestamp))
(month-start (org-element-property :month-start timestamp))
@ -387,8 +390,9 @@ Universal Time, ignoring `org-icalendar-date-time-format'."
(concat
keyword
(format-time-string
(cond (utc ":%Y%m%dT%H%M%SZ")
(cond ((string-equal tz "UTC") ":%Y%m%dT%H%M%SZ")
((not with-time-p) ";VALUE=DATE:%Y%m%d")
((stringp tz) (concat ";TZID=" tz ":%Y%m%dT%H%M%S"))
(t (replace-regexp-in-string "%Z"
org-icalendar-timezone
org-icalendar-date-time-format
@ -396,7 +400,10 @@ Universal Time, ignoring `org-icalendar-date-time-format'."
;; Convert timestamp into internal time in order to use
;; `format-time-string' and fix any mistake (i.e. MI >= 60).
(encode-time 0 mi h d m y)
(and (or utc (and with-time-p (org-icalendar-use-UTC-date-time-p)))
(and (or (string-equal tz "UTC")
(and (null tz)
with-time-p
(org-icalendar-use-UTC-date-time-p)))
t)))))
(defun org-icalendar-dtstamp ()
@ -545,7 +552,8 @@ inlinetask within the section."
contents 0 (min (length contents)
org-icalendar-include-body))))
(org-icalendar-include-body (org-trim contents)))))))
(cat (org-icalendar-get-categories entry info)))
(cat (org-icalendar-get-categories entry info))
(tz (org-element-property :TIMEZONE entry)))
(concat
;; Events: Delegate to `org-icalendar--vevent' to generate
;; "VEVENT" component from scheduled, deadline, or any
@ -556,14 +564,14 @@ inlinetask within the section."
org-icalendar-use-deadline)
(org-icalendar--vevent
entry deadline (concat "DL-" uid)
(concat "DL: " summary) loc desc cat)))
(concat "DL: " summary) loc desc cat tz)))
(let ((scheduled (org-element-property :scheduled entry)))
(and scheduled
(memq (if todo-type 'event-if-todo 'event-if-not-todo)
org-icalendar-use-scheduled)
(org-icalendar--vevent
entry scheduled (concat "SC-" uid)
(concat "S: " summary) loc desc cat)))
(concat "S: " summary) loc desc cat tz)))
;; When collecting plain timestamps from a headline and its
;; title, skip inlinetasks since collection will happen once
;; ENTRY is one of them.
@ -581,7 +589,7 @@ inlinetask within the section."
((t) t)))
(let ((uid (format "TS%d-%s" (cl-incf counter) uid)))
(org-icalendar--vevent
entry ts uid summary loc desc cat))))
entry ts uid summary loc desc cat tz))))
info nil (and (eq type 'headline) 'inlinetask))
""))
;; Task: First check if it is appropriate to export it. If
@ -595,7 +603,7 @@ inlinetask within the section."
(not (org-icalendar-blocked-headline-p
entry info))))
((t) (eq todo-type 'todo))))
(org-icalendar--vtodo entry uid summary loc desc cat))
(org-icalendar--vtodo entry uid summary loc desc cat tz))
;; Diary-sexp: Collect every diary-sexp element within ENTRY
;; and its title, and transcode them. If ENTRY is
;; a headline, skip inlinetasks: they will be handled
@ -626,7 +634,7 @@ inlinetask within the section."
contents))))
(defun org-icalendar--vevent
(entry timestamp uid summary location description categories)
(entry timestamp uid summary location description categories timezone)
"Create a VEVENT component.
ENTRY is either a headline or an inlinetask element. TIMESTAMP
@ -635,7 +643,8 @@ is the unique identifier for the event. SUMMARY defines a short
summary or subject for the event. LOCATION defines the intended
venue for the event. DESCRIPTION provides the complete
description of the event. CATEGORIES defines the categories the
event belongs to.
event belongs to. TIMEZONE specifies a time zone for this event
only.
Return VEVENT component as a string."
(org-icalendar-fold-string
@ -645,8 +654,8 @@ Return VEVENT component as a string."
(concat "BEGIN:VEVENT\n"
(org-icalendar-dtstamp) "\n"
"UID:" uid "\n"
(org-icalendar-convert-timestamp timestamp "DTSTART") "\n"
(org-icalendar-convert-timestamp timestamp "DTEND" t) "\n"
(org-icalendar-convert-timestamp timestamp "DTSTART" nil timezone) "\n"
(org-icalendar-convert-timestamp timestamp "DTEND" t timezone) "\n"
;; RRULE.
(when (org-element-property :repeater-type timestamp)
(format "RRULE:FREQ=%s;INTERVAL=%d\n"
@ -664,7 +673,7 @@ Return VEVENT component as a string."
"END:VEVENT"))))
(defun org-icalendar--vtodo
(entry uid summary location description categories)
(entry uid summary location description categories timezone)
"Create a VTODO component.
ENTRY is either a headline or an inlinetask element. UID is the
@ -672,6 +681,7 @@ unique identifier for the task. SUMMARY defines a short summary
or subject for the task. LOCATION defines the intended venue for
the task. DESCRIPTION provides the complete description of the
task. CATEGORIES defines the categories the task belongs to.
TIMEZONE specifies a time zone for this TODO only.
Return VTODO component as a string."
(let ((start (or (and (memq 'todo-start org-icalendar-use-scheduled)
@ -690,11 +700,11 @@ Return VTODO component as a string."
(concat "BEGIN:VTODO\n"
"UID:TODO-" uid "\n"
(org-icalendar-dtstamp) "\n"
(org-icalendar-convert-timestamp start "DTSTART") "\n"
(org-icalendar-convert-timestamp start "DTSTART" nil timezone) "\n"
(and (memq 'todo-due org-icalendar-use-deadline)
(org-element-property :deadline entry)
(concat (org-icalendar-convert-timestamp
(org-element-property :deadline entry) "DUE")
(org-element-property :deadline entry) "DUE" nil timezone)
"\n"))
"SUMMARY:" summary "\n"
(and (org-string-nw-p location) (format "LOCATION:%s\n" location))