DEV Community

Cover image for Creating an iCalendar event with HTML content in Kotlin / JVM
Yassine Benabbas for Technology at Worldline

Posted on • Edited on

Creating an iCalendar event with HTML content in Kotlin / JVM

Introduction

The iCalendar format allows to define calendar events in a simple text file. The most common file extension of an iCalendar file is .ics and is supported by many apps, such as Outlook.

An iCalendar can define many types of information, such as events and to-dos, using many standard properties. Among these properties, an important one is missing for standard events, which is to define the event description in HTML. Indeed, the default description property DESCRIPTION takes only plain text values. Fortunately, there is an experimental property X-ALT-DESC that can supports HTML markup.

In this post, we'll use a library called biweekly in order to programmatically create an iCalendar event with an HTML description. But first let's start with some manually created files.

Manually created iCalendar with an X-ALT-DESC property

The following file illustrates a basic iCalendar event that contains both DESCRIPTION and X-ALT-DESC.



BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//xyz Corp//NONSGML PDA Calendar Version 1.0//EN
BEGIN:VEVENT
UID:uid1@awesome.com
ORGANIZER:mailto:organizer@awesome.com
DTSTAMP:20230101T100000Z
DTSTART:20230101T100000Z
DTEND:20230102T113000Z
SUMMARY:Awesome Event
DESCRIPTION:Please join this awesome event
X-ALT-DESC;FMTTYPE=text/html:<!doctype html><html><body><h1 style="color:blue">Awesome event</h1><p>Please join</p></body></html>
END:VEVENT
END:VCALENDAR


Enter fullscreen mode Exit fullscreen mode

Setting both DESCRIPTION and X-ALT-DESC properties in the same event is valid and works on any tool that is compatible with iCalendar. In fact, if the tool does not support X-ALT-DESC, then DESCRIPTION will be used instead. Tools that do support X-ALT-DESC can ignore the DESCRIPTION field and use X-ALT-DESC instead. That's what Outlook does as we'll see later.

You can test iCalendar by first putting this content in a text file and changing its extension to .ics. Next you can just open it if you have a calendar app installed (such as Outlook) or import it into a Calendar app (such as Google Calendar). You can also download this ics file which already contains the above text.

This is how Outlook renders the ics file:

manual ics outlook

We can note that Outlook ignores DESCRIPTION when X-ALT-DESC is available.

And here is the rendering of Google Calendar. We can note that it just ignores X-ALT-DESC and uses DESCRIPTION instead.

manual ics calendar

Creating an iCalendar with an X-ALT-DESC property with biweekly

biweekly is an iCalendar library which supports the JVM and Android. This means that it can target a wide range of applications. In this chapter, we'll use this library to create and ics file in a Kotlin project. We'll use IntelliJ IDEA community edition because it's free and provide powerful Kotlin support.

So, let's create a new project by choosing Kotlin as language:

new project

After that, we need to add biweekly as a dependency in build.gradle.kts:



dependencies {
    ...
    implementation("net.sf.biweekly:biweekly:0.6.6") // add this line
    ...
}


Enter fullscreen mode Exit fullscreen mode

Next comes the fun part where we write some code. The following snippet creates an iCalendar event and prints it to the console and writes it into a file.



val event = VEvent()
// set start and end dates
val startDateTime = LocalDateTime.of(2022, 1, 1, 10, 0)
event.setDateStart(Date.from(startDateTime.toInstant(ZoneOffset.UTC)), true)
val endDateTime = startDateTime.plusDays(2).plusHours(1).plusMinutes(30)
event.setDateEnd(Date.from(endDateTime.toInstant(ZoneOffset.UTC)), true)
// set summary, description and organizer
event.setSummary("Awesome event")
event.setDescription("Please join this awesome event")
event.setOrganizer("organizer@awesome.com")
// set the HTML content to X-ALT-DESC. We also need to set FMTTYPE parameter to text/html
event.setExperimentalProperty(
    "X-ALT-DESC",
    "<!doctype html><html><body>" +
            "<h1 style='color:blue'>Awesome event</h1>" +
            "<p>Please join</p></body></html>"
).setParameter("FMTTYPE", "text/html")
// create the iCalendar
val ical = ICalendar()
ical.addEvent(event)
// Print it on the console
print(Biweekly.write(ical).go())
// Write to file
val file = File("awesome-event.ics")
Biweekly.write(ical).go(file)


Enter fullscreen mode Exit fullscreen mode

The generated ics is as follows:



BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Michael Angstadt//biweekly 0.6.6//EN
BEGIN:VEVENT
UID:4a945aec-3b58-437d-90fe-7ded8e352ec9
DTSTAMP:20230125T184903Z
DTSTART:20220101T100000Z
DTEND:20220103T113000Z
SUMMARY:Awesome event
DESCRIPTION:Please join this awesome event
ORGANIZER:mailto:organizer@awesome.com
X-ALT-DESC;FMTTYPE=text/html:<!doctype html><html><body><h1 style='color:bl
 ue'>Awesome event</h1><p>Please join</p></body></html>
END:VEVENT
END:VCALENDAR


Enter fullscreen mode Exit fullscreen mode

We can note that biweekly automatically adds the VERSION, PRODID and UID fields, which is always nice.

Opening the ics with outlook show this.

biweekly ics outlook

We can go even further by embedding an image using a base64 src. Here is the improved code that embeds an image in the ics.



val event = VEvent()
val startDateTime = LocalDateTime.of(2023, 1, 1, 10, 0)
event.setDateStart(Date.from(startDateTime.toInstant(ZoneOffset.UTC)), true)
val endDateTime = startDateTime.plusDays(2).plusHours(1).plusMinutes(30)
event.setDateEnd(Date.from(endDateTime.toInstant(ZoneOffset.UTC)), true)
event.setSummary("Awesome event")
event.setDescription("Please join this awesome event")
event.setOrganizer("organizer@awesome.com")
event.setExperimentalProperty(
    "X-ALT-DESC",
    "<!doctype html><html><body>" +
            "<h1 style='color:blue'>Awesome event</h1>" +
            "<p>Please join</p>" +
            "<img src=\"data:image/jpeg;base64,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\" alt=\"\" />" +
            "</body></html>"
).setParameter("FMTTYPE", "text/html")
val ical = ICalendar()
ical.addEvent(event)
print(Biweekly.write(ical).go())
Biweekly.write(ical).go(File("awesome-event-with-image.ics"))


Enter fullscreen mode Exit fullscreen mode

The ics should render as follows in Outlook.

biweekly ics image outlook

Conclusion

In this post, we have created some iCalendar files by hand and by code using biweekly. We also took advantage of the experimental property X-ALT-DESC to display a more fun and colorful description thanks to the power of HTML.

You can find the source code of the project here.

Happy coding.

Top comments (0)