Tuesday, June 26, 2012

Time and Date management in Java

Java is very well equipped to manage dates and times. Some utility classes will make your live much easier than if you had to do it from scratch. There are a couple of things to understand first, though.

This problem has several different aspects:

Astronomical aspects

  • For a given date and time in location A, what time is it in location B?
  • What is the UTC time, now?

Legal aspects

  • For a given UTC time, what legal time is it in location X?
  • What is the first week of the year in country A?
  • What is the first week of the year in country B?

Misc aspects

  • What is the difference between Time Zone and Time Offset?
The following Objects are used to manage time in Java:
  • java.util.Date
  • java.util.Calendar
  • java.text.SimpleDateFormat
  • java.util.TimeZone
The TimeZone can be used with
  • the format
  • the calendar
This is probably the most important thing to remember here:
When you set the TimeZone at the Calendar level, you are setting the date at a given TimeZone. For example, March 1st 2012 at 12:00 Pacific Time.
When you set the TimeZone at the Format level, you do so to display the date & time set previously as viewed from another time zone. For example you set the date as above (US West Coast), and you want to view it from the US East Coast. Notice that in that case, the date remains unchanged. Only the way to display it is modified.
Note:
We are explicitly setting the TimeZones here, for clarity. If not explicitly set, the default TimeZone is used.
  private final static SimpleDateFormat SDF = new SimpleDateFormat("EEE dd MMMM yyyy HH:mm:ss.SSS Z (z)");    
    ...
    Calendar cal = Calendar.getInstance();
    cal.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
    cal.set(Calendar.YEAR, 2012);
    cal.set(Calendar.MONTH, Calendar.MARCH);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal.set(Calendar.HOUR_OF_DAY, 12);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    SDF.setTimeZone(TimeZone.getTimeZone("America/New_York"));
    System.out.println("New York:     \t Date is " + SDF.format(cal.getTime()));    
    SDF.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
    System.out.println("San Francisco:\t Date is " + SDF.format(cal.getTime()));    
Output:
 New York:       Date is Thu 01 March 2012 15:00:00.000 -0500 (EST)
 San Francisco:  Date is Thu 01 March 2012 12:00:00.000 -0800 (PST)
More examples will be given below.

The default time zone is found from the TimeZone Object:
    TimeZone tz = TimeZone.getDefault();
    System.out.println("Default Time Zone:" + tz.getID() + ", " + tz.getDisplayName());
Output:
 Default Time Zone:America/Los_Angeles, Pacific Standard Time
The TimeZone object will also provide the list of all the valid time zones.
    String[] ids = TimeZone.getAvailableIDs();
    for (String s : ids)
      System.out.println(s);
Output:
 Etc/GMT+12
 Etc/GMT+11
 MIT
 Pacific/Apia
 Pacific/Midway
 Pacific/Niue
 Pacific/Pago_Pago
 Pacific/Samoa
 US/Samoa
 America/Adak
 America/Atka
 Etc/GMT+10
 HST
 Pacific/Fakaofo
 Pacific/Honolulu
 Pacific/Johnston
 Pacific/Rarotonga
 Pacific/Tahiti
 SystemV/HST10
 US/Aleutian
 ...
Notice in the list above the different kind of time zones:
  • Etc/GMT+12
  • PDT
  • America/Los_Angeles
  • Etc/GMT+12 refers to a fixed time offset, of 12 hours in that case
  • PDT refers to the time in use during the winter on the US West Coast
  • America/Los_Angeles refers to a location where the time in use can be PDT and PST. The time offset is different for those two times.
Let us illustrate this last point, we will display two dates, March first and June first:
  private final static SimpleDateFormat SDF = new SimpleDateFormat("EEE dd MMMM yyyy HH:mm:ss.SSS Z (z)");

  ...
    // March 1st 2012
    Calendar cal = GregorianCalendar.getInstance();
    SDF.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
    cal.set(Calendar.YEAR, 2012);
    cal.set(Calendar.MONTH, Calendar.MARCH);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    System.out.println("Pacific Coast:\t Date is " + SDF.format(cal.getTime()));
    
    // June 1st 2012
    cal.set(Calendar.YEAR, 2012);
    cal.set(Calendar.MONTH, Calendar.JUNE);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    System.out.println("Pacific Coast:\t Date is " + SDF.format(cal.getTime()));    
