Apr 23, 2008

Using the Calendar class in J2ME for date and time

Parsing and displaying dates and times is often complicated because of formatting and locale issues. Java 2 Platform, Standard Edition (J2SE) provides several classes to simplify date and time handling -- classes such as java.util.Calendar, java.util.Date, java.util.TimeZone, and java.text.DateFormat. By comparison, the Mobile Information Device Profile (MIDP) defines only subsets of the Calendar, Date and TimeZone classes, and does not include any form of DateFormat. How, then, can your MIDP applications properly handle dates and times?

The answer lies in the javax.microedition.lcdui.DateField class, part of the MIDP high-level user interface API. DateField is an interactive user interface component that displays a date, time, or both. It also allows you to edit the date and time. DateField extends the Item class. This means that DateField components can be placed on Form objects. So the first step in using a DateField is to create a Form and place the DateField on the form:

    Form f = new Form( "A Form" );
f.append( df );

As with any Item, the DateField is displayed only when the form is made active by calling Display.setCurrent.

The DateField class defines two constructors:

    public DateField( String label, int mode );
public DateField( String label, int mode,
java.util.TimeZone zone );

To properly display dates and times, a DateField instance needs to know which time zone to use. The two-argument constructor uses the device's default time zone. The three-argument constructor lets you specify an explicit time zone if the default is inappropriate. Note that you can't change the displayed time zone without creating a new instance of DateField.

The first two arguments are identical in both constructors. The first argument is the label to display alongside the field -- use null if there is no label. The second argument is the input mode of the field. There are three possible modes, these are declared as constants in the DateField class:

    public static final int DATE = 1;
public static final int TIME = 2;
public static final int DATE_TIME = 3;

The input mode controls what the field displays: a date only, a time only, or a combined date and a time. You can change the input mode at any time by calling the setInputMode method.

When you create a new DateField instance, you do not have to set a date or time. The following code, for example, displays an uninitialized date:

    Display display = ....; // initialized elsewhere
Form f = new Form( "An Empty Date" );
DateField df = new DateField( "Date:",
DateField.DATE );
f.append( df );
display.setCurrent( f );

To initialize the field to a particular date or time, call setDate and pass in a java.util.Date object initialized to the correct value:

    Calendar c = Calendar.getInstance();
c.set( Calendar.MONTH, Calendar.OCTOBER );
c.set( Calendar.DAY_OF_MONTH, 18 );
c.set( Calendar.YEAR, 1996 );
c.set( Calendar.HOUR_OF_DAY, 16 );
c.set( Calendar.MINUTE, 39 );
c.set( Calendar.SECOND, 45 );
c.set( Calendar.MILLISECOND, 0 );

Date moment = c.getTime();
DateField df = new DateField( null,
DateField.DATE_TIME );
df.setTime( moment );

A Date object represents a moment in time (in coordinated universal time, or UTC, to be exact) as the number of milliseconds since midnight, January 1, 1970. Use a Calendar instance to create a Date instance, as shown above.

Note that a DateField in TIME input mode requires the date portion to be set to January 1, 1970. Two useful routines for clearing out the date portion of a Date and for combining two Date objects into a single object are as follows:

    // Return a Date with the time intact but the date
// set to January 1, 1970

public static Date clearDate( Date d ){
Calendar c = Calendar.getInstance();
c.setTime( d );
c.set( Calendar.MONTH, Calendar.JANUARY );
c.set( Calendar.DAY_OF_MONTH, 1 );
c.set( Calendar.YEAR, 1970 );
return c.getTime();
}

// Combine a date and time into a single
// Date instance

public static Date combineDateTime(
Date date, Date time ){
Calendar cd = Calendar.getInstance();
Calendar ct = Calendar.getInstance();

cd.setTime( date );
ct.setTime( time );

ct.set( Calendar.MONTH,
cd.get( Calendar.MONTH ) );
ct.set( Calendar.DAY_OF_MONTH,
cd.get( Calendar.DAY_OF_MONTH ) );
ct.set( Calendar.YEAR,
cd.get( Calendar.YEAR ) );

return ct.getTime();
}

Always do your date manipulation using the Calendar class, not using the raw milliseconds value stored in a Date object.

After a DateField is displayed, the system will allow the user to select the object and edit the date, time or both, depending on the input mode. Whenever you need to obtain the new date/time, call the getDate method:

DateField df = ....; Date editedDate = df.getDate();

