Date And Time Manipulation
Overview
Chapter 6 mentions that programmers need arrays (not to mention "a raise"). Dare we say that programmers need a date, too? Well, maybe, but only if there's time!
Functions that manipulate dates and times are among the most important in any application. These functions include retrieving the current date and time, and manipulations such as adding a duration to a date, subtracting a duration from a date, extracting parts of a date, and formatting a date or time. Both RPG and Java require this fundamental functionality, and both languages include built-in support for it.
Before RPG IV, RPG programmers had a special set of keywords to retrieve a date, day, month, or year. These are the UDATE, UDAY, UMONTH, and UYEAR keywords, respectively. In addition, to retrieve the time from the system, the TIME op-code was used with a variable in the result field. The specified field could be six digits long, indicating that only the time should be retrieved, or 12 digits long, indicating that both the date and time should be retrieved.
In V2R2 of RPG III, IBM introduced a new set of keywords: *DATE, *DAY, *MONTH, and *YEAR. These keywords offered four-digit years versus the two-digit years of their predecessors. Given a four-digit field, *YEAR and *DATE return the century portion of the date in addition to the year. For example, if the date is July 22, 1961, then *DATE returns 07221961, whereas UDATE returns 072261. The *DAY and *MONTH keywords are identical to UDAY and UMONTH and were added simply to provide consistency with their predeces sors. With this introduction of four-digit years to RPG, the TIME operation has also been modified to accept a field 14 digits long. This tells the compiler that you want the century portion of the date retrieved, as well as the year.
RPG IV Date and Time Enhancements
With RPG IV in V3R1, new data types were introduced, namely D, T, and Z (for date, time, and timestamp, respectively). These are consistent with the same data types added to the database DDS languages in V2R1.1 of the operating system.
With this new set of data types, the language also added support for op-codes to allow the RPG programmer to easily manipulate dates and times. The ADDDUR and SUBDUR operations allow you to add and subtract a specific duration from a given date or time. The EXTRCT op-code allows you to extract a portion of a date or time from a field and place the result in the result field. In addition, the TEST operation code validates the contents of a date, time, or timestamp data type field. Further, RPG IV adds support for many known international date, time, and timestamp formats through keywords on the D (Definition) and H (Header) specifications. For example, you can tell the compiler that you want to use the *DMY date format for day/month/year representation (dd/mm/yyyy), or you can specify you want to use the *USA format for month/day/year representation (mm/dd/yyyy). You will learn about this in more detail later in this chapter.
Java Date and Time Support
Java does not offer primitive types for dates and times, as you might have noticed in the discussion of primitive types in Chapter 5. Rather, it offers classes to represent dates and times. These classes are rich with methods to retrieve, compare, add, subtract, extract, format, and test both dates and times. Again, you will see here the power and elegance of classes and objects in their ability to encapsulate in one place all the algorithms and functions needed to fully support a particular concept—and the ability to easily reuse that well-tested code, forever, in all applications. It happens that the Java engineers designed and wrote these classes for you, but you should be thinking by now of other classes you could write to encapsulate other code you have found yourself writing and rewriting time and again.
Java's support for dates and times can be broken down into the areas shown in Table 8.1, which also shows the classes supplied to address those areas, and the comparable RPG functionality. Note that Java does not separate date and time into different classes, but rather encapsulates them into a single object and gives you the ability to set, extract, and manipulate just the parts you are interested in.
Date/Time Function |
Java Class |
RPG Function |
---|---|---|
Raw time in milliseconds |
currentTimeMillis method in System class in java.lang |
Not available |
A simple date and time object |
Date class in java.util |
*DATE and TIME |
Advanced date and time with support for manipulation, comparison, and extraction |
GregorianCalendar class in java.util |
D, T, and Z data types, plus ADDDUR, SUBDUR, TEST, and EXTRCT op-codes |
Date and time formatting |
SimpleDateFormat class in java.text |
DATFMT and TIMFMT keywords |
Multiple time-zone support |
TimeZone class in java.util |
Not available |
This chapter shows you some of the date and time capabilities of Java, which hint at the rich functionality it supports.
Raw Time
Every computer contains a built-in clock as part of its hardware, which (surprise) keeps track of time. This clock is used by the CPU (Central Processing Unit) for hardware purposes, as well as by the operating system to establish the current date and time when you turn on your machine. In fact, if you have ever wondered how the clock stays accurate while your machine's main power supply is off, it typically uses a battery. The clock, unlike the one you have hanging on the wall, keeps track of time in milliseconds. Such precision is necessary for computers, of course, while your kitchen clock can easily live without it (depending on the precision of your recipes).
What are these milliseconds based on? If you write a little program to read the clock from your hardware, you will end up with numbers such as 6874732864982. These are the total number of milliseconds that have elapsed since a universally recognized time and date in history, called the epoch. In the case of RPG, the base year, or epoch, is January 1, 1940. In the case of Java, the base year is January 1, 1970. The number returned means that the clock inside your computer is continually incrementing its count of milliseconds.
When your program asks for that value, the clock returns the number of milliseconds it has counted, in the case of Java, since January 1, 1970.
This base-line epoch date provides a standard from which computers or programming languages do their date and time manipulation. For example, the currentTimeMillis static method in the class System in package java.lang returns the number of milliseconds from the computer clock. An example of it is shown in Listing 8.1.
Listing 8.1: Retrieving the Current Time in Milliseconds, in Java
public class MyTimeMilliseconds { public static void main(String args[]) { long timeInMil; timeInMil = System.currentTimeMillis(); System.out.println("Time is: " + timeInMil); } }
This example calls the method currentTimeMillis in the Java-supplied class System to retrieve the number of milliseconds elapsed from the base date. Notice that this method returns a long primitive value containing the number of milliseconds. Is this useful? Well, sometimes it is, such as when you want to calculate the elapsed time for a particular operation. You can simply record this value at the beginning of the operation, and again at the end of the operation, and then take the difference to determine how long it took. You could divide the difference by 1,000 to get the number of seconds, if you wanted.
Here's an opportunity for your own little utility class! Assuming you might need to record elapsed time during the performance-tuning part of your development, why not design a little class that will do this? It will have two instance variables to record the start and end times, and two methods to set them. It will also supply a method to subsequently return the elapsed time in terms of milliseconds. It would also be nice to return the elapsed time in the form of a string in the format "hh hours, mm minutes, ss seconds, nn milliseconds." This will make it easy to write the elapsed time in a human-readable format. The ElapsedTime class for this is shown in Listing 8.2.
Listing 8.2: Listing 8.2: A Class for Recording Elapsed Time
public class ElapsedTime { private long startTime; private long endTime; public void setStartTime() { startTime = System.currentTimeMillis(); } public void setEndTime() { endTime = System.currentTimeMillis(); } public long getElapsedTime() { return (endTime - startTime); } public String toString() { long deltaMillis = getElapsedTime(); // deltaMillis = n + s*1000 + m*60*1000 + h*60*60*1000; long millis = (deltaMillis) %1000; long deltaSeconds = (deltaMillis) /1000; long deltaMinutes = deltaSeconds / 60; long deltaHours = deltaSeconds / (60 * 60); long hours = (int)(deltaMillis / (60 * 60 * 1000)); long minutes= (int)(deltaMinutes - (hours*60) ); long seconds= (int)(deltaSeconds - (hours*60*60) (minutes*60)); String result = "Elapsed time: " + hours + " hours, " + minutes + " minutes, " + seconds + " seconds, " + millis + " milliseconds"; return result; } } // end class ElapsedTime
This class is simple enough that we don't need to dissect it. However, note that it does not use static methods because you want to allow for multiple objects of this class simultaneously, each recording different times in their instance variables. The interesting method is the toString method, which is a standard method in Java. If you supply it, then the System.out.println method will call it when it is given an object of this class as a parameter. To use this class to record elapsed time, simply instantiate an object of it, then call setStartTime before the operation starts, and setEndTime after the operation ends. You can then call System.out.println, passing the object, to see the results printed to the console. Listing 8.3 shows an example.
Listing 8.3: Listing 8.3: Putting the ElapsedTime Class to Use
public class TestElapsedTime { public static void main(String args[]) { ElapsedTime timeRecorder = new ElapsedTime(); // how long to append 1,000 blanks to a String timeRecorder.setStartTime(); // start recording String bigString = " "; for (int idx=0; idx<10000; idx++) bigString += " "; // append a blank to the string timeRecorder.setEndTime(); // end recording System.out.println(timeRecorder); // how long to append 1,000 blanks to a StringBuffer timeRecorder.setStartTime(); // start recording StringBuffer bigStringBuffer = new StringBuffer(" "); for (int idx=0; idx<10000; idx++) bigStringBuffer.append(" "); // append a blank timeRecorder.setEndTime(); // end recording System.out.println(timeRecorder); } }
When we tested this class, we got the following:
Elapsed time: 0 hours, 0 minutes, 1 seconds, 963 milliseconds Elapsed time: 0 hours, 0 minutes, 0 seconds, 10 milliseconds
This proves that StringBuffer is much more efficient than String! You can also time multiple operations simultaneously, simply by instantiating as many instances of the class as you need. (This is the beauty of objects.) One enhancement you might want to make is to define a default constructor that calls setStartTime, so that users don't have to explicitly call it when they instantiate the object at the right time.
Get me a Date, and Give me the Time!
Getting the current time in terms of milliseconds from the epoch is nice, but certainly not a usual thing to do. Rather, the most pressing requirement is to get the current date and time from the system in a form that is meaningful—a form that lets you extract or display it in terms of the year, month, day, hour, minute, and/or second. This is what you have always done in RPG using the TIME op-code, as shown in Listing 8.4.
Listing 8.4: Retrieving the Current Date and Time in RPG with the TIME Op-code
C GETDATTME BEGSR C TIME DTTM 14 0 C ENDSR
As mentioned earlier, the TIME op-code retrieves the time as well as the date. This example creates a simple subroutine that retrieves the date and time and puts them in the field DTTM. Notice the length of the field. It has a length of 14 to indicate to the system that you need not only the time, but the date as well. This also tells the compiler and the system that you need the date to have a four-digit year. You can also specify a date, time, or timestamp field (D, T, or Z) in the result field. Of course, this subroutine would be better coded as a procedure!
Java supplies you with the Date class in package java.util, which retrieves the current date and time when instantiated. Listing 8.5 shows an example.
Listing 8.5: Retrieving the Current Date and Time in Java with the Date Class
import java.util.*; public class MyTime { public static void main(String args[]) { Date today = new Date(); System.out.println("Date and time is: " + today); } }
This example defines and instantiates an object of type Date named today. The object is used directly in the call to println, which results in something like this:
Date and time is: Wed Aug 08 21:44:52 EDT 2001
Note that the class must have its own toString method that returns a String with a default format. In fact, this format will be different depending on the time zone your computer is set for.
In RPG, once you retrieve the date and time fields, you "manipulate" them using the various supplied date- and time-specific op-codes. The same applies to Java applications, but instead of op-codes, you use supplied methods in either the Date class or (as you will see) the GregorianCalendar class, which is also in the java.util package.
The Date class is the basic class in Java for representing a date and time in terms of years, months, days, hours, minutes, seconds, and milliseconds. It was supplied in the original class version of Java, and it is still used when all you want is a simple date object, and you do not need to manipulate it. However, since JDK 1.0, this class has been deprecated, meaning it is no longer preferred when the intention is to manipulate or compare dates. Instead, a new class named GregorianCalendar specializes in date manipulations, like adding or subtracting durations. Internally, GregorianCalendar still uses a Date object to store the actual date, and supplies the method getTime (strangely) to get it. This is still needed because the Date object is still the preferred object to use for displaying dates with System.out.println, since it formats them nicely (a topic covered in more detail later). Thus, the preferred way to get the current date is to instantiate GregorianCalendar, and the easiest way to display that date is to use getTime to display the resulting Date object, as shown in Listing 8.6. This results in the same output as Listing 8.5.
Listing 8.6: Retrieving the Current Date and Time in Java with the GregorianCalendar Class
import java.util.*; // for GregorianCalendar class public class GetDate { public static void main(String args[]) { GregorianCalendar today = new GregorianCalendar(); System.out.println("Date and time is: " + today.getTime()); } }
You can also use other constructors in Date and GregorianCalendar to create specific date and times versus the current date and time. For example, Date has a constructor that takes a long value that represents a number of milliseconds from the epoch, such as System's method currentTimeMillis returns. Using this will create an object for the date and time that the millisecond value represents. The Date class also has a method getTime that returns the milliseconds from the epoch value this object represents. The GregorianCalendar class has constructors for specifying (as integer values) the year, month, and day, or the year, month, day, hour, minute, and second, as shown in Listing 8.7.
Listing 8.7: Using the GregorianCalendar Class to Represent Any Date
GregorianCalendar xmas01 = new GregorianCalendar(2001,11,25); GregorianCalendar xmas01noon = new GregorianCalendar(2001,11,25,12,0,0); System.out.println(xmas01.getTime()); System.out.println(xmas01noon.getTime());
Note that the month, and only the month, is specified as zero-based. The result of Listing
8.7 is this:
Tue Dec 25 00:00:00 EST 2001 Tue Dec 25 12:00:00 EST 2001
There are also constants defined in GregorianCalendar for the months, so you do not have to remember to zero-base them. The constants are the actual English names for the months, all in uppercase as per the standard for names of constants, for example, GregorianCalendar.DECEMBER. The JDK documentation does not actually show these constants, as they come from the parent class Calendar, which you have to look up to see all the handy constants defined there, such as the constants for days of the week. (This concept of parent and child classes is discussed in Chapter 9.)
While GregorianCalendar does not have a constructor that takes a Date object, it does have a setTime method that takes one and sets the internal date from it. There might be times when you have a Date object and wish to make it a GregorianCalendar object. This can be done using this method after instantiation, as in:
Date dateObject = new Date(); GregorianCalendar gcObject = new GregorianCalendar(); gcObject.setTime(dateObject);
One last and important thing to note about date objects is how to store them in a database. In the discussion of database access in Chapter 13, you will see that you can retrieve database date, time, and timestamp fields directly into objects of class type Date, Time, and Timestamp in package java.sql. These are all child classes of the Date class, so they can be used directly as though they were Date objects. This means you can turn one into a GregorianCalendar object, for example, by calling the setTime method. To go the other way, and create these objects from your GregorianCalendar objects (for the purpose of writing to the database), one option is to use the constructor in these that takes a long value representing the milliseconds from the epoch. You can get this value from a GregorianCalendar object by calling its getTimeInMillis method, like this:
GregorianCalendar gc = new GregorianCalendar(); java.sql.Date databaseDate = new java.sql.Date(gc.getTimeInMillis());
Note how this example uses class names qualified with the package name, versus importing the package. As discussed in Chapter 2, either way is okay.
We've spent enough time on how to get a date! The next sections discuss how to compare and manipulate our dates.
Date time math and duration
RPG introduced the ADDDUR and SUBDUR op-codes with the introduction of the new language definition of RPG IV in V3R1. These op-codes enable you to add a specific duration to a date or subtract it from a base date. You can add or subtract days, months, or years from a given date by specifying the second part of factor two. In addition to subtracting a number from a date by extending factor two to indicate the type of number being subtracted, you can subtract one date from another date, and indicate in the result field what units to give the results in. This saves a lot of math on your part, as shown in Listing 8.8.
Listing 8.8: Date Durations with the SUBDUR and ADDDUR Op-codes in RPG
DstartD S D DATFMT(*ISO) INZ(D'2001-08-18') DendD S D DATFMT(*ISO) INZ(D'2001-08-28') Dresult S 10P 0 DresultD S D C* C endD SUBDUR startD result:*D C result dsply C endD SUBDUR 10:*D resultD C resultD dsply C startD ADDDUR 10:*D resultD C resultD dsply C eval *inlr = *on
This example first subtracts the end date (endD) from the start date (startD), and stores the difference in a numeric field (result) that will hold the number of days, as specified by the *D in the second part of the result. (The result is 10.) Next, it subtracts 10 days from the end date (endD) and stores the result in a date field (resultD). Finally, it adds 10 days to the start date (startD) and stores the result in a date field (resultD). The results of these last two operations are 2001-08-18 and 2001-08-28, respectively.
Table 8.2 lists the different options you can specify in the second part of factor two or the result field to indicate the type of the addition or subtraction you would like to accomplish. For example, if you wanted to determine the difference in number of months between two dates, you would specify *MONTHS or *M in the second part of the result field.
Date Part |
Keyword |
Short Form |
---|---|---|
Year |
*YEARS |
*Y |
Month |
*MONTHS |
*M |
Day |
*DAYS |
*D |
Hour |
*HOURS |
*H |
Minute |
*MINUTES |
*MN |
Second |
*SECONDS |
*S |
Microsecond |
*MSECONDS |
*MS |
Java uses the GregorianCalendar class to do date duration. Indeed, manipulating dates is the primary purpose of the GregorianCalendar versus its simpler cousin the Date class. While Date objects just store a date and allow simple operations such as displaying it, GregorianCalendar objects allow for complicated operations such as date comparisons and duration computation. To do this, of course, it has to have complicated, built-in algorithms that have a deep knowledge of the Gregorian calendar. (Versus, say, the Chinese calendar, which would require a different set of algorithms. There is no calendar other than Gregorian supported in Java today, but the architecture is there to easily allow others to be written. They are expected in the future, and may exist by the time you read this.)
The GregorianCalendar class has the method add, which allows you to add a date part, such as days or months or years, to a date. This will change the internal date value appropriately, as shown in Listing 8.9.
Listing 8.9: Date Durations with the add Method in Java's GregorianCalendar
import java.util.*; public class AddDateDurations { public static void main(String args[]) { GregorianCalendar gc = new GregorianCalendar(2001,11,31); System.out.println(); System.out.println("Before addition.....: " + gc.getTime()); gc.add(GregorianCalendar.DATE,2); System.out.println("After adding 2 days : " + gc.getTime()); gc.add(GregorianCalendar.YEAR,2); System.out.println("After adding 2 years: " + gc.getTime()); } } // end AddDateDurations
This example uses the GregorianCalendar class to do the date manipulation. It first instantiates the GregorianCalendar class with parameters to give an object representing December 31, 2001. Next, it does some date manipulation using the GregorianCalendar class. After printing the current date in the calendar, it uses the add method supplied in the GregorianCalendar class to add two days to the current day, and then adds two years to that. The result is printed after each addition:
Before addition.....: Mon Dec 31 00:00:00 EST 2001 After adding 2 days : Wed Jan 02 00:00:00 EST 2002 After adding 2 years: Fri Jan 02 00:00:00 EST 2004
What, then, tells the add method which part of the date you are adding? For example, how does it know you want the day, not the year? As you can see from the example, this is the purpose of the first parameter used in the call, which is a Java-supplied constant.
Java uses the constants YEAR, MONTH, DATE, HOUR, MINUTE, SECOND, and MILLISECOND to distinguish the type of addition. This is equivalent to the second part of factor two entered in RPG IV. As you recall, RPG uses *D, *M, or *Y in the second part of factor two to distinguish the type of addition. In both languages, the hard work of rolling all affected date parts is done for you.
Here is another example that illustrates this date "rolling" behavior in Java:
GregorianCalendar gc = new GregorianCalendar(2001,10,30); System.out.println("The date before addition: " + gc.getTime()); gc.add(GregorianCalendar.DATE,2); System.out.println("The date after addition: " + gc.getTime()); gc.add(GregorianCalendar.MONTH,2); System.out.println("The date after addition: " + gc.getTime()); gc.add(GregorianCalendar.DATE,26); System.out.println("The date after addition: " + gc.getTime()); gc.add(GregorianCalendar.DATE,1); System.out.println("The date after addition: " + gc.getTime());
This gives exactly the results you would hope for:
The date before addition: Fri Nov 30 00:00:00 EST 2001 The date after addition: Sun Dec 02 00:00:00 EST 2001 The date after addition: Sat Feb 02 00:00:00 EST 2002 The date after addition: Thu Feb 28 00:00:00 EST 2002 The date after addition: Fri Mar 01 00:00:00 EST 2002
Table 8.3 compares the keywords available in RPG to the constants available in Java.
Date Part |
RPG |
Java |
---|---|---|
Year |
*YEARS or *Y |
GregorianCalendar.YEAR |
Month |
*MONTHS or *M |
GregorianCalendar.MONTH |
Day |
*DAYS or *D |
GregorianCalendar.DATE |
Hour |
*HOURS or *H |
GregorianCalendar.HOUR |
Minute |
*MINUTES or *MN |
GregorianCalendar.MINUTE |
Second |
*SECONDS or *S |
GregorianCalendar.SECOND |
Millisecond |
*MSECONDS or *MS |
GregorianCalendar.MILLIS ECOND |
If the add method is equivalent to the ADDDUR op-code, what is equivalent to the SUBDUR op-code? The answer is the add method again! Adding a negative number is equivalent to subtracting. For example, adding the following two lines of code to the previous example will result in subtracting four years from the current date (be careful—you might go back in time!):
gc.add(Calendar.YEAR,-4); System.out.println("The date after deletion: " + gc.getTime());
How do you get the number of years, days, or months between two given dates? Unfortunately, there is no method supplied by Java to do this (which is a shame). However, it is possible to write the code yourself. Just call the currentTimeInMillis method on both of the GregorianCalendar objects, and subtract the larger from the smaller. Then divide the difference by 1000*60*60*24 to get the number of elapsed days, for example. If you want to print the difference in a nice way, use the ElapsedTime class from Listing 8.2.
Comparing dates
In RPG, you can simply use comparative op-codes like IF to find out if a date is before, after, or equal to another date value. How is this done in Java? Remember that dates are objects in Java, not primitive types, so operators like ==, <, and > will not work to compare to objects. These operators would only compare the memory addresses, which is not what you want. Instead, the GregorianCalendar class provides the methods equals, before, and after to perform these comparisons. Listing 8.10 shows how the before method determines whether a date is before another date.
Listing 8.10: Comparing Dates with the before Method in Java's GregorianCalendar
import java.util.*; public class CompareDates { public static void main (String args[]) { GregorianCalendar gc1 = new GregorianCalendar(2000, 11, 31); GregorianCalendar gc2 = new GregorianCalendar(2001, 0, 1); if (gc1.before(gc2)) System.out.println("Yes it is"); else System.out.println("No it is not"); } }
This example creates two GregorianCalendar objects, gc1 and gc2, representing December 31, 2000, and January 1, 2001, respectively. (Once again, only the month is zero-based.) It then uses the before method to determine if the gc1 date is before gc2. As a result, the program prints "Yes it is", as you would expect. If you substituted the after or equals method, the result would be "No it is not".
Extracting parts of a date or time
What if you have a variable that contains a date or time value, and you would like to retrieve only the day or the year portion of it? In RPG IV, a new operation code was introduced for this specific purpose. The EXTRCT op-code allows you to specify a field of type date, time, or timestamp in factor two, followed by the duration code that specifies the specific information you want to extract or retrieve. For example, specifying a time field in factor two followed by a colon and *H tells the compiler that you want to retrieve only the hour portion. The extract op-code can be used with any date, time, or timestamp field, and can return one of the following values depending on what you specify in the second part of factor two:
- The year, month, or day of a date or timestamp field
- The hours, minutes, or seconds of a time or timestamp field
- The microseconds of a timestamp field
The result field must be specified to be assigned the value returned. The type of the result field can be numeric or character. The constants for determining what to extract are the same as those in Table 8.2 for the ADDDUR and SUBDUR op-codes, for example:
C EXTRCT TStamp:*H temp1 4 0
As another example, what if you would like to print the actual name of the day instead of a number? For example, how would you determine which day October 22, 2000 fell on? (It was a Sunday, FYI.) For RPG, you would do this as shown in Listing 8.11.
Listing 8.11: Retrieving the Day Name as Text in RPG
D days S 9A DIM(7) CTDATA PERRCD(1) D* D CurrentDate S D D OneSunday S D INZ(D'2000-08-06') D Temp S 7 0 D TheDay S 1P 0 C* C 'enter date' DSPLY CurrentDate C CurrentDate SubDur OneSunday Temp:*d C Temp DIV 7 Temp C MVR TheDay C IF TheDay = 0 C EVAL TheDay = 7 C ENDIF C days(TheDay) DSPLY C MOVE *ON *INLR ** CTDATA days Monday Tuesday Wednesday Thursday Friday Saturday Sunday
The code starts by establishing a base line to deal with the calculation. This example declares the field OneSunday to represent any past Sunday. In this case, you simply pick Sunday August 6, 2000. When the user enters the date he or she wants to know about, the code simply subtracts it from the base date to calculate the number of days elapsed, using the SUBDUR op-code. Once you have the difference, you divide by seven, which is the number of days in a week.
The next line in the code uses the MVR (Move Remainder) op-code to move the remainder to the index that will be used later to retrieve the actual name of the day from the compile-time array. For example, if a user entered a date of August 8, 2000, the difference between the dates is two. Dividing by seven also gives a remainder of two, so the day was a Tuesday.
However, what if you entered the date August 13, 2000? The difference this time is seven. Dividing seven by seven gives a remainder of zero. If you directly used this as the index to the array, you would get an "index out of range" error. This is why in the code checks for that condition. If the remainder is zero, the day was a Sunday, and the index needs to be set to seven.
To accomplish this with Java, you can use a Java-supplied method in the GregorianCalendar class that gives the day of the week as an integer. The get method returns an integer from one to seven that represents the days Sunday to Saturday, respectively. The same example written in Java is shown in Listing 8.12.
Listing 8.12: Retrieving the Day Name as Text in Java
import java.util.*; public class GetDayOfWeek { public static final String days[] = {"SUNDAY","MONDAY","TUESDAY","WEDNESDAY", "THURSDAY","FRIDAY","SATURDAY"}; public static void main(String args[]) { GregorianCalendar today = new GregorianCalendar(); int dayOfWeek = today.get(GregorianCalendar.DAY_OF_WEEK); String dayAsString = days[dayOfWeek-1]; System.out.println(dayAsString); } }
As in the RPG example, the Java code declares a "compile-time" array days and initializes it to the names of the days. It then creates a GregorianCalendar object to retrieve today's date (versus prompting for a date, like RPG example did). Once it has a date, it can call the get method to retrieve the number of the day, which, after subtracting zero to make it zero-based, is used as the index of the array days. The println method prints the name of the day as a string.
Note that you have to pass a constant DAY_OF_WEEK to the get method to tell it which part of the date to extract. You can also extract other parts of the date by specifying a different constant. The list of constants available is shown in Table 8.4.
Constant |
Description |
---|---|
DATE or DAY_OF_MONTH |
The one-based day in the month (one to 31) |
DAY_OF_WEEK |
The one-based day in the week (one to 7) |
DAY_OF_WEEK_IN_MONTH |
The ordinal day of the week in the month (one to five), for example, a two if this is the second Friday in the month |
DAY_OF_YEAR |
The day within the year (one to 365) |
ERA |
The era as a constant, either AD or BC |
YEAR |
The year (four digits) |
WEEK_OF_YEAR |
The week number within year (one to 52) |
Of course, GregorianCalendar objects also capture time, not just dates, so there are constants for extracting time parts as well, as shown in Table 8.5. Not only is there this very handy get method, there is also a set method, which takes the same constants, as well as a second parameter which is the value to set that date or time part.
Constant |
Description |
---|---|
AM_PM |
The morning or afternoon as a constant, either AM or PM |
HOUR |
The 12-hour number (one to 12) |
HOUR_OF_DAY |
The 24-hour number (one to 24) |
MINUTE |
The minute within the hour (zero to 59) |
SECOND |
The second within the minute (zero to 59) |
MILLISECOND |
The millisecond within the second (zero to 999) |
DST_OFFSET and ZONE_OFFSET |
The millisecond offset from Daylight Savings Time and Greenwich Mean Time |
While the approach shown here works fine if you are writing an English-only application, a better approach would be to use the SimpleDateFormat class described in the following sections, with the E special character.
Formatting Date Fields
In RPG, there are many ways to format your date fields. You can simply use the new keyword DATFMT(*format{separator}) to tell the compiler the format you want your date fields to have. You can specify this keyword in two places: on the H-spec to establish global default formatting for your date fields, or on specific fields on the D-spec to indicate specific formatting for those fields. If no keyword is specified on either, the default *ISO is used. Table 8.6 illustrates the different date formats available in RPG.
Date Format |
Name |
Format |
Length |
Separator |
---|---|---|---|---|
Month/Day/Year |
*MDY |
mm/dd/yy |
8 |
/-.,& |
Day/Month/Year |
*DMY |
dd/mm/yy |
8 |
/-.,& |
Year/Month/Day |
*YMD |
yy/mm/dd |
8 |
/-.,& |
Julian |
*JUL |
yy/ddd |
6 |
/-.,& |
USA Standard |
*USA |
mm/dd/yyyy |
10 |
/ |
European Standard |
*EUR |
dd.mm.yyyy |
10 |
. |
International Standard Organization |
*ISO |
yyyy-mm-dd |
10 |
- |
Japanese Standard |
*JIS |
yyyy-mm-dd |
10 |
- |
For example, to tell the compiler that you want all fields in the program to have the *MDY format, you would code this:
H DATFMT(*MDY/)
Not only does this tell the compiler the format to use (MDY), it also specifies the actual separator to use between the day, time, and year (/). As you see from Table 8.6, you can specify any separator you like, depending on the actual format used and whether that separator is allowed or not. You can continue in the RPG code and specify the same keyword on certain fields, but with a different format. This would indicate to the compiler that the *MDY format is to be used globally on all fields in your program, except those fields that have this keyword specified on their D-spec. The rule is very simple and, in fact, applies to both date and time formats. Listing 8.13 illustrates this.
Listing 8.13: Using the DATFMT Keyword to Format Dates in RPG
H DATFMT(*ISO) D*———————————————————————— DstartDate S D INZ(D'2001-08-18') DUSADate S D DATFMT(*USA) DEURDate S D DATFMT(*EUR) DJISDate S D DATFMT(*JIS) DdefaultDate S D D*———————————————————————— C EVAL USADate = startDate C USADate dsply C EVAL EURDate = startDate C EURDate dsply C EVAL JISDate = startDate C JISDate dsply C EVAL defaultDate = startDate C defaultDate dsply
In this example, the keyword DATFMT is inserted on the H-spec to indicate that the overall default date format for the program is *ISO. This format will be used if no other format is specified on the D-spec, or in factor one of a C-spec. The example declares five different date fields. The first, startDate, just holds the initial date, and uses the default *ISO format. The next four will hold the value of startDate after a move. Each uses a different one of the four-digit year formats: *USA, *EUR, *JIS, and *ISO (inherited from the H-spec). The C-specs do four moves to illustrate the date conversion from one format type to another, giving the following results:
08/18/2001 18.08.2001 2001-08-18 2001-08-18
V4R2 of RPG IV supports three new external date formats, but only on the MOVE, MOVEL, and TEST op-codes: *CMDY, *CDMY, and *LONGJUL. You cannot use these formats when defining new internal fields. If you specify a DATFMT keyword on the H-spec, then all date literals you specify, such as on the INZ keyword for date fields, must conform to this format. This is true even for date fields declared with their own DATFMT keywords.
In Java, it is perhaps even easier to format a date field. The JDK supplies a SimpleDateFormat class in the java.text package to accommodate this. This class is very powerful and supplies functionality superior even to RPG IV. It allows you to choose any user-defined pattern for formatting. For example, see Listing 8.14.
Listing 8.14: Using the SimpleDateFormat Class to Format Dates in Java
import java.text.*; import java.util.*; public class TestDateFormat { public static void main (String args[]) { Date date = new Date(); System.out.println("Before formatting: " + date); String fPattern = new String("MM/dd/yyyy"); SimpleDateFormat test = new SimpleDateFormat(fPattern); String dateString = test.format(date); System.out.println("After formatting: " + dateString); } }
This example first instantiates a Date object with today's date. It then builds the pattern, which is equivalent to the RPG IV date format of *USA, by initializing it to the String object fPattern. Once that is established, it can then instantiate a SimpleDateFormat object and pass your pattern to it as a parameter. This is now a date formatting machine! You can simply call its format method with any Date object, and that date will be formatted according to the originally supplied pattern. The format method returns a formatted String object, which you can directly output with System.out.println, as with the current Date object in the example. If the current date is December 24, 2001, for example, you'll see this:
After formatting: 12/24/2001
If you are using a GregorianCalendar object instead of a Date object, recall you can use the getTime method to return a Date object from the GregorianCalendar object, which you would then pass to your SimpleDateFormat object's format method.
The only thing you need to change to establish a new format is the value of the pattern, either by instantiating a new object or using the applyPattern method of the SimpleDateFormat class. The SimpleDateFormat class allows you to specify various (case-sensitive) characters as part of the string to indicate different formatting patterns. Table 8.7 lists the predefined characters for specific date parts.
Character |
Meaning |
---|---|
G |
The era designator—text |
y |
The year—a number |
M |
The month in the year—text or a number |
d |
The day in the month—a number |
E |
The day in the week—text |
D |
The day in the year—a number |
F |
The day of the week in the month—a number |
w |
The week in the year—a number |
W |
The week in the month—a number |
' |
The escape character for text—a delimiter |
'' |
Single quotes around literals |
As you have seen, the M, d, and y characters are used for the pattern in the example. Notice that it uses an uppercase M to show the month in the year, but a lowercase d and y to show the day in month and the year, respectively. What do you think the result would be if you used an uppercase D? As shown in the table, D displays the day's number in the year. This means that if today's date were December 24, 2001, the formatted result when using the pattern MM/DD/yyyy would be 12/359/2001. Listing 8.15 shows a few more examples of date formatting.
Listing 8.15: More Examples of Formatting Dates in Java
import java.text.*; import java.util.*; public class TestDateFormat2 { public static void main (String args[]) { GregorianCalendar gcDate = new GregorianCalendar(2001,11,24); SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy G 'JAVA4RPG'"); String dateString = formatter.format(gcDate.getTime()); System.out.println("Formatted date: " + dateString); formatter.applyPattern("'Day of week:' EEEE"); dateString = formatter.format(gcDate.getTime()); System.out.println("Formatted date: " + dateString); } }
This gives the following output:
Formatted date: 24.12.2001 AD JAVA4RPG Formatted date: Day of week: Monday
Notice the use of G in the format to display the era, and the use of a literal 'JAVA4RPG'. Pay particular attention to the use of single quotes around the 'JAVA4RPG' literal. When specifying string literals in the format, you must enclose them in single quotes; otherwise, Java assumes that each letter is meant to be one of the substitution variables. The exception to this rule is the separator characters slash, dash, period, ampersand, and comma—they need not be enclosed in quotes.
The second pattern in Listing 8.15 uses the E character to display the day of week. Recall that, earlier in this chapter, you saw a technique to display the name, in text, for the day of the week. It used the get method in the GregorianCalendar class, indexing into an array that listed the names of all seven days. As it turns out, the SimpleDateFormat class can do that for you, using the E pattern.
As a comparison, Table 8.8 lists all the date formats available in RPG and their equivalent patterns in Java. You might be wondering why we use multiple characters in our patterns, such as yyyy, MM, and dd. One reason is that it helps other programmers easily see at a glance precisely the pattern, including lengths for the constituent parts. Of course, it is also more than this. The number of repetitions of the symbols has precise meaning to the SimpleDateFormat class. Well, sort of precise. Notice in Table 8.7 that some of the symbols expand into numbers, such as 1999, while some expand into text, such as Friday. For numbers, the count of repeated symbols in the pattern string tell Java exactly how many digits to show. If the count is greater than the resulting number, the number is zero-padded on the left. For example, for June, you would see 06, not 6, if you used MM in the pattern. If the count is too small to hold the result, however, it will not be truncated, so using one M for the month will show 12 for December and 6 for June. The year is an exception to this rule, as it will truncate to two digits (but no less) if only two digits are given.
Date Format |
Name |
Format |
Java Pattern |
---|---|---|---|
Month/Day/Year |
*MDY |
mm/dd/yy |
MM/dd/yy |
Day/Month/Year |
*DMY |
dd/mm/yy |
dd/MM/yy |
Year/Month/Day |
*YMD |
yy/mm/dd |
yy/MM/dd |
Julian |
*JUL |
yy/ddd |
yy/DDD |
USA Standard |
*USA |
mm/dd/yyyy |
MM/dd/yyyy |
European Standard |
*EUR |
dd.mm.yyyy |
dd.MM.yyyy |
International Standards Organization |
*ISO |
yyyy-mm-dd |
yyyy-MM-dd |
Japanese Standard |
*JIS |
yyyy-mm-dd |
yyyy-MM-dd |
For text-resulting symbols like E for day in week, there are usually two forms of output possible: a short form such as Fri and a long form such as Friday. Java uses the long form only if the symbol is repeated four or more times. For symbols that can be text or number, such as M for month, Java uses text (such as January) if the symbol is repeated three or more times, and the number (such as 01) otherwise.
Note |
Java allows all of the separators you can use with RPG IV, namely, the slash (/), dash (-), period, ampersand (&), and comma. |
Formatting Time Fields
RPG has just as many optional formats for your time fields as for dates. To format time fields, you use a keyword similar to that for dates, namely TIMFMT(*format{separator}). This tells the compiler the format you want your time fields to have. Table 8.9 lists the possible parameters you can specify.
Time Format |
Name |
Format |
Length |
Separator |
---|---|---|---|---|
Hours:minutes:seconds |
*HMS |
hh:mm:ss |
8 |
:.,& |
International Standards Organization |
*ISO |
hh.mm.ss |
8 |
. |
USA Standard |
*USA |
hh:mm am/pm |
8 |
: |
Europe Standard |
*EUR |
hh.mm.ss |
8 |
. |
Japanese |
*JIS |
hh:mm:ss |
8 |
: |
Again, as with date formatting, you are allowed to specify this keyword in two places: the H-spec (as a global default) or the D-spec (field-specific). If you like to format your times in a format similar to hh:mm:ss, for example, specify the format '*HMS:' as the parameter to the TIMFMT keyword. All optional separators are indicated in the table for each format type. Listing 8.16 shows an example of time formatting.
Listing 8.16: Using the TIMFMT Keyword to Format Times in RPG
H TIMFMT(*HMS.) H*———————————————————————— DstartTime S T INZ(T'10.25.30') DUSATime S T TIMFMT(*USA) DEURTime S T TIMFMT(*EUR) DJISTime S T TIMFMT(*JIS) DISOTime S T TIMFMT(*JIS) DdefaultTime S T D*———————————————————————— C EVAL USATime = startTime C USATime dsply C EVAL EURTime = startTime C EURTime dsply C EVAL JISTime = startTime C JISTime dsply C EVAL ISOTime = startTime C ISOTime dsply C EVAL defaultTime = startTime C defaultTime dsply
This example inserts the keyword TIMFMT on the H-spec to indicate that the overall time format for the program is *HMS with the period separator (.). Five moves on the C-specs illustrate the time conversion from one format type to another:
10:25 AM 10.25.30 10:25:30 10:25:30 10.25.30
Note that if you specify a TIMFMT keyword on the H-spec, then all time literals you specify, such as on the INZ keyword for time fields, must conform to this format. This is true even for time fields declared with their own TIMFMT keyword.
In Java, the SimpleDateFormat class, in addition to formatting a Date object for date display, can also, of course, format it for time display. This is because a Date object incorporates both date and time values, as Listing 8.17 shows.
Listing 8.17: Using the SimpleDateFormat Class to Format Times in Java
import java.text.*; import java.util.*; public class TestTimeFormat { public static void main(String args[]) { Date date = new Date(); System.out.println("Before formatting: " + date); SimpleDateFormat test = new SimpleDateFormat("hh:mm:ss"); String timeString = test.format(date); System.out.println("After formatting: " + timeString); } }
This is identical to the date formatting in Listing 8.14 except that it specifies "hh:mm:ss" for the format instead of "MM/dd/yyyy". Because only letters pertaining to time are specified, you get results showing only a formatted time. Thus, if the current time is 10:25:30, after running the program, the timeString object will contain the value 10:25:30. The letters you can specify in the format pattern pertaining to time are shown in Table 8.10.
Character |
Meaning |
---|---|
h |
The hour in am/pm form—a number from one to 12 |
H |
The hour within the day—a number from zero to 23 |
m |
The minute within the hour—a number |
s |
The second within the minute—a number |
S |
Milliseconds—a number |
a |
The am/pm marker—text |
k |
The hour within the day—a number from one to 24 |
K |
The hour in am/pm form—a number from zero to 12 |
z |
The time zone—text |
' |
The escape for text—a delimiter |
'' |
Single quotes around literals |
As with date formatting, the characters you specify in the pattern are case-sensitive, and the rules relating to the number of repetitions of a symbol apply the same way. Let's look at another example, in Listing 8.18.
Listing 8.18: More Examples of Formatting Times in Java
import java.text.*; import java.util.*; public class TestTimeFormat2 { public static void main(String args[]) { Date currentTime = new Date(); SimpleDateFormat format1 = new SimpleDateFormat("'Time is:' hh:mm:ss:SS a zz"); SimpleDateFormat format2 = new SimpleDateFormat("'Time is:' kk-mm-ss"); SimpleDateFormat format3 = new SimpleDateFormat("'Time is:' KK-mm-ss"); String timeString1 = format1.format(currentTime); String timeString2 = format2.format(currentTime); String timeString3 = format3.format(currentTime); System.out.println(timeString1); System.out.println(timeString2); System.out.println(timeString3); } }
This gives the following:
Time is: 09:43:52:35 PM EST Time is: 21-43-52 Time is: 09-43-52
This example uses SS for milliseconds, a for am/pm, and zz to indicate the time zone. It also shows a couple of the different options for expressing the hour, namely the lower- case k and the uppercase K. The former gives the number 21 instead of nine, as expected, but the latter gives nine, while the documentation implies you should get eight. Possibly this is a bug, since it seems to be the same as lowercase h.
For comparison, Table 8.11 lists the time formats available in RPG and their equivalent patterns in Java.
Time Format |
Name |
Format |
Java Pattern |
---|---|---|---|
Hours:minutes:seconds |
*HMS |
hh:mm:ss |
hh:mm:ss |
International Standards Organization |
*ISO |
hh.mm.ss |
hh.mm.ss |
USA Standard |
*USA |
hh:mm am/pm |
hh:mm aa |
Europe Standard |
*EUR |
hh.mm.ss |
hh.mm.ss |
Japanese |
*JIS |
hh:mm:ss |
hh:mm:ss |
The Default Formatter
You have seen how to create your own formats using SimpleDateFormat and specify the format on the constructor or via the applyFormat method. The Java documentation recommends you do not typically create your own formats, though. Rather, there are static methods in the DateFormat class (SimpleDateFormat's parent class, a concept covered in the next chapter) that, when called, will return to you a SimpleDateFormat object. Well, actually these methods are defined to return you a DateFormat object, so you have to cast them to a SimpleDateFormat object, another concept covered in the next chapter.
These methods are getDateInstance, getTimeInstance and getDateTimeInstance. They are designed to give you preset formats for dates, times, and date plus time, respectively, that are culturally correct for the country and language of the computer where your Java program is running. As you know, each country has its own preferred method of formatting dates and times, and if you are writing international software, it is polite to adhere to these standards versus forcing your own country's standard on others. Java gives you a nice easy way to do this, as shown in Listing 8.19.
Listing 8.19: Getting Default Date, Time, and Date/Time Formatters in Java
import java.text.*; import java.util.*; public class TestDefaultFormats { public static void main(String args[]) { Date currentTime = new Date(); SimpleDateFormat datefmt = (SimpleDateFormat)DateFormat.getDateInstance(); SimpleDateFormat timefmt = (SimpleDateFormat)DateFormat.getTimeInstance(); SimpleDateFormat dttmfmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(); System.out.println(datefmt.format(currentTime)); System.out.println(timefmt.format(currentTime)); System.out.println(dttmfmt.format(currentTime)); } }
This example tests each of the three static methods to retrieve culturally correct format objects for date, time, and date plus time. The result of running this on Windows NT set for USA and English is the following:
Jul 4, 2001 5:16:33 PM Jul 4, 2001 5:16:33 PM
You get a short form for the month, which might not be what you want, even if otherwise you can accept the format given. Well, there is a way to change this, by specifying a parameter on the method calls indicating whether you want a short, medium, long, or full format. The constants defined in DateFormat for these options are, logically enough, SHORT, MEDIUM, LONG, and FULL. Listing 8.20 tests each of them to see what you get.
Listing 8.20: Using Different Default Formatting Styles in Java
import java.text.*; import java.util.*; public class TestDefaultFormats2 { public static void main(String args[]) { int styles[] = {DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}; String names[] = {"SHORT", "MEDIUM", "LONG", "FULL"}; SimpleDateFormat datefmt, timefmt, dttmfmt; Date currentTime = new Date(); for (int idx = 0; idx < styles.length; idx++) { System.out.println("Style: " + names[idx]); datefmt = (SimpleDateFormat) DateFormat.getDateInstance(styles[idx]); timefmt = (SimpleDateFormat) DateFormat.getTimeInstance(styles[idx]); dttmfmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(styles[idx],styles[idx]); System.out.println(datefmt.format(currentTime)); System.out.println(timefmt.format(currentTime)); System.out.println(dttmfmt.format(currentTime)); System.out.println(); } } }
The syntax (SimpleDateFormat) casts an object from one type to another in Java (covered in Chapter 9). For now, it is enough to know that DateFormat is defined to return a DateFormat object, but actually returns a SimpleDateFormat object, so you need to tell Java this.
Note that getDateTimeInstance requires two parameters, one for the date style and one for the time style. Running this gives the following:
Style: SHORT 7/4/01 5:17 PM 7/4/01 5:17 PM Style: MEDIUM Jul 4, 2001 5:17:55 PM Jul 4, 2001 5:17:55 PM Style: LONG July 4, 2001 5:17:55 PM EDT July 4, 2001 5:17:55 PM EDT Style: FULL Wednesday, July 4, 2001 5:17:55 PM EDT Wednesday, July 4, 2001 5:17:55 PM EDT
We recommend you use these static methods to produce your formats for dates and times, versus creating your own formats. It is, of course, your choice, however.
Testing Date and Time
So far you have compared, manipulated and displayed dates. What is left but to test them? A function we often use in our programs is testing the validity of a given date, time, or timestamp. That is, quite often, we ask the user to input a date, or we read a date from a database file, and we wish to ensure that it is a valid date. Frequently, it comes in the form of a character field, and our intention is to convert it to a real date (or time) for the purpose of recording it permanently in a database or using it in comparisons or manipulations.
RPG, of course, has our friend the TEST op-code. This op-code allows you to test that a given character, date, time or timestamp field contains a valid date. You specify in factor one the date format to test against (as shown in Table 8.6), and in the result field, you specify the field to test. Note that for date tests, we have to specify the D extender, and if you want to use the new-for-V4R2 %ERROR built-in function instead of an error indicator, you also have to specify the E extender. Listing 8.21 is an example of how you can use this to test if a given character field contains a date that is valid for the *USA format (mm/dd/yyyy).
Listing 8.21: Using Different Default Formatting Styles in RPG
D DateField1 S 10A INZ('12/25/2001') D DateField2 S 8A INZ('12252001') D DateField3 S 8A INZ('99999999') C* C *USA TEST(DE) DateField1 C IF %ERROR = '0' C 'good date' DSPLY C ELSE C 'bad date' DSPLY C ENDIF C *USA0 TEST(DE) DateField2 C IF %ERROR = '0' C 'good date' DSPLY C ELSE C 'bad date' DSPLY C ENDIF C *USA0 TEST(DE) DateField3 C IF %ERROR = '0' C 'good date' DSPLY C ELSE C 'bad date' DSPLY C ENDIF
Three fields are tested here. The last two have no separators, so you have to specify zero after the *USA value in factor one. The result of running this program is the following:
good date good date bad date
Let's compare this to Java. To test that a String in Java contains a valid date, you actually use the parse method of the SimpleDateFormat class. This is the functional opposite of the format method! Rather than formatting an existing date into a String, it parses a given String and returns a Date. The String must match the format of the pattern specified for the SimpleDateFormat. If it does not, then null is returned instead of a Date object. Listing 8.22 shows a Java class that prompts for a date from the user (via the console) and attempts to parse it into a date. If that fails, an error is issued; otherwise, the resulting Date object is printed.
Listing 8.22: Parsing Strings into Dates in Java
import java.util.*; import java.text.*; public class TestDateParsing { public static void main(String args[]) { System.out.println("Enter a date in mm/dd/yyyy format:"); /* prompt for, and read, user input from console... */ String inLine = null; java.io.BufferedReader d = new java.io.BufferedReader( new java.io.InputStreamReader(System.in)); try { inLine = d.readLine(); } catch (java.io.IOException exc) {} // try parsing it... SimpleDateFormat parser = new SimpleDateFormat("MM/dd/yyyy"); Date date = parser.parse(inLine, new ParsePosition(0)); if (date == null) System.out.println("You entered an invalid date!"); else System.out.println("Date created is: " + date); } // end main } // end of class TestDateParsing
Note that parse requires not only the String object to attempt to parse into a date, but also a ParsePosition object. Just always pass new ParsePosition(0), as shown here. Let's run this and see what we get with different inputs:
>java TestDateParsing Enter a date in mm/dd/yyyy format: 12/25/2001 you typed: 12/25/2001 Date created is: Tue Dec 25 00:00:00 EST 2001 >java TestDateParsing Enter a date in mm/dd/yyyy format: 12252001 You entered an invalid date! >java TestDateParsing Enter a date in mm/dd/yyyy format: 9999999999 You entered an invalid date!
We think this is pretty cool! Can you imagine the ugly parsing code you would have to write to do this otherwise?
Look Before You Leap
One of the other methods available in the GregorianCalendar class that is worth mentioning is the isLeapYear method. Listing 8.23 shows an example of how you use it.
Listing 8.23: Using the isLeapYear Method in the GregorianCalendar Class in Java
import java.util.*; public class TestLeap { public static void main(String args[]) { GregorianCalendar date = new GregorianCalendar(); System.out.println("1600 a leap year? " + date.isLeapYear(1600)); System.out.println("1900 a leap year? " + date.isLeapYear(1900)); System.out.println("1976 a leap year? " + date.isLeapYear(1976)); System.out.println("1999 a leap year? " + date.isLeapYear(1999)); System.out.println("2000 a leap year? " + date.isLeapYear(2000)); } }
You simply supply the method with the year as an integer parameter. This gives the following:
1600 a leap year? true 1900 a leap year? false 1976 a leap year? true 1999 a leap year? false 2000 a leap year? true
Calculating a leap year is not as straightforward as you might initially expect. For example, it is not as simple as determining if the year is divisible by four. If you need to know if a given year is a leap year, you can simply rely on this method.
Time Zones
You'll sometimes need to establish the current date or time in an explicit time zone other than the one in which the program is currently running. This functionality is not available in RPG, but Java does offer it. Java supports a TimeZone class in the java.util package, which can be used to instantiate an instance of a particular time zone, as in:
TimeZone tz_GMT = TimeZone.getTimeZone("GMT"); TimeZone tz_EST = TimeZone.getTimeZone("EST");
Notice how the constructor parameter is simply a string representing a valid, known time zone ID, such as EST for Eastern Standard Time. To see all the supported time zones, use the static getAvailableIDs method:
String tzs[] = TimeZone.getAvailableIDs(); for (int idx = 0; idx < tzs.length; idx++) System.out.println(tzs[idx]);
To get the current time zone for your computer, use the TimeZone.getDefault method. Finally, once you have a TimeZone object, you can use it in both your GregorianCalendar object to affect manipulations, and in your DateFormat object to affect the time displayed. In both cases, simply use the setTimeZone method. For example, if you have a Date object, say from instantiating the Date class explicitly or from using the getTime method of a GregorianCalendar object, you might want to display that object's time value for each of your offices around the world. To do this, you create a SimpleDateFormat object as usual, but before using its format method, you first call the setTimeZone method with the TimeZone object you wish to use. Listing 8.24 shows how this is done.
Listing 8.24: Using the TimeZone Class in Java
import java.util.*; import java.text.*; public class TestTimeZones { public static void main(String args[]) { Date today = new Date(); // Current date/time TimeZone tz1 = TimeZone.getTimeZone("PST"); TimeZone tz2 = TimeZone.getTimeZone("EST"); TimeZone tz3 = TimeZone.getTimeZone("GMT"); TimeZone tz4 = TimeZone.getTimeZone("JST"); TimeZone tz5 = TimeZone.getDefault(); SimpleDateFormat formatter = new SimpleDateFormat("hh:mm:ss - 'TimeZone = ' z"); formatter.setTimeZone(tz1); System.out.println( formatter.format(today) ); formatter.setTimeZone(tz2); System.out.println( formatter.format(today) ); formatter.setTimeZone(tz3); System.out.println( formatter.format(today) ); formatter.setTimeZone(tz4); System.out.println( formatter.format(today) ); formatter.setTimeZone(tz5); System.out.println( formatter.format(today) ); } // end main method } // end TestTimeZones class
This results in the following:
11:15:31 - TimeZone = PST 02:15:31 - TimeZone = EST 07:15:31 - TimeZone = GMT 04:15:31 - TimeZone = JST 02:15:31 - TimeZone = EST
Note how the displayed time is different depending on what time zone is used, which is what you want.
Use the getDefault method to return a TimeZone object representing the time zone of the machine on which the program is running. In Windows, you might have to ensure your time zone is set properly on your machine.
Summary
This chapter discussed the following aspects of date and time manipulation:
- RPG IV date and time support is far superior to its predecessor in RPG III.
- The Java method currentTimeMillis in the System class returns the total number of milliseconds since the base time or epoch.
- Java has a Date class for retrieving and representing basic dates. In comparison, you use the TIME op-code to retrieve the time and/or date in RPG.
- Java has a GregorianCalendar class for performing date and time manipulations. In comparison, RPG has the SUBDUR and ADDDUR op-codes, combined with the date, time, and timestamp field types.
- To extract parts of a date in Java, use the get method on a GregorianCalendar object. In RPG, use the EXTRCT op-code.
- Java has a SimpleDateFormat class for formatting any Date object. RPG has the DATFMT and TIMFMT keywords.
- For culturally correct date formats, use the getDateInstance, getTimeInstance, and getDateTimeInstance static methods.
- Use the isLeapYear method found in the GregorianCalendar class to test whether a year is a leap year in Java.
- Java supplies TimeZone and SimpleTimeZone classes for international support.