Jython: Timezone Manipulation and Meeting Invitations vs Outlook
As part of a project at work I’ve built some Jython code that builds iCalendar attachments to include meeting invitations for scheduled maintenance sessions. Jython is Python-in-Java which takes some getting used to but is damned handy when you’re working with JIRA. I will share a few anecdotes:
1) For doing date and time calculations, specifically to determine locale offset from UTC, you’re a lot happier calling the Java SimpleDateFormat stuff than you are dealing with Python. Python is a beautiful language but I burned a lot of time in an earlier version of this code figuring out how to convert between different time objects for manipulation and whatnot. This is not what you would expect from an intuitive, weakly-typed language, and it is interesting to find that the more obtuse, strongly-typed language handles time zones and it just fricking works.
Here is some sample code:
from java.text import SimpleDateFormat from com.atlassian.jira.timezone import TimeZoneManagerImpl tzm = TimeZoneManagerImpl(ComponentManager.getInstance().getJiraAuthenticationContext(), ComponentManager.getInstance().getUserPreferencesManager(), ComponentManager.getInstance().getApplicationProperties()) # df_utc = DateFormat UTC # df_assignee = DateFormat Assignee df_utc = SimpleDateFormat("EEE yyyy-MM-dd HH:mm ZZZZZ (zzz)") df_assignee = SimpleDateFormat("EEE yyyy-MM-dd HH:mm ZZZZZ (zzz)") tz = df_utc.getTimeZone() df_utc.setTimeZone(tz.getTimeZone("UTC")) df_assignee.setTimeZone(tzm.getTimeZoneforUser(assignee)) issue_dict['Start_Time_text'] = df_utc.format(start_time.getTime()) issue_dict['Start_Time_html'] = df_utc.format(start_time.getTime()) if df_utc != df_assignee: issue_dict['Start_Time_text'] += "\r\n " issue_dict['Start_Time_text'] += df_assignee.format(start_time.getTime()) issue_dict['Start_Time_html'] += "<br />" issue_dict['Start_Time_html'] += df_assignee.format(start_time.getTime()) # Get TimeZone of Assignee # Start Time in Assignee TimeZone
Since our team is global I set up our announcement emails to render the time in UTC, and, if it is different, in the time zone of the person leading the change. For example:
|Start Time:||Mon 2013-09-23 23:00 +0000 (UTC)
Mon 2013-09-23 16:00 -0700 (PDT)
- We have a team in London. I have not yet tested it but as I understand it, once they leave BST, their timezone is UTC. I am looking forward to seeing if this understanding is correct.
- As I understand it, I’m pulling the current time zone of the user, which changes when we enter and leave DST, which means that the local time will be dodgy when we send an announcement before the cutover for a time after the cut-over.
2) I was sending meeting invitations with the host set to the assignee of the maintenance event. This seemed reasonable to me, but when Mac Outlook saw that the host was set, it would not offer to add the event to the host’s calendar. After all, all meeting invitations come from Microsoft Outlook, right?! If I am the host it must already be on my calendar!!
I tried just not setting the host. This worked fine except now people would RSVP to the event and they would get an error stuck in their outboxes.
So . . . set the host to a bogus email address? My boss was like “just change the code to send two different invitations” which sounds easy enough for him but I know how creaky and fun to debug is my code. I came upon a better solution: I set the host address to
firstname.lastname@example.org. This way, Outlook is naive enough to believe the email address doesn’t match, but all our software which handles mail delivery knows the old ways of address extension . . . I can send one invitation, and have that much less messy code to maintain.
from icalendar import Calendar, Event, UTC, vText, vCalAddress # [ . . . ] event = Event() # [ . . . ] # THIS trick allows organizer to add event without breaking RSVP # decline functionality. (Outlook and its users suck.) organizer_a = assignee.getEmailAddress().split('@') organizer = vCalAddress('MAILTO:' + organizer_a+ '+calendar@' + organizer_a) organizer.params['CN'] = vText(assignee.getDisplayName() + ' (' + assignee.getName() + ')') event['organizer'] = organizer
You can get an idea of what fun it is to build iCalendar invitations, yes? The thing with the parentheses concatenation on the CN line is to follow our organization’s convention of rendering email addresses as “email@example.com (Full Name)”.
3) Okay, third anecdote. You see in my first code fragment that I’m building up text objects for HTML and plaintext. I feed them into templates and craft a beautiful mime multipart/alternative with HTML and nicely-formatted plaintext . . . however, if there’s a Calendar invite also attached then Microsoft Exchange blows all that away, mangles the HTML to RTF and back again to HTML, and then renders its own text version of the RTF. My effort to make a pretty text email for the users gets chewed up and spat out, and my HTML gets mangled up, too. (And, yes, I work with SysAdmins so some users actually do look at the plain text . . .) I hate you, Microsoft Exchange!