Here is a simple MIDlet that lets you view and edit dates and times using all three input modes.

import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

/**
* Demonstration of time/date editing using the MIDP
* DateField class.
*/

public class DateFieldTest extends MIDlet {

private Display display;

// Define our Command objects

private Command exitCommand =
new Command( "Exit", Command.EXIT, 1 );
private Command okCommand =
new Command( "OK", Command.OK, 1 );
private Command cancelCommand =
new Command(
"Cancel", Command.CANCEL, 1 );

public DateFieldTest(){
}

protected void destroyApp( boolean unconditional )
throws MIDletStateChangeException {
exitMIDlet();
}

protected void pauseApp(){
}

protected void startApp()
throws MIDletStateChangeException {
if( display == null ){ // first time called...
initMIDlet();
}
}

private void initMIDlet(){
display = Display.getDisplay( this );
testList = new TestList();
display.setCurrent( testList );
}

public void exitMIDlet(){
notifyDestroyed();
}

// Return a Date with the time intact but the date
// set to January 1, 1970

public static Date clearDate( Date d ){
Calendar c = Calendar.getInstance();
c.setTime( d );
c.set( Calendar.MONTH, Calendar.JANUARY );
c.set( Calendar.DAY_OF_MONTH, 1 );
c.set( Calendar.YEAR, 1970 );
return c.getTime();
}

// Combine a date and time into a single
// Date instance

public static Date combineDateTime( Date date,
Date time ){
Calendar cd = Calendar.getInstance();
Calendar ct = Calendar.getInstance();

cd.setTime( date );
ct.setTime( time );

ct.set( Calendar.MONTH,
cd.get( Calendar.MONTH ) );
ct.set( Calendar.DAY_OF_MONTH,
cd.get( Calendar.DAY_OF_MONTH ) );
ct.set( Calendar.YEAR,
cd.get( Calendar.YEAR ) );

return ct.getTime();
}

// The list of tests we can perform, arranged
// in threes so that ( index % 3 ) == one
// of DATE, TIME or DATE_TIME

static final String[] testLabels = {
"Current date",
"Current time",
"Current date/time",
"Edit date",
"Edit time",
"Edit date/time",
};

private TestList testList;
private Date editDate;

//
// Displays the list of actions
//

class TestList extends List
implements CommandListener {
public TestList(){
super( "DateField Tests", IMPLICIT,
testLabels, null );
addCommand( exitCommand );
setCommandListener( this );
}

public void commandAction( Command c,
Displayable d ){
if( c == exitCommand ){
exitMIDlet();
} else if( c == List.SELECT_COMMAND ){

// Figure out which date to display
// and what the input mode is

int which = getSelectedIndex();
String label = getString( which );
int mode = ( which % 3 ) + 1;
boolean save = ( which > 2 );

display.setCurrent(
new Edit( save, label, mode ) );
}
}
}

//
// Edit a date, time or date/time, optionally
// saving the value.
//

class Edit extends Form
implements CommandListener {

public Edit( boolean save, String label,
int mode ){

super( label );
this.save = save;

Date d = editDate;

if( !save ){
d = new Date();
}

dateField = new DateField( null, mode );
append( dateField );

if( d != null ){
if( mode == DateField.TIME ){
d = clearDate( d );
}

dateField.setDate( d );
}

addCommand( okCommand );

if( save ){
addCommand( cancelCommand );
}

setCommandListener( this );
}

public void commandAction( Command c,
Displayable d ){
Alert alert = null;
Date date = dateField.getDate();

if(
save && date != null && c == okCommand ){
if( editDate != null ){
int mode = dateField.getInputMode();
if( mode == DateField.DATE ){
editDate = combineDateTime(
date, editDate );
} else if(
mode == DateField.TIME ){
editDate = combineDateTime(
editDate, date );
} else {
editDate = date;
}
} else {
editDate = date;
}

Calendar cal = Calendar.getInstance();
cal.setTime( editDate );

alert = new Alert( "New date/time" );
alert.setString(
"The saved date/time is now " + cal );
alert.setTimeout( Alert.FOREVER );
}

if( alert != null ){
display.setCurrent( alert, testList );
} else {
display.setCurrent( testList );
}
}

private DateField dateField;
private boolean save;
}
}

No comments:

Down with the Dictatorship!

    "Let them hate me, so that they fear me" - Caligula 41AD