Sunday, January 14, 2007

Not Exactly Masters of Time and Space...

Code example and downloadable project corrected on 2007/01/16.

Previous correction note corrected on January 28, 2007. That is January. And the editors of the last correction have been sacked. And their moose also.


We are coming up on a change in the rules of Daylight Saving Time (DST) in the United States. The US government has written quite a bit about it. Sun has updated the JVM to deal with it. Apple has even updated its JVM to deal with it. But Apple has not yet updated WebObjects.

Even if an update to WebObjects is shipped before the DST change, one can still have a problem right now. If an application stores a date or time anywhere between March 11, 2007 and April 1, 2007, any arithmetic on that date might have a problem.

Let us try an example. Suppose that it is 7 pm on January 13, 2007 and I request an alarm for two months from now. Then I ask the application when that alarm will occur. The application says that the alarm will occur at March 13, 2007 at 7 pm. The application would be wrong.

One can fix the javafoundation.jar file, which I will describe below. But it is possible to avoid many problems of this type. I am going to write more about this later, perhaps much more, but a short answer is this. First, a date and time without a time zone means nothing. If your application lets users put in dates and times without letting them specify the time zone, you are asking for trouble. Second, always store dates and times in UTC. Period. Simply put, databases should never know about any time zone except UTC, and a user should never have to guess what the assumed time zone is for a particular date and time string.

If I followed these rules, I would be able to figure out the problem. I created the alarm at 7 pm on January 13, 2007, PST (Pacific Standard Time). Then I ask the application when that alarm will occur. The application says that the alarm will occur at 7 pm on March 13, 2007, PST. The application would still be wrong. But knowing that March 13 is after the DST change, I could figure out that 7 pm in PST is equal to 8 pm in PDT, which stands for Pacific Daylight Time. It may still be confusing, but if one displays the time zones, one does have enough information to figure out what has happened.

If you end up having a problem from the DST change, your problem may not look like this. But these kinds of application problems are confusing. Something that depends on date calculations may break in subtle ways. You may understand what is happening when you see it. But imagine explaining this to your manager, or your manager's manager. I would not want this job.

Enough talk. Here is some code, in the form of a jUnit test. The project that contains this code can be found here.

public void test2006SpringForward() {
NSTimestampFormatter formatter = new NSTimestampFormatter("%Y/%m/%d %H:%M:%S %Z");

String target1 = "2006/04/02 01:59:00 America/Los_Angeles";
String target2 = "2006/04/02 03:00:00 America/Los_Angeles";

// start at 1 minute before 2:00 AM on April 2, 2006 in the Pacific time zone
NSTimestamp ts1 = new NSTimestamp(2006, 4, 2, 9, 59, 0, TimeZone.getTimeZone("America/Los_Angeles"));

assertEquals("creating time", target1, formatter.format(ts1));

// add 1 minute to bring us to 12:59, a minute before midnight
NSTimestamp ts2 = ts1.timestampByAddingGregorianUnits(0, 0, 0, 0, 1, 0);

assertEquals("flipped over DST or not? ("+target2+") != ("+formatter.format(ts2)+")", target2, formatter.format(ts2));
}

public void test2007SpringForward() {
NSTimestampFormatter formatter = new NSTimestampFormatter("%Y/%m/%d %H:%M:%S %Z");

String target1 = "2007/03/11 01:59:00 America/Los_Angeles";
String target2 = "2007/03/11 03:00:00 America/Los_Angeles";

// start at 1 minute before 2:00 AM on March 11, 2007 in the Pacific time zone
NSTimestamp ts1 = new NSTimestamp(2007, 3, 11, 9, 59, 0, TimeZone.getTimeZone("America/Los_Angeles"));

assertEquals("creating time", target1, formatter.format(ts1));

// add 1 minute to bring us to 12:59, a minute before midnight
NSTimestamp ts2 = ts1.timestampByAddingGregorianUnits(0, 0, 0, 0, 1, 0);

assertEquals("flipped over DST or not? ("+target2+") != ("+formatter.format(ts2)+")", target2, formatter.format(ts2));
}


On my system, the first test passes and the second test fails. We have a problem.

% ant exec

exec:
[exec] .F.flipped over DST or not? (2007/03/11 03:00:00 America/Los_Angeles) != (2007/03/11 02:00:00 America/Los_Angeles) expected:<...3...> but was:<...2...>


It turns out that the fix is really very simple on the system I am using right now, which is running Mac OS X 10.4.8, with the latest versions of the JVM. I am tempted to try to describe the solution for Windows and Solaris, as well as Mac OS X, but I only have access to Mac OS X at the moment. On Mac OS X systems, the updates to the JVM that have already occurred have fixed the DST problem for most applications. But, for historical reasons, WebObjects uses its own version of the time zone information, and this has led us to the current problem.

