Wednesday Mar 16, 2011

ical4j 1.0 released

The ical4j library has just reached 1.0 state. See http://sourceforge.net/news/?group_id=107024 . It has actually been stable for quite some time already and quite a few java based Calendar projects are heavily relying on it.

Thursday Jan 29, 2009

VTODO with DUE date in Apple iCal

Following my previous post, I've started to look at how due dates are leveraged by real clients, starting with Apple iCal.

Apple iCal (on Mac OS 10.5) let you create VTODOs ("To Do Items") and store them on a CalDAV Server. By default those todos have no associated dates but one can add a due date (no time):

Here is how such a todo is stored on the CalDAV server:

...
DTSTART;TZID=Europe/Paris:20040101T120000
DUE;VALUE=DATE:20090127
... 

The VTODO component has a correct DUE property but it also has a DTSTART property which:

  1. comes out of nowhere (not visible in the UI),
  2. has no meaningful value (value hardcoded to 01/01/2004).

Client that takes the DTSTART into consideration to do some processing (e.g. to build a view) might show inconsistent results when processing such a VTODO.

Worth, the DTSTART property has a DATETIME value when the DUE property has a DATE value, causing the VTODO to be invalid per the new calsify spec.

If one tries to add an alarm (trigger -15 minutes before), the VTODO is stored as:

...
DTSTART;TZID=Europe/Paris:20040101T120000
DUE;VALUE=DATE:20090127
BEGIN:VALARM
X-WR-ALARMUID:5874F585-58CD-4357-88CF-951AEAF8663A
ACTION:AUDIO
TRIGGER:-PT15M
ATTACH;VALUE=URI:Basso
END:VALARM
... 

The TRIGGER property is missing a RELATED=END parameter which would link the alarm time to the DUE date. As a consequence, other clients will consider the TRIGGER to be relative to the DTSTART property. Since its value is meaningless (some time in 2004), the alarm will probably disappear (or be triggered as soon as the client fetches the todo).

Friday Jan 16, 2009

Disconnect between icalendar and iTIP mandatory properties

Never noticed it before iTIP validation was added to ical4j but there is a disconnect between the mandatory properties specified by iCalendar and the ones specified in iTIP REQUESTs.

For VEVENT, the SUMMARY is mandatory in iTIP REQUEST but not in iCalendar.

For VTODO, the DTSTART, PRIORITY and SUMMARY are mandatory in iTIP REQUEST but not in iCalendar.

In the context of the new CalDAV scheduling draft where automatic scheduling takes place as soon as a calendar resource has an organizer and attendees, this implicitely means that those calendar resource should also have the iTIP mandatory properties.

For SUMMARY and PRIORITY, client implementers can always add them with a value of null and 0 respectively but for DTSTART, this is more problematic.

When assigning a task to somebody else, it is possible (and actually quite likely) that one would set the DUE date and not the DTSTART.

Monday Dec 01, 2008

iCalendar to XML conversion using ical4j

The ical4j library is probably the reference java library for manipulating iCalendar data. It offers a full representation of the iCalendar (RFC 2445) data model (calendar components, properties and parameters), a parser, as well a set of helper classes to do date/time calculations.

A nice design point is that there is a good separation between the act of parsing an iCalendar stream and the building of ical4j objects.

This is achieved by using an event driven model where the parser calls back a handler object whenever a new event (beginning/end of a component or property,...) is encountered by the parser. In other words, the parser is the iCalendar equivalent of an XML SAX parser.

Here is the interface that a handler has to implement:

public interface ContentHandler {

    /\*\*
     \* Triggers the start of handling a calendar.
     \*/
    void startCalendar();

    /\*\*
     \* Triggers the end of handling a calendar.
     \*/
    void endCalendar();

    /\*\*
     \* Triggers the start of handling a component.
     \*/
    void startComponent(String name);

    /\*\*
     \* Triggers the end of handling a component.
     \*/
    void endComponent(String name);

    /\*\*
     \* Triggers the start of handling a property.
     \*/
    void startProperty(String name);

    /\*\*
     \* Triggers the handling of a property value.
     \*/
    void propertyValue(String value) throws URISyntaxException, ParseException,
            IOException;

    /\*\*
     \* Triggers the end of handling a property.
     \*/
    void endProperty(String name);

    /\*\*
     \* Triggers the handling of a parameter.
     \*/
    void parameter(String name, String value) throws URISyntaxException;
}