Output:
 Pacific Coast:  Date is Thu 01 March 2012 00:00:00.000 -0800 (PST)
 Pacific Coast:  Date is Fri 01 June 2012 00:00:00.000 -0700 (PDT)
Notice the Time in use, and the Time Offset. The Time Zone remains the same.
Some Time Zones use Daylight saving, other do not:
    cal.set(Calendar.YEAR, 2012);
    cal.set(Calendar.MONTH, Calendar.MARCH);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    SDF.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
    System.out.println("Pacific Coast:\t Date is " + SDF.format(cal.getTime()));
    SDF.setTimeZone(TimeZone.getTimeZone("Pacific/Honolulu"));
    System.out.println("Hawaii:\t Date is " + SDF.format(cal.getTime()));

    cal.set(Calendar.YEAR, 2012);
    cal.set(Calendar.MONTH, Calendar.JUNE);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    SDF.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
    System.out.println("Pacific Coast:\t Date is " + SDF.format(cal.getTime()));    
    SDF.setTimeZone(TimeZone.getTimeZone("Pacific/Honolulu"));
    System.out.println("Hawaii:\t Date is " + SDF.format(cal.getTime()));
Output:
 Pacific Coast:  Date is Thu 01 March 2012 00:00:00.000 -0800 (PST)
 Hawaii:         Date is Wed 29 February 2012 22:00:00.000 -1000 (HST)
 Pacific Coast:  Date is Fri 01 June 2012 00:00:00.000 -0700 (PDT)
 Hawaii:         Date is Thu 31 May 2012 21:00:00.000 -1000 (HST)
Hawaiian Standard Time remains the same, Pacific Time goes form Daylight to Standard.
The TimeZone can be set at the Format level, or at the Calendar level:
    Calendar cal = GregorianCalendar.getInstance();
    System.out.println("Cal:" + cal.getTime());
    System.out.println("Default Time zone:\t Date is " + SDF.format(cal.getTime()));
    // Set the time zone on the FORMAT to display the UTC time
    SDF.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
    System.out.println("Mask set to UTC:\t Date is " + SDF.format(cal.getTime()));
    
    // Set the TimeZone at the DATE level
    System.out.println("HOUR is     " + cal.get(Calendar.HOUR_OF_DAY) + ", before changing the Date's TZ");
    cal.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
    System.out.println("HOUR is now " + cal.get(Calendar.HOUR_OF_DAY));
    System.out.println("Date changed to UTC:\t Date is " + SDF.format(cal.getTime()));
    // The date remains the same.
    System.out.println("Cal:" + cal.getTime());
Output:
 Cal:Tue Jun 26 10:26:35 PDT 2012
 Default Time zone:    Date is Tue 26 June 2012 10:26:35.606 -0700 (PDT)
 Mask set to UTC:      Date is Tue 26 June 2012 17:26:35.606 +0000 (UTC)
 HOUR is     10, before changing the Date's TZ
 HOUR is now 17
 Date changed to UTC:  Date is Tue 26 June 2012 17:26:35.606 +0000 (UTC)
 Cal:Tue Jun 26 10:26:35 PDT 2012
Notice above that changing the Date's TimeZone automatically adjusts the offsets (the HOUR_OF_DAY in that case). Only the TimeZone is changed, not the actual original date, it still describes the same instant, the exact same point in time.

For a given UTC Time, what time is it in San Francisco, and in Hawai'i:
    Calendar cal = GregorianCalendar.getInstance();
    cal.setTimeZone(TimeZone.getTimeZone("Etc/UTC")); 
    cal.set(Calendar.YEAR, 2012);
    cal.set(Calendar.MONTH, Calendar.MARCH);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal.set(Calendar.HOUR_OF_DAY, 12);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    SDF.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
    System.out.println("UTC:\t Date is " + SDF.format(cal.getTime()));    
    SDF.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
    System.out.println("San Francisco:\t Date is " + SDF.format(cal.getTime()));    
    SDF.setTimeZone(TimeZone.getTimeZone("Pacific/Honolulu"));
    System.out.println("Hawaii:\t Date is " + SDF.format(cal.getTime()));    
In the code, we set the TimeZone at the Calendar level, meaning that we set the date at March 1st, 2012, at 12:00 UTC. Then we display it, viewed from "Etc/UTC", "America/Los_Angeles", and "Pacific/Honolulu".
 UTC:            Date is Thu 01 March 2012 12:00:00.000 +0000 (UTC)
 San Francisco:  Date is Thu 01 March 2012 04:00:00.000 -0800 (PST)
 Hawaii:         Date is Thu 01 March 2012 02:00:00.000 -1000 (HST)
Also, notice something important: some TimeZones do not have the same TimeOffsets all over the year. The Time Offset is automatically adjusted when you use a TimeZone subject to daylight saving.