I did put this in an ant build.xml file, though. Hopefully, this will make the use of it clearer to people not using Mac OS X. Here is the part of the build.xml that fixes the problem. You can change the properties below if needed on your system. You must run this with administrator privileges and manually invoke the fixFoundation target.

<property name="SLF" value="/System/Library/Frameworks" />
<property name="fRJ" value="framework/Resources/Java" />

<property name="zoneInfoDir" value="/usr/share/zoneinfo" />

<property name="jarfile" value="${SLF}/JavaFoundation.${fRJ}/javafoundation.jar" />

<target name="fixFoundation">
<tempfile property="temp" />
<mkdir dir="${temp}" />
<unjar src="${jarfile}" dest="${temp}" />
<delete quiet="true" file="${temp}/com/webobjects/foundation/TimeZoneInfo/zoneinfo.zip" />
<zip destfile="${temp}/com/webobjects/foundation/TimeZoneInfo/zoneinfo.zip" basedir="${zoneInfoDir}" excludes="*.tab,**/Riyadh87,**/Riyadh88,**/Riyadh89,**/\+VERSION" />
<move file="${jarfile}" tofile="${jarfile}_bak" />
<jar basedir="${temp}" destfile="${jarfile}" />
<delete quiet="true" dir="${temp}" />

</target>


Now, I can run the test above and see the following:

% ant exec

exec:
[exec] ..

BUILD SUCCESSFUL
Total time: 1 second
%


References:
http://www.energy.ca.gov/daylightsaving.html
http://developer.apple.com/documentation/webobjects/Reference/API/
http://www.faqs.org/docs/Linux-HOWTO/TimePrecision-HOWTO.html
http://ant.apache.org/manual/

Thursday, January 04, 2007

Quick and Useful Way to Edit Data from an EOModel

I created something useful. It did not take very long, but it is useful and I do not remember anyone mentioning this kind of thing in the past.

I have a Direct to Web app built that, when I give it an EOModel as a parameter, launches a fresh, new web interface that allows me to edit data in the entities of the model. Imagine if you could do a "Browse Data" in EOModeler and then edit the data. One cannot do that, but one can take just an EOModel and put data into the entities without creating a fresh project. I used to create a D2W app to administrate every EOModel I have in any app, so I find this useful. I no longer need those duplicate projects around.

Here is how you do this:

- Using Xcode, create a "Direct to Web Application" project named "d2Model". When it asks you to specify an EOModel, just specify some model. It does not matter which model.
- You may want to build the project at this point. There is no reason for this, but I usually do. I just always build a project first thing, after I create it.
- Delete the model from your project. Make sure that you delete the references and the files and do a build in the project for "Clean All Targets".
- Edit the project's Application class's constructor as so:


public Application() {
super();
System.out.println("Welcome to " + this.name() + "!");
String modelPath = System.getProperty("eomodel.full.path");
if (modelPath == null)
throw new java.lang.IllegalArgumentException("Please give me a \"eomodel.full.path\" to use as a model.");
try {
EOModel model = new EOModel(new java.net.URL("file://"+modelPath));
EOModelGroup.defaultGroup().addModel(model);
} catch (java.net.MalformedURLException mue) {
throw new java.lang.IllegalArgumentException("Could not find EOModel: "+modelPath);
}
}


- build the project
- launch Terminal
- If you have Xcode set up in the default manner, you can cd to the build directory, inside the project, then the Development directory inside that. If you have Xcode set up differently, you will know where the woa is already.
- I moved the woa to a useful location. For example, you can do:
mv d2Model.woa ~/d2Model.woa


To use the application, I then do the following:

- open Terminal
- type the following:
~/d2Model.woa/d2Model -Deomodel.full.path=/Users/ray/Projects/MyToDo/ToDo.eomodeld


This lets me edit the data in the "to do" app I have been playing with. When I am done editing, I log out, close the browser window, and do a ^C in the Terminal window.

There are lots of better things to do here.

For example, a while back I had a droplet for this. One would drop a model on it and, like magic, there it was.

But this is version 0.1.

On the "to do" list for this is:

1) put the project somewhere, where I decide to put things.
2) create a double-clickable java app that I can drop an EOModel on, as I used to do with the droplet.
3) shorten the session time-out and put a "System.exit(0)" somewhere convenient in the Session subclass, so that I do not have to ^C the application instance
4) turn the woa into a standalone-app, so that one can use it on a machine that does not a WO installation.
5) whatever else I think of later....