And here is a sample class implementing this interface to produce a very basic iCalendar to XML conversion (using the StAX XML API):

public final class XMLHandler implements ContentHandler {  
    
    private final XMLStreamWriter xmlWriter;
    
    public XMLHandler(XMLStreamWriter xmlWriter) {
        this.xmlWriter = xmlWriter;
    }
    
    /\*\*
     \* {@inheritDoc}
     \*/
    public void startCalendar() {
        writeStartElement("vcalendar");
    }

    /\*\*
     \* {@inheritDoc}
     \*/
    public void endCalendar() {
        writeEndElement();
    }

    /\*\*
     \* {@inheritDoc}
     \*/
    public void startComponent(String name) {
        writeStartElement(name);
    }

    /\*\*
     \* {@inheritDoc}
     \*/
    public void endComponent(String name) {
        writeEndElement();
    }

    /\*\*
     \* {@inheritDoc}
     \*/
    public void startProperty(String name) {
        writeStartElement(name);
    }

    /\*\*
     \* {@inheritDoc}
     \*/
    public void propertyValue(String value) throws URISyntaxException, ParseException,
            IOException {
        // would need unwrapping
        writeCharacters(value);
    }

    /\*\*
     \* {@inheritDoc}
     \*/
    public void endProperty(String name) {
        writeEndElement();
    }

    /\*\*
     \* {@inheritDoc}
     \*/
    public void parameter(String name, String value) throws URISyntaxException {
        writeAttribute(name, value);
    }
    
    private void writeStartElement(String name) {
        try {
            xmlWriter.writeStartElement(name.toLowerCase());
        } catch (XMLStreamException xe) {
            throw new IllegalStateException("got xml error while writing", xe);
        }
    }
    
    private void writeCharacters(String value) {
        try {
            xmlWriter.writeCharacters(value);
        } catch (XMLStreamException xe) {
            throw new IllegalStateException("got xml error while writing", xe);
        }
    }
    
    private void writeAttribute(String name, String value) {
        try {
            xmlWriter.writeAttribute(name.toLowerCase(), value);
        } catch (XMLStreamException xe) {
            throw new IllegalStateException("got xml error while writing", xe);
        }
    }
    
    private void writeEndElement() {
        try {
            xmlWriter.writeEndElement();
            xmlWriter.writeCharacters("\\r\\n");
        } catch (XMLStreamException xe) {
            throw new IllegalStateException("got xml error while writing", xe);
        }
    }

Finally, a sample program making use of this handler, along with the program output:

public final class Main {
    
    public static final String ICALSTREAM =
            "BEGIN:VCALENDAR\\r\\nPRODID:-//Sun/Sample//EN\\r\\nVERSION:2.0\\r\\n" + 
            "BEGIN:VEVENT\\r\\nUID:1\\r\\nDTSTAMP:20070313T082041Z\\r\\nDTSTART;VALUE=DATE:20081212\\r\\n" +
            "SUMMARY:wrapped \\r\\n" +
            " summary\\r\\n" +
            "END:VEVENT\\r\\nEND:VCALENDAR";
    /\*\*
     \* @param args the command line arguments
     \*/
    public static void main(String[] args) throws Exception {
        
        Reader reader = new UnfoldingReader(new StringReader(ICALSTREAM));
        
        CalendarParser parser = CalendarParserFactory.getInstance().createParser();
        
        StringWriter writer = new StringWriter();
        XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
        XMLHandler handler = new XMLHandler(xmlWriter);
        xmlWriter.writeStartDocument();
        parser.parse(reader, handler);
        xmlWriter.writeEndDocument();
        xmlWriter.close();
        
        System.out.println("xml representation:" + writer.toString());
    }
}
 
java ical2xml.Main
xml representation:<?xml version="1.0" ?><vcalendar><prodid>-//Sun/Sample//EN</prodid>
<version>2.0</version>
<vevent><uid>1</uid>
<dtstamp>20070313T082041Z</dtstamp>
<dtstart value="DATE">20081212</dtstart>
<summary>wrapped summary</summary>
</vevent>
</vcalendar>

The generated XML is totally non standard (Calconnect is currently defining such a standard) and quite ugly but it shows how the parser can be used to do lightweight processing of an iCalendar stream without going through the intermediate step of creating a full ical4j Calendar object (which can be quite expensive in terms of CPU and memory).

The same technique could be used to generate a json output or to translate an iCalendar stream into a different object model.

About

arnaudq

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
Bookmarks