Every number, and every date/time, has two forms of representation:

  • Internal: a binary number representing an integer, a floating-point value, or (for date/time) the number of milliseconds since Jan. 1, 1970 midnight GMT.
  • External: the same value in human-readable format.

All the date classes (java.util.Date, java.sql.Date, Calendar), and all the numeric wrapper classes (Integer, Long, Double, etc.) have toString() methods, of course, which will return their values in human-readable text. But that usually doesn’t suffice. For this, Java has classes to get you from internal to external and vice versa.

NumberFormat and DecimalFormat

If you want to format a number for display to a human, the easiest way is to get an instance of a subclass of NumberFormat, the abstract class that handles number formatting. Invariably, the subclass returned is DecimalFormat, which by default formats numbers with group separators to the left of the decimal, and (if the number is not an integer) a decimal point followed by as many significant digits as needed. Here’s an example.

float theValue = 1234.56;
NumberFormat nf = NumberFormat.getInstance();
String displayValue = nf.format(theValue);

nf stores an instance of DecimalFormat, and the resulting displayValue is “1,234.56”.

But NumberFormat has several getInstance methods, each returning a DecimalFormat configured to format numbers in different ways.

NumberFormat creation methodResult of formatting
static NumberFormat getInstance()
getNumberInstance()
Digits to the left of the decimal with group separators and no leading zeroes; significant digits (if any) to the right of the decimal.
static NumberFormat getCurrencyInstance()Same as getInstance() but with addition of a currency symbol.
static NumberFormat getIntegerInstance()Same as getInstance() but without decimal places.
static NumberFormat getPercentageInstance()Same as getInstance() but with values displayed as percentages.
NumberFormat instance acquisition methods

All the methods above return a formatter class configured for the current locale (known as Locale.FORMAT), which depends on how your operating system is set up. If the locale for your system is the United States, this means comma for the group separator, a period for the decimal separator, and “$” for the currency symbol. But you can pass an alternate Locale to any of these methods. If, for example, you code

float theValue = 1234.56;
NumberFormat nf = NumberFormat.getCurrencyInstance(Locale.ITALY);
String displayValue = nf.format(theValue);

the value of displayValue will be “1.234,56 €”.

NumberFormat has many methods for modifying the separators, currency symbols, number of decimal places to the left and the right of the decimal, and the nature of the formatted output.

But what if the default behavior from get…Instance() doesn’t suffice? It happens that you can create a DecimalFormat directly (although without being able to specify Locale) with a pattern of your choice. Furthermore, you can have separate patterns for positive and negative values. For example:

DecimalFormat df = new DecimalFormat(" 0000 ;(0000)");
String pos = df.format(123);
String neg = df.format(-123);

After this code executes, pos will be ” 0123 ” and neg will be “(0123)”. Note that each of the values has a leading zero, because the patterns specify it.

Pattern characterFunction
0Digit
#Digit, leading zero shows as absent
.Decimal separator or monetary decimal separator
Minus sign
,Grouping separator
;Subpattern boundary; separates positive and negative subpatterns
%Multiply by 100 and show as percentage
Used to quote special characters, although as the example above shows, this isn’t always necessary. To display a single quote by itself, code two apostrophes in a row.
Most of the pattern characters used by DecimalFormat

Once you’ve got your DecimalFormat, you can call various methods to affect whether it displays as currency, the maximum number of digits on either side of the decimal, whether it uses grouping, etc.

StringUtils

Suppose you want to arrange your formatted numbers in a right-justified column for a report or a web page. You can use a DecimalFormat that ensures that leading zeroes are provided so all formatted values have the same length. Most people don’t like leading zeroes, but if you use the # formatting character, the values in your column won’t be of uniform length.

The previous lesson introduced you to the Apache Commons Project’s DateUtils class. The same project has a StringUtils class for manipulating Strings. And one of those methods can pad a String on the left with the character of your choice:

int size = 15;
String value = "123-";
String paddedString = StringUtils.leftPad(value, size);

sets paddedString to ” 123-“–i.e., it right-justifies it to a length of 15 characters.

leftPad also lets you choose a single character or a String to pad with. StringUtils has methods for padding on the right, centering, joining strings with a specified separator, and a host of other useful functions.

See the instructions in the previous lesson for adding Apache Commons classes to your project.

DateFormat and SimpleDateFormat

Like NumberFormat, Java provides the abstract DateFormat class for formatting and parsing dates. And like NumberFormat, it supports a number of different formats and Locales, depending on how the static getDateInstance() or getDateTimeInstance() method is coded.

DateFormat methodFunction
static DateFormat getDateInstance()Gets a date formatter with the default style for the default locale.
static DateFormat getDateInstance(int style)Gets a date formatter with the specified style for the default locale.
static DateFormat getDateInstance
(int style, Locale aLocale)
Gets a date formatter with the specified style for the specified locale.
static DateFormat getDateTimeInstance()Gets a date/time formatter with the default date and time styles for the default locale.
static DateFormat getDateTimeInstance
(int dateStyle, int timeStyle)
Gets a date/time formatter with the specified date and time styles for the default locale.
static DateFormat getDateTimeInstance
(int dateStyle, int timeStyle,
Locale aLocale)
Gets a date/time formatter with the specified date and time styles and locale.
static DateFormat getInstance()Gets a default date/time formatter that uses the SHORT style for both date and time.
Creation methods for DateFormat

