updated demo here. you can download the source from riaforge.
i've put up an alpha version here. it's a flex 3.x "mashup" using google maps as the UI plus geonames to supply the tz info once a user clicks on the map.
some things to note:
- it really is alpha quality, error handling, etc. isn't very pretty
- the normal double click to zoom in google maps behavior doesn't work as my single mouse click listener seems to be swallowing all the double clicks, so use the zoom control instead
- it's really too big to be of much practical use, ie a much smaller widget would be better for use on a page (i am as they say "design challenged")
once i iron out all the kinks i'll post the source in the usual places.
update: seems there's an issue w/google maps API where it also fires off a single mouse click event for any double click events, so you can't easily tell the two events apart. workaround was to add shift-key to the mouse click to get tz info for single mouse click.
update: there's some kind of issue w/an iphone app doing a DDOS on the geonames web services, please change your code to use ws5.geonames.org instead of ws.geonames.org.
you can download the source, such as it is, from riaforge.
the Royal Thai Navy's Hydrographic department is in charge of providing a national time server & it looks like they got windows covered at least (yes they devoted more than 1/2 of that page to windows 95/windows ME, no Mac, no Linux--not sure what stats they're running off).
in any case, if you're in Thailand, point your time server at:
time.navy.mi.th
and bob's your uncle. i like the idea of having a "local" time server so we swapped tout de suite. works plenty fine. the navy, as usual, did a bang up job.
how on earth do you think you can coordinate a global project by not giving folks useful info? geez.
i've been hitting the download link throughout the day, thinking maybe the mozilla folks were all east US coasters (really no idea, just a WAG) & i'd see something around noon here in bangkok. nope. nothing. butkis. just version 2.0.0.14.
oh well. in case anybody's missed the link, go here: http://www.spreadfirefox.com/.
everybody from cisco to ibm's doing another tz two-step (you should see the list of s/w and h/w that has to be patched). there will probably have to be a new tz definition as the old UTC-4 tz was shared by la paz, bolivia which isn't changing their tz.
if you support venezuelan users/sites better get cracking:
notice how i refrained from any comparson to woody allen's Bananas movie even though it's one of my favorites ;-)
don't you just love politicians ;-)
for more info:
hopefully ColdFusion 8 (aka scorpio) will upgrade it's JDK to a version that includes the new timezone data. if not--which i think is highly unlikely--or you can't upgrade in time you can do it yourself. for MX, upgrading to J2SE 1.4.2_11 or better will do the trick.
you can read more here. an interesting note from that page is that several countries follow US DST rules, something i had never considered before.
let me provide a bit of background first.
ColdFusion datetimes are, i think, coldfusion.runtime.OleDateTime. The actual datetime is stored as decimal day offset from the ColdFusion epoch (31-Dec-1899) which is similar to DB2's and Excel's epochs. you can prove this to yourself by doing something like:
which should return 38864.5102546 for a datetime of {ts '2006-05-27 12:14:46'}. btw this also hints at another way to examine day only differences between dates, ie. you can do int(now()) to ditch the time bits. there's something curious about ColdFusion datetime because you can also treat them as if they were core java Dates. for example
<cfoutput>#now.getTime()#</cfoutput>
returns the java epoch offset (milliseconds since 1-Jan-1970), 1148707294281 for a datetime value of {ts '2006-05-27 12:21:34'} in the server's timezone. in fact pretty much all of the core java Date class methods (though many are deprecated) will work on a ColdFusion datetime. you can use toString(), toLocaleString(), toGMTString(), setYear(), etc. though if do use these methods you'll have accomodate core java's Date "quirks" such as years being year-1900, months starting at zero (ie january is month zero in core java), as well as handle tricky data type issues (mainly for setTime() which is looking for a long).
now to the heart of the issue.
it's important to understand that ColdFusion references all datetime objects relative to the ColdFusion server's timezone. if you dump coldfusion.runtime.OleDateTime you'll see a field for TimezoneOffset (similarly core java's Date has a TimezoneOffset getter but no setter). something's gotta go there, might as well be the server's timezone data (i'm sure more thought went into this than that but you get my drift). all datetimes are server datetimes as far as ColdFusion is concerned.
if your application must handle multiple timezones, ideally you'd want to store your datetimes as UTC and convert this to whatever timezone you needed. in fact that's the whole purpose of the timezone CFC. all well and good but all datetimes are server datetimes as far as ColdFusion is concerned. while your intention is that all datetimes are UTC, your ColdFusion server doesn't see that and dependng on your server's timezone, strange stuff can happen. the clearest example is what happens to datetimes on the cusp of Daylight Savings Time (DST) on ColdFusion servers in timezones that use DST (like most of the US-based ColdFusion servers). a datetime of 2006-04-02 02:01:00.0 on those servers would never exist, it would automagically get flopped over to 2006-04-02 03:01:00.0 because ColdFusion doesn't see your UTC timezone only the server's timezone. so for an hour or so twice a year, your datetimes would be wrong by an hour. oops.
lets review some potential workarounds.
- by far the easiest solution to this problem is to simply set your server's timezone to UTC and bob's your uncle. no fuss, no muss. however i think this won't be a viable solution for many applications hosted on shared servers, so lets look at other workarounds
- a slicker solution would be for Adobe to provide a setTimeZone() function similar to setLocale() that would force the ColdFusion server to use the selected timezone for all it's datetime operations. if you agree with me on this by all means let the ColdFusion team know.
- a messier but certainly more portable and near term ready approach would be to use an epoch offset instead of a "real" datetime. two solutions that come to mind are to convert all your datetimes to java epoch offsets as described above or icu4j's universal time scale. right now this is the approach we're going to implement in the new version of the timezone CFC.
i'd like to thank wayne bianca for starting this research in the first place (though given what i've been doing for the last two weeks, i think ignorance is bliss) and spike for helping me stop overthinking the solution.
- com.ibm.icu.util.ZoneRule: an abstract class representing a tz transition rule. this class represents basic properties of zone rule such as raw UTC offset and DST offset and abstract methods to access onset information.
- com.ibm.icu.util.TimeListZoneRule: a concrete class extending ZoneRule. this class represents zone transition point(s) defined by UTC millis.
- com.ibm.icu.util.RecurrentZoneRule: a concrete class extending ZoneRule. this class represents recurrent zone transitions defined by a rule, such as first Sunday in April. the way to define recurrent rule is pretty similar to SimpleTimeZone.
- com.ibm.icu.util.RuleBasedTimeZone: a class extending TimeZone. this class aggregates one or more ZoneRule instances. using this class and ZoneRule instances, you can create a custom TimeZone which supports any historical zone transitions.
- com.ibm.icu.util.VTimeZone: a class extending TimeZone, wraps either RuleBasedTimeZone or OlsonTimeZone (default TimeZone implementation used by ICU4J). this class would have two constructor methods for creating a new VTimeZone instance from 1) TZID such as "America/New_York" and 2) RFC2445 VTIMEZONE component. this class also provides some method to write out underlying zone rules into VTIMEZONE format.
in addtion to the new classes mentioned above, he also proposes some modifications to existing classes:
- com.ibm.icu.util.TimeZone: an additional method - "List getZoneRules()", which returns a list of ZoneRule instances for the TimeZone. the implementation in TimeZone class just throws UnsupportedOperationException.
- com.ibm.icu.util.SimpleTimeZone / com.ibm.icu.impl.OlsonTimeZone: overrides "List getZoneRules()" to return actual ZoneRule instances for these TimeZone implementation.
the javadocs for the proposed changes have been (temporarily) put up here. if you want to participate in the discussion regarding these changes hop on over to the ICU sourceforge site and subscribe to the mailing list.
jitter bug references: 4577, 5012
to me these seem like some decent improvements and i know several folks in the ColdFusion community are interested in timezones, especially their rules.
default="#tzObj.getDefault().ID#"
icu4j:
default="#variables.timeZone.getDefault().getDisplayName()#"
the tz that the core java default method was returning wasn't understood by icu4j but it didn't throw an error but silently returned the UTC tz instead. whoops.
you can pick up the new version here.
icu4j on the other hand, has had this and other updated timezone info for some time now.
tz=createObject("java","com.ibm.icu.util.TimeZone");
//get TZ based on country
zones=tz.getAvailableIDs("TH");
</cfscript>
<cfdump var="#zones#">
how cool is that?
one kind of subtle gotcha i've run into was timezones (tz). i'd always assumed it worked like email, you could control the display datetimes on the client as they were stamped with enough info to figure out how to display the email datetime in the client's tz. nope. what you get is either the aggregator's tz or the aggregator's service provider tz or some kind of compromise tz if the service provider handles many tz. whether or not your device can assemble it into your tz is another story.
if you're lucky or smart enough to handle just the one national market using a national aggregator/telecom, you'd never notice this. it bites your ankles though when you "go inter" as they say here in Thailand. depending on where the client device and aggregator's servers are located, you could get the rather cool phenomena of SMS coming from the future (shades of the 4400). while i think it's pretty cool, users however have a rather different opinion. no amount of cajoling or threatening will make any difference, you're stuck with that tz. probably something you might want to keep in mind.
you can read a bit more here.
ps: the experimental DST bits are on hold now, s. isaac dealey was kind enough to review the logic and sent me a bunch of stuff to look over. hopefully i can get to it this weekend.
useDaylight=true,transitions=149,lastRule=java.util.SimpleTimeZone[id=Mexico/BajaNorte,
offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,
startMonth=3,startDay=1,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=2,
endMonth=9,endDay=-1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
the interesting bits are the "lastRule". you can see the startDay, startMonth (remembering that java months start at 0), etc. the trouble comes in deciphering the logic for the DST start/end date parts. endDay=-1?? what the heck? if you read the javadocs API for these classes, the logic is spelt out in the API but setting up cf's logic to handle this was getting messy. that's when i noticed startMode and endMode. these values seemed like a way to short circuit that messy logic, unfortunately, these aren't really documented in the APIs. to make a long story shorter, some googling and peeking inside source code i came up with these:
- DOM_MODE (1): Daylight savings time start/ends on the exact day of the month specified by day of month. For example, the fifth day of the month.
- DOW_IN_MONTH_MODE (2): Daylight savings time start/ends on the day of the week specified by dayofweek that occurs in the month as specified by day of month. For example, the last Sunday of the month.
- DOW_GE_DOM_MODE (3): The absolute value of dayofweek gives the day of the week occurring on or after the day of the month specified by day of month for which daylight savings time start/ends. For example, the first Sunday on or after the fifteenth of the month.
- DOW_LE_DOM_MODE (4): The absolute value of dayofweek gives the day of the week occurring on or before the day of the month specified by day of month for which daylight savings time start/ends. For example, the first Sunday on or before the fifteenth of the month.
in any case, i'd appreciate folks testing this and if possible maybe having a look at the DST start/end logic and letting me know of any problems.
thanks.
trying to fix that blunder led me into the bowels of cf where i had some trouble getting my head around the exact numeric units that cf uses for datetime objects. i was doing a javacast("long",arguments.thisDate) thinking i'd get milliseconds since java epoch to pass into java.util.TimeZone's getOffset method, ie assuming a cf datetime was a java.util.Date. but that snippet actually returns 38399 for today (16-feb-2005). which certainly doesn't look like the number of milliseconds since 1-jan-1970. recalling i blogged something about binary time scales, i whipped out a calculator to see when cf's "epoch" actually started. 38399 turns out to be the number of days since 31-dec-1899. that makes the cf datetime object type (which is actually a coldfusion.runtime.OleDateTime) more like "Excel_Time" or "DB2_Time" which measure dates in days since their epoch started (31-dec-1899).
well the timezone CFC's fixed up now. and i learned a bit more about cf (and not to assume too much about how cf works).
in any case functions (castFromUTC/castToUTC/etc) that used code like this:
tZ=tzObj.getTimeZone(arguments.thisTZ);
// set gregorian calendar to selected timezone
gregorianObj.setTimeZone(tZ);
// set gregorian calendar to selected datetime
gregorianObj.setTime(arguments.thisDate);
// calculate offset
thisOffset=(gregorianObj.get(gregorianObj.DST_OFFSET)/1000) + (gregorianObj.get(gregorianObj.ZONE_OFFSET)/1000);
return dateAdd("s",thisOffset,thisDate);
were re-worked to use:
var timezone=tzObj.getTimeZone(arguments.thisTZ);
// get offset in hours, also considers DST var thisOffset=timezone.getOffset(javacast("long",arguments.thisDate))/3600000;
// send cast date back to caller
return dateAdd("h",thisOffset,arguments.thisDate);
and sped things up 2x-3x. you can see the new CFC in action here.

