Cold Fusion and Flash Remoting – Dates changing based on timezone (undesired)

Problem:
I had just finished this flex application where users are editing date/time sensitive data that is all specific to a specific timezone (Mountain Time). Turns out they have users in multiple timezones who all intend to edit this data. When users not on Mountain Time access the data, the dates/times are all adjusted from Mountain Time to their time zone (arrgghh!).

Example: ColdFusion server is on Mountain Time. DB value = 12:00:00 PM. User in Pacific Time sees 11:00:00 AM on the client flex application. User Adds new value of 1 PM (thinking Mountain Time), value is changed to 2 PM during remoting.

When just dealing with date objects with a time of 00:00:00 (or 12:00:00 AM) this can cause the date to change to the previous day because the time rolls back to 11 PM the previous day. Oh the joy!

Bottom line: Dates will be converted/offset for the user’s timezone in both directions of Flash Remoting!

This would be great if you’re building a calendar system for users in multiple timezones, but isn’t always desirable. One would think that there would be a flash param, or server setting in ColdFusion that could suppress this behavior. Nope!

Some would say (and did) that I should just make all the objects strings. This app is centered around dates and times, so that didn’t sound good, nor did it sound fun to re-factor all the code so loop over all the records and cast them as dates. Why using types if you have to do that?

So here’s how I solved it:
1. First, I changed my “fill”-type methods in ColdFusion so that the dates in my value/transfer objects were first converted to UTC using dateConvert(“local2UTC”, myDate). This ensured that the dates being sent to the client Flex app were in UTC format (important!). I also changed my create and update methods to do the reverse, converting the dates back to local using dateConvert(“utc2Local”,myNewDate) before committing to the DB.

2. Next, I created a method on the ColdFusion server-side that would provide the client flex application with the UTC offset by using ColdFusion’s getTimezoneInfo() method. This ColdFusion function creates a struct containing time zone info, which I in turn, send to te client. On te client, I persist the utcHourOffset to the Model instance (I am using cairngorm btw). At the same time, I also set a variables in the client apps’ Model instance for the client’s timezone offset and finally a method for returning the difference between the client and server’s offset.


// both values stored as hours, but could be anything you want them to be, so
// long as you adjust your related logic appropriately

public var serverUTCOffset:Number = 0; //provided by server via remoting call

public var clientUTCOffset:Number = new Date().getTimezoneOffset()/60;

// returns the difference in hours between the
// client's timezone offset and the server's timezone
public function get clientToServerOffset() : Number {
return clientUTCOffset-serverUTCOffset;
}

3. Here’s where it get’s a little tricky… No matter what you do on the server-side, the dates will still adjust for the timezone of the client during the remoting process. So the dates/times coming across the wire are now adjusted for BOTH the timezone offset AND UTC. That means you need to account for the adjustment made by remoting, then the UTC format. This has to be done when reading data FROM the server and sending data TO the server.

Inside of my value object, I needed to do some adjusting, changing what previously public variables to public functions getters/setters.

***Note: In the example below, BindableDate is a custom class that exposes each date part to data binding (something Date doesn’t do naturally). They could just as easily be Date instances.

/** Transient tag keeps this var from going across the wire or persisting */
[Transient] public var bindBreakStart:BindableDate = new BindableDate(new Date( 0,0,0,0,0,0,0) );

/** program break start getter/setter - adjusts for UTC **/
public function set breakStart( value:Date ) : void {
var utcValue:Date = new Date( (value.valueOf() + ( _model.clientToServerOffset*3600000 )) - _model.serverUTCOffset*3600000 );
bindbreakStart = new BindableDate( utcValue );
dispatchEvent( new Event ( "breakStartChanged" ) );
}

[Bindable ( "breakStartChanged" ) ]
public function get breakStart () : Date {
return new Date( (bindBreakStart.data.valueOf() - ( _model.clientToServerOffset*3600000 )) + _model.serverUTCOffset*3600000 );
}

// I could have just added the *3600000 to the clientToServerOfffset and serverUTCOffset
// variables in the model, but didn't.

private var _model:Model = Model.instance;

What the above does is accounts first for the offset applied during remoting, then adjusts for UTC.

ValueOf() in flash/flex is milliseconds since midnight January 1, 1970, universal time, so we add (when setting) or subtract (when getting) the ms of the difference between the client and server’s UTC offset. This gets us back to a UTC time that would be the same as UTC time on our application server. From there, we adjust for UTC by adding/subtracting the server’s UTC offset. This ensures that the data will always be identical to the server.

I am sure this could be refactored into something prettier. Maybe I’ll do so eventually, but for now, it fixes the problem.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: