I've been going through Chandler, making various parts of its workings timezone-aware. Here are some of the issues I've run into. For some background, have a look at the
0.6 Chandler Timezone spec.
UI Issues
- The default timezone dropdown is too big: Mimi has suggested it move to a menu (under File, probably).
- In week view, if you're not running on a massively wide display, we'll clip the timezone when displaying text like "3:00 PM PST".
Localization of timezone names
PyICU currently has localizations for strings of the form "PST" or "Pacific Standard Time". If, as the UI spec says, we want to display timezones
as "US/Pacific", we will have to create localizable strings for these. (This is the approach taken by Apple iCal, for example).
Equivalent timezones
When running on Linux, I've noticed that my default timezone appears as "PDT", while it's (the technically more correct) "US/Pacific" on the Mac. It would be
good to map this correctly, so there aren't duplications in the timezone drop-downs.
"Floating" timezones
At one point, I had put in a "floating" entry into the detail view timezone dropdown. For the most part, this worked, although it was causing weird UI errors
when switching to/from floating in recurring events. So, for now, I've disabled this.
Unsafe datetime comparisons
Python's
datetime API doesn't allow you to compare na�ve and non-na�ve datetimes (it raises
a
TypeError if you do so). Note that "compare" here covers the usual builtin operators like
==,
<,
<=,
>,
but also builtin functions like
min,
max and
cmp.
Currently, all
datetime objects are na�ve in Chandler: This includes instances from imported events, as well as objects used internally (e.g. to figure out where to display events in the calendar, or when the event reminder dialog should be displayed). Once events with timezone information are present, then, Chandler has a dangerous mixture of na�ve and non-na�ve
datetimes, and we need a strategy for porting over code that does comparisons.
Let's say we're trying to compare
naif, a na�ve
datetime with
sophisticate,
a non-na�ve instance. There are a couple of possibilities (using
<= as an example):
-
naif <= sophisticate.replace(tzinfo=None)
-
naif.replace(tzinfo=PyICU.ICUtzinfo.getDefault()) <= sophisticate
It really depends on the situation as to which is correct. For instance, if the calendar UI is trying to determine whether or an event with a floating timezone overlaps
another (non-floating) event, 2. above is correct, since "floating" events are always interpreted as occurring in the user's current time zone. However, there are cases where the first comparison is what we want.
For now, for minimizing code changes (or making them more obvious), I've added API on the Calendar module that uses the regular Python comparison API if it's safe, and falls back to 1. otherwise. Where now you have
>>> naive <= sophisticate
you would use the API via:
>>> Calendar.datetimeOp(naive, '<=', sophisticate)
This is somewhat of a stopgap measure, partly because all these usages have to be reviewed to check whether #2. should be used instead.
An abundance of na�vet�
There are cases in our code where calculations are performed incorrectly when
datetimes have differing timezones. For example, the calendar UI assumes that you can figure out what day a given event starts on by using the
datetime
class's
toordinal() method.
Along similar lines, code that changes event information (e.g., editing of start and end times in the detail view, or drag-and-drop of events in the calendar UI) needed
to be changed to make sure that timezone information was preserved. In many cases, the old code would drop all time zone info from the newly created
datetime instances.
dateutil
JeffreyHarris pointed out that the python
dateutil library, our underlying implementation for recurrence, uses unsafe
datetime comparisons everywhere. As a result, we need to make sure that
datetimes accessible to a given
rruleset are either all naïve or all non-naïve. The current implementation attempts to do more than that by making sure that all
these
datetimes have the same
tzinfo; there may be cases that have been missed, though. (For example, this
would have to apply to any arguments to the
before or
after methods. Maybe subclassing
dateutil.rrule.rrule{,set}
would be a better way to deal with this problem than the current approach of tweaking all the call sites.)
PyICU DateFormat usage
There is somewhat of a mismatch between Python
datetimes and PyICU's
DateFormat
(or
MessageFormat). In ICU, times contain no timezone information (they're represented as a POSIX timestamp).
Timezone is owned by
DateFormat objects: Unless specified otherwise, Format objects are created with the user's current timezone. So, when
getting a displayable string from a
datetime with a non-default timezone, you have to call
setTimeZone() before
using any
DateFormat instances. (For
MessageFormat instances, the situation is more
complex because you have to do this for any date sub-formats).
Note:
DateFormat.parse() has similar issues.
Possibly we should make PyICU itself could deal with this (since it already has code to coerce
datetime objects to ICU's
UDate). This
would probably not be hard to change for
DateFormat, but
MessageFormat would be more difficult. In general, the ICU formatting API isn't very Pythonic (e.g.,
Formattable objects would probably not exist if ICU had originated in a dynamic language). Whether it's worth addressing this general concern in PyICU is open to question.
For the moment, I've worked around this in AttributeEditors by adding a
DateFormatter class that wraps a
PyICU.DateFormat instance, but
deals with changing the timezone when you call its
format() method. Similarly, it has a
parse() method that takes a
ICUtzinfo argument and returns a
datetime in the correct timezone.
--
GrantBaillie - 11 Aug 2005