You can manipulate the Calendar directly, without having to use a format. Let's assume you've set - like above - a Calendar at 12:00 UTC, on March 1st 2012. And you want to know the hour of the day on the US West Coast:
    cal.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
    System.out.println(cal.get(Calendar.YEAR) + "-" + 
                      (cal.get(Calendar.MONTH) + 1) + "-" +
                       cal.get(Calendar.DAY_OF_MONTH) + ", " + 
                       cal.get(Calendar.HOUR_OF_DAY) + " hour(s).);
Output:
 2012-3-1, 4 hour(s).
Notice that the months index begins with 0 (we had to add 1).

An aspect of the Country, the legal time and time zone

In some countries, the week begins on Sundays. In some others, it begins on Mondays.
Let us run the following code different ways:
    Calendar cal = new GregorianCalendar(2012, Calendar.JANUARY, 1);
    SimpleDateFormat sdf = new SimpleDateFormat("EEEE dd-MMMM-yyyy '\tis in week' w");    
    System.out.println(sdf.format(cal.getTime()));
The US way:
 Prompt> java -Duser.language=en -Duser.country=US sometests.FirstWeekTest
 Sunday 01-January-2012  is in week 1
The British way:
 Prompt> java -Duser.language=en -Duser.country=GB sometests.FirstWeekTest
 Sunday 01-January-2012  is in week 52
The French way:
 Prompt> java -Duser.language=fr -Duser.country=FR sometests.FirstWeekTest
 dimanche 01-janvier-2012  is in week 52
The Swedish way:
 Prompt> java -Duser.language=sv -Duser.country=SE sometests.FirstWeekTest 
 söndag 01-januari-2012  is in week 52
Sunday is the first day of the week in the US, but not in Europe. As a result, the number of the week is affected! Week #1 is the first one to begin in the year.
In Europe, January 1st 2012 is part of the last week of 2011. This last week's number is 53 (yes 53, this is not a typo) in the US, 52 in Europe.
This can be something to consider when making cross-continent appointments, like in "Let us meet on week 17".

Never underestimate date and time management, it can be a quite complex problem, to say the least.
At the end of the XIXth century, a clerk was affected to the synchronization of the clocks on the railroad network of Switzerland.
He was later awarded the Nobel Price of Physics in 1921.
His name was Albert Einstein...

Monday, June 25, 2012

NMEA Rebroadcasting

This post is more like a question...
This is always the same problem, most of the NMEA devices use a Serial Port, which is always accessed exclusively (i.e. by no more than one program at a given time).
The way around this exclusive access is to have the exclusive reader to rebroadcast the NMEA data using another channel, accessible from several other programs. This other channel can be HTTP, TCP, UDP... Those channels are machine, system and language agnostic. From Java to Java, RMI is also an interesting alternative.
SailMail reads the NMEA Stream to get the position of the boat, and thus is able to find the best SailMail station, provides the possibilty to rebroadcast the data it reads. Available channels are TCP, UDP, and even COM (rebroadcast on another serial port). It rebroadcasts the NMEA Sentence as it has been read, assuming - appropriately in my humble opinion - that if it is NMEA you are interested in, you know how to parse it.
There is also something else, which OpenCPN is aware of, this is GPSd.
It runs as a daemon - on the systems where daemons exist - and is only good for GPSs, not all the NMEA messages, because it's rebroadcasting the data after reworking them in another format, some proprietary classes over json (JavaScript Object Notation).
The GPSd protocol is not exactly documented, and the project leader does not return my emails...
In the NMEA Console, you can read any of those channels (Serial, TCP, UDP, RMI) and rebroadcast on HTTP, UDP, TCP and RMI (simultaneously if needed). I'm also working on a GPSd rebroadcast, running anywhere Java runs (no need for a daemon).
Anyway, both approaches have pros and cons (even if I'd rather rebroadcast the data as they come).
If anyone has any comment or idea, please speak up!
Thanks in advance.

Monday, May 21, 2012

From ugly to glossy

This is about showing how the exact same data can be displayed with the same original idea, yellow text on a red circle, from Java and Swing.

OK, that's the first shot. The circle is itchy, it's anything but attractive. Let's add some anti-alias to the picture:

The edge of the circle looks better, the way the font is rendered too. But a nicer font should help too

The flat background is not looking good. Let's add a gradient to it, going from red to dark red, from the top to one third of the diameter.

That's better, but quite not there yet... We would need some glossy effect. That one is interesting, and surprisingly simple. The dark red background is flat (no gradient), the glossy effect comes from the light gray oval drawn on top, that one has a gradient, from light gray to dark red, going vertically, from the top of the circle, to one third of the diameter.

A shadow in the background would bring some bevel to the picture. It is a RadialGradientPaint.

Now that looks better!
The java code is not specially complicated, and it can certainly be reused.
Here is an example of some code using this technique.
And by the way, forget about dark red... Black is much classier.

All the code demonstrated in this article is available here.

Thursday, May 03, 2012

Dynamic Java Compilation

In some Unit Test project, we had a need for a dynamic code generation, naturally followed by a compilation.
The goal was to assemble several Unit Tests into a single Test Suite. The natural path is to write a Test Suite class, compile it, and run it. The problem happens when you want to automate this kind of process... There is in Java 1.6 a class named javax.tools.JavaCompiler, that can help. The steps are the following ones:
  • Generate the java code, in a String (StringBuffer, byte array, CharSequence, what not)
  • Compile the code - into a class
  • Load the compiled code with the ClassLoader so it is available in the classpath
  • Run the generated class
The default behavior of the JavaCompiler is to write the compiled class on the disc, and this is not what we wanted, as the directory you run your code from might very well be a read-only directory. There is actually a way to keep the compiled class in memory, as illustrated by the attached file.
There is some frustrating restriction though. The JavaCompiler cannot be used with a custom ClassLoader. This means that a class generated by a dynamic compilation as described above cannot be used for any further dynamic compilation. For example, you generate ClassA. Then you generate ClassB, extending ClassA, generated and loaded. The compilation of ClassB throws a ClassNotFoundException about ClassA...
You cannot pass your own ClassLoader to the JavaCompiler, for reasons I will not discuss here.
That is something to keep in mind. If this issue remains, a workaround would be to generate JVM byte code, using libraries like ASM; it works very well, but this makes the code a bit more difficult to debug, and beside, Java and JVM byte code are two (very) different languages.

Tuesday, April 06, 2004

XML Schema's uniques keys and keyrefs

XML Schema provides several features that are kind of mimicking some of the ones you can find in a relational database, but with some differences.
We intend here to talk about the following XML Schema elements:
  • key and keyref
  • unique

Get the required softwares

You need an XML Parser supporting the latest XML Schema specifications. An IDE with an XML Schema Visual Editor would also help.

key and keyref

The XML Schema elements key and keyref provide a way to ensure that within a given XML document matching a schema using them, a reference cannot be made to a missing value. The XML Schema element key also preserves unicity of the value within the instance document. As a result, it is useless to use the unique XML Schema element if a key is already mentionned. This is kind of natural, it'd be hard to imagine that a keyref would refer to several keys with the same value... For clarity, we will assume in this document that we have associated the XML Schema URI with the xsd prefix. From an element or attribute of an instance document, to make a reference to another value from the same instance document, this value must have been described as an xsd:key in the associated schema. Then, and only then, xsd:keyref can be used in the XML Schema to establish the fact that the value in the instance document must refer to another specified as a key in the schema. Let us consider an XML Schema, and a corresponding instance documents:
<schema xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:tns="http://www.example.org"
        targetnamespace="http://www.example.org"
        elementformdefault="qualified">
  <element name="root">
    <complextype>
      <sequence minoccurs="1" maxoccurs="1">
        <element name="AAA" type="tns:myAAA"/>
        <element name="BBB" type="tns:myBBB"/>
      </sequence>
    </complextype>
    <xsd:key name="myId">
      <xsd:selector xpath="./tns:AAA/tns:a"/>
      <xsd:field xpath="@id"/>
    </xsd:key>
    <xsd:keyref name="myIdref" refer="tns:myId">
      <xsd:selector xpath="./tns:BBB/tns:b"/>
      <xsd:field xpath="@idref"/>
    </xsd:keyref>
  </element>
  <complextype name="myAAA">
    <sequence minoccurs="1">
      <element name="a" minoccurs="1" maxoccurs="unbounded">
        <complextype>
          <attribute name="id" type="xsd:NCName" use="required"/>
        </complextype>
      </element>
    </sequence>
  </complextype>
  <complextype name="myBBB">
    <sequence minoccurs="1">
      <element name="b" minoccurs="1" maxoccurs="unbounded">
        <complextype>
          <attribute name="idref" type="xsd:NCName" use="required"/>
        </complextype>
      </element>
    </sequence>
  </complextype>
</schema>
<ns:root xmlns:ns="http://www.example.org">
  <ns:aaa>
    <ns:a id="x"/>
    <ns:a id="y"/>
  </ns:aaa>
  <ns:bbb>
    <ns:b idref="x"/>
    <ns:b idref="y"/>
    <ns:b idref="y"/>
  </ns:bbb>
</ns:root>
<root xmlns="http://www.example.org">
  <aaa>
    <a id="x"/>
    <a id="y"/>
  </aaa>
  <bbb>
    <b idref="x"/>
    <b idref="y"/>
    <b idref="y"/>
  </bbb>
</root>
<root xmlns="http://www.example.org">
  <aaa>
    <a id="x"/>
    <a id="y"/>
  </aaa>
  <bbb>
    <b idref="x"/>
    <b idref="y"/>
    <b idref="z"/>
  </bbb>
</root>
The XML Schema on the left is specifying that a root has to contain exactly one element called AAA first, and exactly one element called BBB after.
Under the AAA, there will be at least one element called a, which has a mandatory attribute called id. Under the BBB, there will be at least one element called b, which has a mandatory attribute called idref. The value of the idref attribute must already exist in the id attribute of one a element under the AAA element we've mentioned above.
As a result, the document presented on the top right is valid, because the values found by following the XPath expressed as /root/BBB/b@idref can be also reached by following the /root/AAA/a@id XPath.
The document at the middle right is the same as the one at the top right, but with a default namespace. For the same reason, the document presented on the bottom right is invalid, because the z value of the third b@idref cannot be reached by following the /root/AAA/a@id XPath.
Important
It is important to notice that xsd:key and xsd:keyref are making use of the XPath syntax - and specification - to locate the approriate values. As per the XPath 1.0 specification, an XPath expression cannot use a default (implicit) namespace. That means that if "http://www.example.org" had been a default namespace in the XML Schema, i.e. declared with no prefix, the XPath expression to locate the key would have becomed
<xsd:key name="myId">
  <xsd:selector xpath="./AAA/a"/>
  <xsd:field xpath="@id"/>
</xsd:key>
and this XPath syntax would have lead to impossible-to-validate instance documents, as the key values could not have been located by using this XPath syntax. A validator considering this syntax as valid, and resolving the key values, would thus break the XPath 1.0 specification.

unique

XML Schema provide the possibility to preserve the unicity of a value, without having to make it a key. The key and unique elements preserve unicity of the values. key also allows a unique value to be refered to (with a keyref). Let us consider an XML Schema, and a corresponding instance documents:
<schema xsd="http://www.w3.org/2001/XMLSchema" 
        xmlns:tns="http://www.example.org"
        targetnamespace="http://www.example.org" 
        elementformdefault="qualified">
  <element name="root">
    <complextype>
      <sequence minoccurs="1" maxoccurs="1">
        <element name="AAA" type="tns:myAAA"/>
      </sequence>
    </complextype>
    <xsd:unique name="uniqueA">
      <xsd:selector xpath="./tns:AAA/tns:a"/>
      <xsd:field xpath="@id"/>
    </xsd:unique>
  </element>
  <complextype name="myAAA">
    <sequence minoccurs="1">
      <element name="a" minoccurs="1" maxoccurs="unbounded">
        <complextype>
          <attribute name="id" type="xsd:NCName" use="required"/>
        </complextype>
      </element>
    </sequence>
  </complextype>
</schema>
<root xmlns="http://www.example.org">
  <aaa>
    <a id="x"/>
    <a id="y"/>
    <a id="z"/>
  </aaa>
</root>
<root xmlns="http://www.example.org">
  <aaa>
    <a id="x"/>
    <a id="y"/>
    <a id="y"/>
  </aaa>
</root>
The document at the bottom right breaks the unicity rule...

Using Java to validate such constraints

import java.io.InputStream;
import java.net.URL;
import oracle.xml.parser.schema.XMLSchema;
import oracle.xml.parser.schema.XSDBuilder;
import oracle.xml.parser.v2.XMLDocument;
import oracle.xml.parser.v2.XMLParser;
import oracle.xml.parser.v2.DOMParser;

public class Validator {
  private final static String SCHEMA_LOCATION  = "key.xsd";
  private final static String VALID_DOCUMENT   = "valid.xml";
  private final static String INVALID_DOCUMENT = "invalid.xml";

  public Validator() {
    try {
      URL validatorStream      = this.getClass().getResource(SCHEMA_LOCATION);
      URL docToValidate        = this.getClass().getResource(VALID_DOCUMENT);
      URL docToInValidate      = this.getClass().getResource(INVALID_DOCUMENT);
      DOMParser parser = new DOMParser();
      parser.showWarnings(true);
      String version = DOMParser.getReleaseVersion();
      System.out.println("Using " + version);
      parser.setErrorStream(System.out);
      parser.setValidationMode(XMLParser.SCHEMA_VALIDATION);
      parser.setPreserveWhitespace(true);
      XSDBuilder xsdBuilder = new XSDBuilder();
      InputStream is = validatorStream.openStream();
      XMLSchema xmlSchema = (XMLSchema)xsdBuilder.build(is, null);
      parser.setXMLSchema(xmlSchema);
      URL doc = docToValidate;
//    URL doc = docToInValidate;
      parser.parse(doc);
      XMLDocument valid = parser.getDocument();
      System.out.println("Valid");
    } catch (Exception ex) {
      System.out.println("Invalid...");
      ex.printStackTrace();
    }
  }

  public static void main(String[] args) {
    Validator validator = new Validator();
  }
}