mangstadt / biweekly

biweekly is an iCalendar library written in Java.
BSD 2-Clause "Simplified" License
323 stars 44 forks source link

Make VTimezone Serializable #41

Closed bcmedeiros closed 8 years ago

bcmedeiros commented 8 years ago

I'm trying to use ICalWriter on a production host that has no access to internet. When I call icalWriter.getTimezoneInfo().setDefaultTimeZone(tz);, it indirectly uses TzUrlDotOrgGenerator, which need access to the internet and thus blocks my code.

One way I thought of fixing this is to obtain the VTimezone instance on a host with internet access, serialize it, and then de-serialize it on the "real" app code. The main problem is that VTimezone is not serializable :(

bcmedeiros commented 8 years ago

The code I'm running with this patch is the following:

    try (ICalWriter icalWriter = new ICalWriter(writer, ICalVersion.V2_0)) {
        TimeZone tz = TimeZone.getTimeZone("America/Sao_Paulo");
        VTimezone vtz;
        try (ObjectInputStream os = new ObjectInputStream(
            Resources.class.getResourceAsStream("mail/tz_America_Sao_Paulo.SVTimezone"))) {
            vtz = (VTimezone) os.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }
        icalWriter.getTimezoneInfo().assign(vtz, tz);
        icalWriter.getTimezoneInfo().setDefaultTimeZone(tz);

        icalWriter.write(ical);
        File file = File.createTempFile("rem", ".ics");
        Biweekly.write(ical).go(file);
        mail.addFileAttachment(file, "rem.ics");
    } catch (IOException e) {
        throw new MailException(e);
    }
mangstadt commented 8 years ago

Hi Bruno,

The tzurl.org website stores all of its timezone definitions in iCalendar files. You could download the ones that you need, and then copy them to the system that doesn't have internet.

Then, instead of using Java serialization, you can then parse the definitions from the files, like so:

ICalWriter writer = new ICalWriter(...);

TimeZone tz = TimeZone.getTimeZone("America/Sao_Paulo");
VTimezone vtz = parse(new File("America_Sao_Paulo.ics"));
writer.getTimezoneInfo().assign(vtz, tz);
writer.getTimezoneInfo().setDefaultTimeZone(tz);

ICalendar ical = ...
writer.write(ical);
writer.close();
public static VTimezone parse(File file) throws IOException {
    ICalReader reader = null;
    try {
        reader = new ICalReader(file);
        reader.readNext();
    } finally {
        if (reader != null) {
            reader.close();
        }
    }

    return reader.getTimezoneInfo().getComponents().iterator().next();
}
mangstadt commented 8 years ago

I'm closing this issue for now. Please let me know if you have any other questions.

bcmedeiros commented 8 years ago

It seems to work perfectly, thank you!

Sorry for my desperate pull request ;)

mangstadt commented 8 years ago

You're welcome. Note that the code to do this has changed in version 0.5.0, which was released today.

ICalWriter writer = new ICalWriter(...);

TimeZone tz = TimeZone.getTimeZone("America/Sao_Paulo");
VTimezone vtz = parse(new File("America_Sao_Paulo.ics"));
TimezoneAssignment assignment = new TimezoneAssignment(tz, vtz);
writer.setGlobalTimezone(assignment);

ICalendar ical = ...
writer.write(ical);
writer.close();
public static VTimezone parse(File file) throws IOException {
    ICalReader reader = null;
    ICalendar ical;
    try {
        reader = new ICalReader(file);
        ical = reader.readNext();
    } finally {
        if (reader != null) {
            reader.close();
        }
    }

    return ical.getTimezoneInfo().getComponents().iterator().next();
}