The styles mentioned above are constant values that belong to the DateFormat class and have the following meanings:

Style valueSample formatted dateSample formatted time
DateFormat.SHORT12/15/523:30 PM
DateFormat.MEDIUM
(also default)
Dec 15, 19523:30:32 PM
DateFormat.LONGDecember 15, 19523:30:32 PM EDT
DateFormat.FULLTuesday, December 15, 1952 AD3:30:32pm PM Eastern Daylight Time
Results of DateFormat style selection

For more control over the format, we have DateFormat’s concrete subclass: SimpleDateFormat. Just as with DecimalFormat, SimpleDateFormat uses a developer-specified pattern to format date/times for output and parse strings for conversion to date/times. Here’s an example.

Calendar now = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat
     ("EEE MMM d, yy hh:mma zzz");
sdf.getCalendar().setTimeZone(TimeZone.getTimeZone("US/Eastern");
String output = sdf.format(now);

This example shows how the SimpleDateFormat has an associated Calendar which determines the current TimeZone and Locale, and shows how you can modify this Calendar to affect the formatting. Since I ran this example on Aug. 15, 2022 at 6:51 AM EDT, output was: "Mon Aug 15, 22 06:15AM EDT". I happened not to be in the Eastern time zone when I ran this; if I were (and I wanted now and sdf to reflect that), I wouldn’t bother modifying sdf‘s Calendar.

What you get for different pattern characters often depends on how many there are. Change the one above to "EEEE MMMM dd, yyyy hh:mma zzzz" and the result is "Monday August 15, 2022 06:15AM Eastern Daylight Time".

Here are some of SimpleDateFormat’s more useful pattern characters. For complete documentation, including the rules for date formatting which can be unexpectedly complex, please consult the documentation.

CharacterDisplay
GEra designator: AD or BC
yYear: yy gives a two-digit year; three or more y’s gives a 4-digit year
MMonth of year:
m gives one- or two-digit month number;
mm gives two-digit month number;
mmm gives month abbreviation;
four or more m’s gives full month name capitalized.
dDay of month:
d gives one- or two-digit day of month;
two or more d’s gives the day of month with as many leading zeroes as needed to match the number of d’s.
FDay of week in month
EDay name; one to three E’s gives the three-character day name abbreviation; four or more gives the capitalized day name.
uDay number within week. No matter what the date being formatted is, or what the SimpleDateFormat’s Calendar says, Monday is always 1, Tuesday = 2, … Sunday = 7. Yes, this is inconsistent with the Calendar class.
aAM/PM marker
HHour in day, 24-hour format. HH will provide a leading zero.
hHour in day, 12-hour format. hh will provide a leading zero.
mMinute in hour. mm will provide a leading zero.
sSecond in minute. ss will provide a leading zero.
zGeneral time zone. Three or fewer z’s provides the abbreviation. Four or more produces the spelled out time zone name.
Commonly-used SimpleDateFormat pattern characters

And Now, an Editorial

It’s often tempting to maintain dates, times, and numbers in memory in external format, using String.

DON’T.

There is no one correct way to represent dates, times, or numbers in a character format.

Numbers in character form might or might not be signed, or have leading zeroes or decimal points or trailing decimal zeroes. Dates might be punctuated by hyphens, or slashes, or not at all; they might be in MM/DD/YYYY format (as in the United States), or DD/MM/YYYY format (as in most of Europe and Asia). Or the month might be represented by its abbreviation–and is the ninth month “SEP” or “Sept”?

In String format, “+12.00” is not equal to “12”, even though mathematically they are. “12/31/2000” is not equal to “2000-12-31”, even though they represent the same day.

Furthermore, computation and comparison with values in String format requires additional coding at least. Consider that although 99 is less than 100, “99” is greater than “100”. And “12/31/1999” follows “01/01/2000”!

When it comes to numeric data, String is for human consumption only.

It happens your humble scrivener worked on a project in the US where the tech lead insisted on maintaining dates internally as String in “MM/DD/YYYY” format–even though he himself came from India where dates are written in “DD/MM/YY” format. This led to extra coding for date comparisons and the like, which would have been unnecessary had the dates been kept in Date instances. Fortunately, nothing went wrong, but someday, some maintenance developer is going to come along and, unaware of all this, end up with code that thinks December 1999 is later than January 2000.

This practice inspired the following ditty, sung to the tune of the Mister Ed” theme:

What You Need To Know

  • Numbers and date/time values have internal and external (i.e. human-readable String) representations.
  • NumberFormat and DecimalFormat provide a flexible framework for formatting numeric values to Strings, and parsing Strings to numeric values.
  • SimpleDateFormat does the same for date/time values.
  • It’s almost always better to convert numbers and date/time values from external to internal format when they need to be stored in memory, and convert them from internal format to external only when they need to be presented to the outside world. Keeping such values internally in external formats is a Terrible, Horrible, No Good, Very Bad Idea.

Next: Recursion