Tuesday, May 04, 2010

When June 1 1900 is not June 1 1900

We're developing a fairly large application in Java. There are 2 front end applications, one written in Flex, the other in plain Spring-MVC and Spring-WebFlow. Both of them are using 3 main applications deployed as 3 different wars on the same application server. The front end applications talk with the back end applications using RMI exposed as HTTP. The back end applications also talk to each other using the same protocol.

One of the applications deployed in the back end is responsible for validating data entered in the front end applications. One of the rules in the, so called ValidationService, checks if the SSIN of a person is valid.
In Belgium, all people have a unique SSIN (Social Security Identification Number), comprised of 11 digits. The first 6 digits are based on the person's birth date. So if this person was born on April 21, 1978, the SSIN starts with 780421xxxyy. To check if an SSIN is valid, you need to check the person's birth date against these first 6 digits. In fact, you need to do a lot more to check if the SSIN is valid, but the issue I was having was related to the person's birth date.

For test purposes, one of the front end applications was running on a Windows machine, while the back end applications where running on a Linux machine (some version of Ubuntu). When entering 06/01/1900 and an SSIN starting with 000106, the ValidationService threw an error at me claiming the SSIN did not match the given birth date. While debugging the ValidationService, I saw the date that was entering the system was 05/31/1900 (23h50m), instead of 06/01/1900 (00h00m). How strange.

To be able to understand the above problem, we need to know how object serialization works in Java. Whenever objects are transfered over a wire, they are serialized. Each object knows how it can serialize and deserialize itself by implementing the private methods writeObject and readObject. The java.util.Date object simply converts itself to a long value while serializing. This long value represents the number of milliseconds since January 1, 1970, 00:00:00 GMT.
For some reason, I don't know why, this long value on Linux is different from the long value on Windows. I can prove this using a simple test. Write a simple test class:

public class Test {
public static void main(java.lang.String[] args) {

System.out.println(new java.util.Date(0,5,1));

System.out.println(new java.util.Date(0,5,1).getTime());

}
}

Now, compile it and run it on Windows, this should be the output (Java 6):

Fri Jun 01 00:00:00 CET 1900
-2195942961000

Do the same on Linux, and this should be the output (Java 6):

Fri Jun 01 00:00:00 CET 1900
-2195942400000


Interesting, isn't it. I wonder if there are other platforms affected as well. I mean, what's the output of the simple class on Mac OS X, BSD, AIX, Solaris, ... . IMHO they should all render the same long value, and I have a sneaking suspicion which platform had an F in mathematics :)

The strange thing is, not all dates are affected. I was doing a batch upload of 600.000+ records, all with different birth dates and SSINs and this was the only one throwing the aforementioned error.

Anyway, the above should be kept in mind when deploying different services talking with each other over RMI, on different platforms. I suspect this is a bug, but who cares. The Date and Calendar stuff in Java is a true nightmare and we all should use joda time instead.

2 comments:

Steven Willems said...

The output on Mac OS X 10.6.3:

* Java 1.5:

os.name: Mac OS X
os.version: 10.6.3
java.runtime.version: 1.5.0_19-b02-304
java.specification.name: Java Platform API Specification
java.specification.vendor: Sun Microsystems Inc.
java.specification.version: 1.5
java.vendor: Apple Inc.
java.vendor.url: http://www.apple.com/
java.vendor.url.bug: http://bugreport.apple.com/
java.version: 1.5.0_19
java.vm.info: mixed mode
java.vm.name: Java HotSpot(TM) Client VM
java.vm.specification.name: Java Virtual Machine Specification
java.vm.specification.vendor: Sun Microsystems Inc.
java.vm.specification.version: 1.0
java.vm.vendor: Apple Inc.
java.vm.version: 1.5.0_19-137

Fri Jun 01 00:00:00 CET 1900
-2195942400000


* Java 1.6

os.name: Mac OS X
os.version: 10.6.3
java.runtime.version: 1.6.0_17-b04-248-10M3025
java.specification.name: Java Platform API Specification
java.specification.vendor: Sun Microsystems Inc.
java.specification.version: 1.6
java.vendor: Apple Inc.
java.vendor.url: http://www.apple.com/
java.vendor.url.bug: http://bugreport.apple.com/
java.version: 1.6.0_17
java.vm.info: mixed mode
java.vm.name: Java HotSpot(TM) 64-Bit Server VM
java.vm.specification.name: Java Virtual Machine Specification
java.vm.specification.vendor: Sun Microsystems Inc.
java.vm.specification.version: 1.0
java.vm.vendor: Apple Inc.
java.vm.version: 14.3-b01-101

Fri Jun 01 00:00:00 CET 1900
-2195942400000

kennywest said...

So the output is the same as in Linux. Clearly a Windows problem I guess :/