Thursday, July 29, 2021

Java Tutorial: Formatting Date&Time, Number and String

Chapters

Format Specifiers

If we want more control of how our string is formatted, we can use format specifiers to do just that. Some methods support the use of format specifiers like the printf() and format() methods. Format specifiers start with "%" symbol, followed by characters(converters) that do the formatting and optionally, other formatting elements like flags. These converters indicate the type to be formatted.
Format specifiers for general, character, and numeric types
%[argument_index$][flags][width][.precision]conversion

e.g. 
String str = String.format("PI: %f, %1$+010.5f",Math.PI);

Result
PI: 3.141593, +003.14159
The elements enclosed with brackets[] are optional elements. "%" and conversion elements are the essential parts of the formatter. Elements must be placed according to the order of the format specifiers above.

argument_index is a decimal integer indicating the position of the argument in the argument list. The first argument is referenced by "1$", the second by "2$", etc. Use the < flag to refer to previous argument that is used.

flags is a set of characters that modify the output format. The set of valid flags depends on the conversion. In other words, some flag may not work with some converters.
e.g. #,+,-,0

width is a positive decimal integer indicating the minimum number of characters to be written to the output.

precision is a non-negative decimal integer preceded by dot(.) and it's usually used to restrict the number of characters. The specific behavior depends on the conversion.

conversion or converter indicates the type to be formatted.
e.g. d,f,h

Conversion elements are divided into different categories as follows:

General: may be applied to any argument type.

Character: may be applied to basic types which represent Unicode characters like char, byte, Character,etc. This conversion may also be applied to the types int and Integer when Character.isValidCodePoint(int) returns true.

Numeric: may be applied to integral and floating-point types like int, float, Double, etc.

Date/Time: may be applied to Java types which are capable of encoding a date or time like long, Calendar, Date, etc.

Percent: produces a literal '%' ('\u0025')

Line Separator: produces the platform-specific line separator.

Conversion elements, except for percent(%%) and line separator(%n), expect arguments. This tutorial is just a summary of the formatting tutorial in java. Visit the Formatter documentation to read the full, informative and insightful documentation about formatting in java.
Formatting Strings and Numbers

We can format strings and numbers by using the printf() method or the format() method. These two methods are present in some streams like PrintStream. format() method can also be seen in String class.

Let's try formatting strings and display them on the console.
public class SampleClass{

  public static void main(String[]args){
    String str1 = "Systemic";
    String str2 = "Caution";
    
    //we can use printf() and format()
    //to format an output of a
    //stream
    //printf(String format, Object... args)
    System.out.printf("Sample#1: %n%s%n%S",
                      str1,str2);
    
    System.out.println();
    
    //Formatting doesn't change 
    //the argument's original value
    System.out.println(str2);
  }
}

Result
Sample#1:
Systemic
CAUTION
Caution
First off, let's examine Sample#1. So, the first argument of prinf() and format() is the format string. This string will be displayed on the console. The second arugment is an array of objects. These objects are called arguments of the format string. When evaluating the format string, we start from left to right position.

So, this is the format string: "Sample#1: %n%s%n%S%n"
Let's evaluate this string starting from the left side. There's no specifier in this sebsequence: Sample#1:
So, java writes this subsequence as is. Next, we found a format specifier. Remember, format specifier starts with "%" and ends with a convertor.

the format specifier is "%n". This specifier is a line-separator specifier. This specifier is platform-specific and we should use this instead of "\n". There are java platforms that don't recognize "\n" as newline. Some platforms prefer "\r\n" as newline. This specifier doesn't expect an argument.

Next, that "%n" is followed by "%s" which is a string specifier. This specifier formats an argument as string. This specifier expects an argument and will throw MissingFormatArgumentException if there's no argument found.

This specifier is our first specifier that requires an argument so, this specifier formats the first argument which is the string "Systemic" in the example above. Next, "%s" is followed by "%n", followed by "%S". "%S" is the same as "%s" the only difference is that "%S" formats argument's letters into uppercase letters whereas "%s" doesn't format the letter case of an argument. This also applies to other convertors except for "%n" and "%%".

"%S" is the second specifier that requires an argument so, it formats the second argument which is the string "Caution" in the example above.

Let's try another example.
import java.util.UnknownFormatConversionException;
import java.util.IllegalFormatConversionException;
public class SampleClass{

  public static void main(String[]args){
    try{
      //format() is equivalent to printf().
      //according to java documentation, printf()
      //is more of a convenience method
      System.out.format("Sample#2:%nchar: %c%n"+
                        "dec: %d%noct: %2$o%n"+
                        "hex: %<X%npercent: %<d%%",
                        '\u0042',1325);
      }
      catch(UnknownFormatConversionException e){
        System.err.printf("%nUnknown Format!%nMessage: %s%n"+
                          "Conversion char: %s",
                          e.getMessage(),e.getConversion());
      }
      catch(IllegalFormatConversionException e){
        System.err.printf("%nIllegal Format!%nConverstion char:"+
                          " %c",
                          e.getConversion());
      }
  }
}

Result
Sample#2:
char: B
dec: 1325
oct: 2455
hex: 52D
percent: 1325%
So, this is the format String
"Sample#2:%nchar: %c%ndec: %d%noct: %2$o%nhex: %<X%npercent: %<d%%"
Let's examine this format and start off with "%c". "%c" specifier formats an argument as a character. We use this convertor if we have character argument like unicode sequence and other character sequences.

Next, "%d" formats an argument to a decimal integer. Don't be confuse between decimal integer and decimal number. Decimal integers refers to base-10 integer whereas decimal number refers to a number with fractional part or refers to the fractional part of a number. In US and some other countries, fractional part is separated by dot(.).

Next is this format specifier "%2$o". This specifier contains argument index and an "o" converter. "2$" refers to the second argument which is an integer. "o" formats the second argument to an octal(base-8) integer. In the example above, there are only two arguments. If we remove the argument index, we will encounter an error 'cause "o" expects a third argument.

By using argument index, the second argument is reused and "o" formats the argument to an octal(base-8) integer.

Next, Let's examine this specifier "%<X". "<" refers to the previous argument that has been used. "%2$o" precedes "%<X" and "%2$o" refers to the second argument so, "%<" also refers to the second argument. "X" formats argument to a hexadecimeal(base-16) integer.

"%%" puts a literal percent(%) in the format string. "%" alone indicates a format specifier in format string whereas "%%" indicates a percent(%) literal in format string.

In the example above, UnknownFormatConversionException and IllegalFormatConversionException can be caught. try changing this specifier "%2$o%n" into "%1$o%n" and you will encounter an IllegalFormatConversionException. It throws an exception 'cause "1$" refers to the first argument which is a character and the character can't be formatted to octal integer.

Try changing this specifier "%<X" into "%&X" and you will encounter an UnknownFormatConversionException. It throws an exception 'cause "&" is not a valid formatting element.

Those exceptions are usually encountered when formatting string. There more exceptions that can be encountered. Also, notice that we use System.err instead of System.out to display the error messages on the console. err and out are the same though by convention, use err when displaying error messages in console-based applications.

Let's try one more example.
import java.util.Locale;
public class SampleClass{

  public static void main(String[]args){
    //there's a format() method in String
    //class that we can use. This method
    //is helpful if we want to format 
    //a string and put it somewhere
    //else aside from streams
    //like in a textbox for example
    String str = String.format(Locale.US,
                               "%,.1f",
                               1234567.55);
    System.out.println(str);
    str = String.format("%.4s","Hello Everyone!");
    System.out.println(str);
    str = String.format("%010d",150);
    System.out.println(str);
    str = String.format("[%-10d]",150);
    System.out.println(str);
  }
}

Result
1,234,567.6
Hell
0000000150
[150       ]
First off, let's examine this specifier "%,.1f". "," groups numbers using a locale-specific grouping separators. In US, comma(,) is used to group numbers to represent thousands and beyond. Try changing the locale to Locale.FRENCH and you will get a different grouping separators.

".1" is a precision element. When applied in floating-point number, precision restricts the floating-point's decimal places and round off the last decimal place. "f" is a converter that formats floating-point argument to a floating-point number.

Next, let's examine this specifier "%.4s". ".4" is a precision element. When applied in a string, precision restricts the maximum character that is gonna be included in the formatted result. "s" is convertor that formats argument to a string.

Next, this specifer "%010d". "0" is a flag that pads 0 from left to right. This flag requires width otherwise, an exception will be thrown. Also, there are flags are not allowed to be concatenated with "0" flag like the "-" flag. "10" is the width. It specifiy the maximum character length of the formatted result. If the argument length is less than the width, and "0" flag is set then, excess length will be padded with 0. If "0" flag is not set, whitespace will be used for padding. "d" is a converter that formats an integer argument to a decimal integer.

Last, this specifier "[%-10d]". "[]" are literals. "-" is a flag that formats the alignment of an argument to left justified. "10" is the width and "d" is the decimal integer converter.

NumberFormat Class

We use NumberFormat class to format or parse numbers. NumberFormat is an abstract class so we can't instantiate it directly. Though, there are static NumberFormat's methods that return a NumberFormat instance like getInstance() and other getXXXInstance() methods.
import java.text.NumberFormat;
import java.util.Locale;
public class SampleClass{

  public static void main(String[]args){
    //Use getInstance() or getNumberInstance()
    //to get the normal number format.
    NumberFormat nf = NumberFormat.getInstance();
    
    float f = 12345.44f;
    System.out.println("Default: " + nf.format(f));
    
    nf = NumberFormat.getInstance(Locale.US);
    String str = nf.format(f);
    System.out.println("US: " + str);
    
    nf = NumberFormat.getInstance(Locale.ITALY);
    System.out.println("ITALY: " + nf.format(f));
    
    nf = NumberFormat.getInstance(Locale.US);
    nf.setGroupingUsed(false);
    System.out.println("US: " + nf.format(f));
  }
}

Result
Default: 12,345.44
US: 12,345.44
ITALY: 12.345,44
US: 12345.44
In the example above, getInstance() without argument returns a NumberFormat type with default locale. Default locale is platform-specific. As the time of this writing, my default locale is Locale.US. That's why the first and second result are equal. Yours might not be. Italy groups numbers differently, That's why it's not equal to the previous results.

Use setGroupingUsed() to disable number grouping. By default, setGroupingUsed() is set to true.

Limiting Fractional Digits

We can limit the number of decimal places in the formatted result using the setMinimumFractionDigits() and setMaximumFractionDigits(). Zeroes will fill up unused space if the argument digits count is less than the minimum fraction digits. If the digits are greater than the maximum fraction digits then, the argument will be rounded.
import java.text.NumberFormat;
import java.util.Locale;
public class SampleClass{

  public static void main(String[]args){
  
    NumberFormat nf = NumberFormat.getInstance(Locale.US);
    float f1 = 3.3f;
    float f2 = 3.43588f;
    
    nf.setMinimumFractionDigits(3);
    nf.setMinimumFractionDigits(4);
    System.out.println(nf.format(f1));
    System.out.println(nf.format(f2));
  }
}

Result
3.3000
3.43588
Formatting of Percentage, Currency and Rounding Numbers

We can format percentage and currency. Also, we can assign different types of rounding when formatting decimal numbers.
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Currency;
import java.math.RoundingMode;
public class SampleClass{

  public static void main(String[]args){
    float f1 = 0.5f;
    float f2 = 12300.55f;
    
    //use getPercentInstance() get a
    //percentage format
    NumberFormat nf = NumberFormat.
                      getPercentInstance(Locale.US);
    System.out.println(nf.format(f1));
    
    //use getCurrencyInstance() get a
    //currency format
    nf = NumberFormat.
         getCurrencyInstance(Locale.JAPAN);
    System.out.println(nf.format(f2));
    
    //To set new Currency, use setCurrency() method
    //or invoke getCurrencyInstance()
    nf.setCurrency(Currency.getInstance(Locale.US));
    System.out.println(nf.format(f2));
    
    nf = NumberFormat.getNumberInstance(Locale.US);
    nf.setMaximumFractionDigits(1);
    
    //use getRoundingMode() to get the current
    //RoundingMode. Use setRoundingMode to assign
    //new RoundingMode to the NumberFormat.
    System.out.println("Default Rounding Mode: " + 
                       nf.getRoundingMode());
    System.out.println(nf.format(f2));
    
    nf.setRoundingMode(RoundingMode.DOWN);
    System.out.println("New Rounding Mode: " + 
                       nf.getRoundingMode());
    System.out.println(nf.format(f2));
    
    nf.setRoundingMode(RoundingMode.UP);
    System.out.println("New Rounding Mode: " + 
                       nf.getRoundingMode());
    System.out.println(nf.format(f2));
  }
}

Result
50%
?12,301
$12,301
Default Rounding Mode: HALF_EVEN
12,301.5
New Rounding Mode: DOWN
12,301.5
New Rounding Mode: UP
12,301.6
The first result is "50%" 'cause 0.5 multiplied by 100 is equal to 50 or 50%. The second result is "?12,301" 'cause my default charset of my java platform doesn't support japanese characters. If my default charset supports japanese characters then, the result could be this: "¥12,301". Also, notice that the result is rounded off, discarding decimals in the process. The third result is "$12,301" 'cause I used US locale in this formatted result and my default charset supports "$" character.
Note: It's recommended to use BigDecimal When handling currencies.

Read the Currency class documentation to know more about currencies in java. Read the RoundingMode class documentation to know more about rounding modes in java.

Parsing

If we want a string to be converted to a number then, we can use the parse() method.
import java.text.NumberFormat;
import java.util.Locale;
import java.text.ParseException;
public class SampleClass{

  public static void main(String[]args) 
  throws ParseException{
    NumberFormat nf = NumberFormat.
                      getInstance(Locale.ITALY);
    Number num = nf.parse("1.234.567");
    
    System.out.println(num.intValue());
  }
}

Result
1234567
In the example above, we successfully parse all the characters in "1.234.567" string as a number 'cause we use the Locale.ITALY as locale in the formatted result and dot(.) is equivalent to comma(,) in US locale when grouping digits. Try changing the locale to Locale.US and the result will be different.

Read the parse() method description in the NumberFormat class to know more about the parse() method.

DecimalFormat Class

We can use DecimalFormat to format decimal integers and numbers with more control and flexibility. DecimalFormat uses patterns to format decimal numbers. Patterns contain characters, some of them are normal literals, some have special meaning. Here's the pattern syntax of DecimalFormat.
Prefix(opt) Number Suffix(opt)
Prefix and suffix are any Unicode characters except for \uFFFE, \uFFFF, and some special characters.
Number is a number of set of numbers that we wanna format.

Here are some characters that we can use in a pattern for DecimalFormat.

0: represents a digit and sets the minimum integer or decimal digits of a pattern. 0 digit substitutes for absence of digits. Applicable in the "Number" part of the pattern.

#: represents a digit. absence of digits is ignored. Should be placed in the "Number" part of the pattern.

dot(.): Decimal separator or monetary decimal separator. Should be placed in the "Number" part of the pattern.

comma(,): Grouping separator or monetary grouping separator. Should be placed in the "Number" part of the pattern.

semicolon(;): Separates positive and negative patterns

%: Multiply by 100 and show as percentage. Should be placed in "prefix" and "suffix" part of the pattern.

': Used to quote special characters in a prefix or suffix. Should be placed in "prefix" and "suffix" part of the pattern.

More information can be found in the DecimalFormat class documentation.

"#" and "0" characters

Let's create an example to demonstrate DecimalFormat.
import java.text.DecimalFormat;
public class SampleClass{

  public static void main(String[]args){
    String pattern1 = "##,###.##";
    String pattern2 = "00,000.00";
    double decimal = 1234567.567;
    
    DecimalFormat df = new DecimalFormat(pattern1);
    
    System.out.println("Pattern1: " + 
                       df.format(decimal));
                       
    df = new DecimalFormat(pattern2);
    
    System.out.println("Pattern2: " + 
                       df.format(decimal));
  }
}

Result
Pattern1: 1,234,567.57
Pattern2: 1,234,567.57
First off, we use DecimalFormat(String pattern) constructor. This constructor creates a DecimalFormat using the given pattern and the symbols for the default FORMAT locale. This is a convenient way to obtain a DecimalFormat when internationalization is not the main concern. "##,###.##" and "00,000.00" expect 5 decimal integers and 2 decimal numbers.

if the given decimal integer is less than the pattern's expected digits, absence of digits is ignored(if # is used) or replaced by "0" digit(if 0 is used) and numbers are grouped based on the last group in the pattern. Otherwise, numbers are just grouped based on the last group in the pattern.
Example

Pattern: ##,####

Given: 123456789
Result: 1,2345,6789

Given: 12345678
Result: 1234,5678

Given: 1234567
Result: 123,4567

Given: 123456
Result: 12,3456

Pattern: #,###

Given: 1234
Result: 1,234

Given: 12345
Result: 12,345

Given: 123456
Result: 123,456

Given: 1234567
Result: 1,234,567

As we can see from the results above, numbers are grouped based on the last group in the pattern. For example, in "#,###" pattern, the last group is "###" so, if possible, the given digits are grouped into three digits. The given digits above are greater than the Patterns' expected digits. Let's try given digits that are less than the expected digits of patterns.
Example

Pattern: ##,###

Given: 1234
Result: 1,234

Given: 123
Result: 123

Given: 12
Result: 12

Pattern: 00,000

Given: 1234
Result: 01,234

Given: 123
Result: 00,123

Given: 12
Result: 00,012

Pattern: ##,####,###

Given: 123456789
Result: 123,456,789

Given: 12345678
Result: 12,345,678

Given: 1234567
Result: 1,234,567

Given: 123456
Result: 123,456

Pattern: 00,0000,000

Given: 123456789
Result: 123,456,789

Given: 12345678
Result: 012,345,678

Given: 1234567
Result: 001,234,567

Given: 123456
Result: 000,123,456

For the decimal part, If the given decimal number is greater than the pattern's expected digits then, the given decimal numbers are rounded off, removing the unnecessary decimal numbers in the process. Otherwise, absence of digits is ignored(if # is used) or replaced by "0" digit(if 0 is used) and no rounding off will happen. If the pattern doesn't expect decimal numbers but the given digit has decimal numbers, decimal integers will be rounded off, removing the decimal numbers in the process.
Example

Pattern: #,###.##

Given: 1234.556
Result: 1234.56

Given: 1234.55
Result: 1234.55

Given: 1234.5
Result: 1234.5

Given: 1234
Result: 1234

Pattern: 0,000.00

Given: 1234.556
Result: 1234.56

Given: 1234.55
Result: 1234.55

Given: 1234.5
Result: 1234.50

Given: 1234
Result: 1234.00

Pattern: #,###

Given: 1234.56
Result: 1235
Mixing "#" and "0" in a Pattern

Next, we can mix "#" and "0" in a single pattern. Let's create an example.
import java.text.DecimalFormat;
public class SampleClass{

  public static void main(String[]args){
    //Note, don't put "0" before or in-between "#"
    //You will encounter a runtime exception
    //e.g. 0,###,###
    //
    //put "0" after "#". This only applies in the
    //decimal integer part of the pattern. For
    //the decimal number part, don't put "0"
    //after or in-between "#" or you will
    //encounter a runtime exception
    //e.g.
    //#,#00.00# //legal
    //0,#0#.#0 //Illegal
    
    String pattern = "##,000.00#";
    DecimalFormat df = new DecimalFormat(pattern);
    System.out.println(df.format(12345.246));
    System.out.println(df.format(1234.24));
    System.out.println(df.format(123.2));
    System.out.println(df.format(12));
  }
}

Result
12,345.246
1,234.24
123.20
012.00
Subpattern

Next, let's try a pattern with a subpattern.
import java.text.DecimalFormat;
public class SampleClass{

  public static void main(String[]args){
    String pattern1 = "#,000.00;(-0,000.00)";
    String pattern2 = "#,000.00";
    double digits = 12345.56;
    
    DecimalFormat df = new DecimalFormat(pattern1);
    
    System.out.println("p1: " + df.format(digits));
    System.out.println("p1: " + df.format(-digits));
    
    df = new DecimalFormat(pattern2);
    
    System.out.println("p2: " + df.format(digits));
    System.out.println("p2: " + df.format(-digits));
  }
}

Result
p1: 12,345.56
p1: (-12,345.56)
p2: 12,345.56
p2: -12,345.56
A pattern, can have a positive and negative parts. By default, a pattern without a subpattern is a positive pattern. If a pattern without subpattern formats a negative number, a minus sign(-) will be added as a prefix in the formatted result. Otherwise, a subpattern will be used as a format for negative numbers. The "()" in the subpattern above are normal literals.
Note: Explicit subpattern still follows the number of digits, minimal digits, and other characteristics of a positive pattern. The purpose of subpattern is to apply prefix and suffix to negative pattern that are distinct from the positive pattern.
So, "0,000.0;(0)" == "0,000.0;(0,000.0)"

Letters and Other Characters as Prefix/Suffix

We already have seen in the example above that we can add literals as part of DecimalFormat's pattern. We can put letters and other acceptable characters in the pattern as prefixes/suffixes.
import java.text.DecimalFormat;
public class SampleClass{

  public static void main(String[]args){
    String pattern1 = "Postive: |#,000.00|;"+
                      "Negative: (-0,000.00)";
    double digits = 12345.56;
    
    DecimalFormat df = new DecimalFormat(pattern1);
    
    System.out.println(df.format(digits));
    System.out.println(df.format(-digits));
    
  }
}

Result:
Positive: |12,345.56|
Negative: (-12,345.56)
If we misplace prefix/suffix characters and put them in the "Number" part of a pattern, java may make those misplaced characters as suffixes or an exception may be thrown. For example, "#,(##)0" is equivalent to "#,##0()".

Formatting With Percent(%), Single Quote(') and Scientific Notation

Let's try other characters that have special meaning in DecimalFormat.
import java.text.DecimalFormat;
public class SampleClass{

  public static void main(String[]args){
    String pattern1 = "0.00#%";
    String pattern2 = "'#': #,###";
    String pattern3 = "##0.##E0";
    String pattern4 = "000.00E0";
    
    DecimalFormat df = new DecimalFormat(pattern1);
    System.out.println("p1 " + df.format(.009));
    System.out.println();
    
    df = new DecimalFormat(pattern2);
    System.out.println("p2 " + df.format(1955));
    System.out.println();
    
    df = new DecimalFormat(pattern3);
    System.out.println("p3 " + df.format(1234));
    System.out.println("p3 " + df.format(123456));
    System.out.println("p3 " + df.format(0.00123));
    System.out.println("p3 " + df.format(0.001));
    System.out.println();
    
    df = new DecimalFormat(pattern4);
    System.out.println("p4 " + df.format(1234));
    System.out.println("p4 " + df.format(123456));
    System.out.println("p4 " + df.format(0.00123));
    System.out.println("p4 " + df.format(0.001));
    System.out.println();
  }
}

Result
p1 0.90%

p2 #: 1,955

p3 1.234E3
p3 123.46E3
p3 1.23E-3
p3 1E-3

p4 123.40E1
p4 123.46E3
p4 123.00E-5
p4 100.00E-5
So, pattern1 formats 0.009 to percent by multiplying by 100 and the result is 0.90%. Pattern2 uses the single quote('). The purpose of this character is to format special characters of DecimalFormat to literally literals; disregarding their special meanings.

Pattern3 and pattern4 formats numbers to scientific notation. Pattern3 "##0.##E0" formats 1234 to 1.234E3. Let's discuss how java yielded the result. First off, let's examine the pattern "##0.##E0". Let's focus on the decimal integer part "##0".

According to DecimalFormat's documentation: "If the maximum number of integer digits is greater than their minimum number and greater than 1, it forces the exponent to be a multiple of the maximum number of integer digits, and the minimum number of integer digits to be interpreted as 1"

In my intuition, "0" denotes the minimum integer or decimal digits. The number of "#" and "0" in the pattern denotes that maximum integer or decimal digits. So, the pattern "##0.##E0" has a minimum of one and a maximum of 3 decimal integers. For the decimal part, the pattern doesn't have minimum and a has maximum of 2 decimal numbers.

In pattern3, maximum integer digits are graater than minimum integer digits. So, the given digits 1234 is formatted 1.234E3. As you can see, the exponent is multiple of 3 which is the maximum integer digits. We can also see the other results that are formatted by pattern3 have exponents that are multiple of 3.

In patern4, the exponents of the results that are formatted by pattern4 are not the multiple of maximum integer digits because the maximum integer digits are not greater than the minimum. Instead, they are equal. So, the quoted statement above is not applicable to pattern4.

In pattern4, "0" is replaced with with non-zero digits that are supplied by the given digits. If the given digits are not enough to supply the minimum integer or decimal digits, zeroes will be substituted to meet the minimum digit requirement.

Changing Grouping Separator Symbols of Numbers

We can change the decimal and grouping sepators of a DecimaFormat instance by using the DecimalFormatSymbols class.
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
public class SampleClass{

  public static void main(String[]args){
    DecimalFormatSymbols dfs = new DecimalFormatSymbols();
    
    //This method sets new character as a decimal point
    dfs.setDecimalSeparator('|');
    
    //This method sets new character as a grouping
    //separator. In US and some other countries,
    //comma(,) is used as grouping separator.
    dfs.setGroupingSeparator('-');
    
    String pattern = "#,##0.00";
    
    //We use the third form of DecimalFormat's constructor
    DecimalFormat df = new DecimalFormat(pattern, dfs);
    
    String result = df.format(1234567);
    System.out.println(result);
  }
}

Result
1-234-567|00
Check the DecimalFormatSymbols documentation for more information.

Changing DecimalFormat's Locale

We can use NumberFormat's getInstance() or getNumberInstance() method to get a NumberFormat type with a specified locale that we want.
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.util.Locale;
public class SampleClass{

  public static void main(String[] args){
    NumberFormat nf = NumberFormat.
                      getInstance(Locale.ITALY);
    
    if(nf instanceof DecimalFormat){
      DecimalFormat df = (DecimalFormat)nf;
      
      //This method uses the notation of the
      //default locale of our platform.
      //Though, Symbols like decimal and 
      //grouping separators in the 
      //formatted result are going to be
      //based on a specified locale, if
      //there's one.
      df.applyPattern("#,###,##0.00");
      
      //This method uses the localized notation.
      //In other words, the locale that we 
      //specified when we invoked getInstace()
      //If there's no specified locale, the
      //default locale is used
      //df.applyLocalizedPattern("#.###.##0,00");
      System.out.println(df.format(1234567));
    }
  }
}

Result
1.234.567,00
Note: DecimalFormat inherits the parse() method in NumberFormat so, we can still do parsing in DecimalFormat.

MessageFormat Class

MessageFormat provides a means to produce concatenated messages in a language-neutral way. Unlike other Format classes such as NumberFormat and DecimalFormat, this class doesn't have getInstance factory methods.

A format pattern of MessageFormat consists of String and Format Element. A Format element can be one of these forms:
{ ArgumentIndex }
{ ArgumentIndex , FormatType }
{ ArgumentIndex , FormatType , FormatStyle }
ArgumentIndex is an index that is used for mapping the String to be formatted. FormatType defines the type of the format. FormatStyle defines the styles of the format. Values of FormatType and FormatStyle can be found in the documentation.

Note: MessageFormat differs from the other Format classes in that you create a MessageFormat object with one of its constructors (not with a getInstance style factory method). The factory methods aren't necessary because MessageFormat itself doesn't implement locale specific behavior. Any locale specific behavior is defined by the pattern that you provide as well as the subformats used for inserted arguments.

This means that MessageFormat doesn't have any implementations that handles locale specific behavior. Instead, it delagates the formatting to implementations with locale specific behavior from Format classes. For example, you instantiate a MessageFormat with Locale.US and then format a string using this pattern "{0, number, percent}".

If that's the case then MessageFormat will invoke NumberFormat.getPercentInstance(getLocale()) and delegate the formatting to it.

This example demonstrates MessageFormat.
import java.text.MessageFormat;
import java.util.Date;
import java.util.Locale;

public class SampleClass{

  public static void main(String[] args){
    String message = 
    "My {0} increased in {1, date} {1, time} by "+
    "{2, number, percent}.";
    
    MessageFormat mf = 
    new MessageFormat(message, Locale.US);
    
    System.out.println(mf.format(
    new Object[]{"salary",new Date(), .15}));
  }
}

Result
My salary increased in May 20, 2022 5:12:09 PM by 15%.
In the example above, objects are converted to String objects. MessageFormat(String pattern, Locale locale) constructs a MessageFormat instance with the specified pattern and locale.

format(Object obj) is a method from Format class that formats an object to produce a string with the specified object. This method prefers an Object array as its argument.

If you want to quickly format a pattern without locale, you can use format(String pattern, Object... arguments) static method. For example:
...
String message = 
"My {0} increased in {1, date} {1, time} by "+
"{2, number, percent}.";

System.out.println(MessageFormat.format
(message, "salary", new Date(), .15));
...
We can also use MessageFormat to convert String objects to objects. Take a look at this example.
import java.text.MessageFormat;
import java.text.ParsePosition;
import java.text.ParseException;

public class SampleClass{

  public static void main(String[] args)
                     throws ParseException{
    String pattern = "{0} happened in {1, date} in my {2}.";
    String message = "Olympics happened in May 20, 2012 in my country.";
    
    MessageFormat mf = new MessageFormat(pattern);
    
    Object obj[] = mf.parse(message, new ParsePosition(0));
    
    for(Object o : obj)
      System.out.println(o);
  }
}

Result
Olympics
Sun May 20 00:00:00 CST 2012
country
parse(String source, ParsePosition pos) parses the source starting from the specified ParsePosition. The index that is used by ParsePosition is based on the ArgumentIndex in pair of brackets.

Take note that the message that is going to be parsed must have the characters of the pattern that are not in pair of brackets({}). Otherwise, parse method will return null. More information about this method can be found in this documentation.

Brackets have special meaning in MessageFormat. If we want to denote a bracket as a literal in a pattern, we need to enclose it within single quotes. Take a look at this example.
import java.text.MessageFormat;
import java.util.Locale;

public class SampleClass{

  public static void main(String[] args){
    String message = 
    "''Objects'': '{'{0}, {1}, {2}'}'";
    
    MessageFormat mf = 
    new MessageFormat(message, Locale.US);
    
    System.out.println(mf.format(
    new Object[]
    {"Object1","Object2","Object3"}));
  }
}

Result
'Objects': {Object1, Object2, Object3}
To denote a single quote as a literal in a pattern, use double single quotes just like in the example above.

In the FormatStyle of FormatElement, we can use a pattern that is used by other Format classes. This pattern in the FormatElement is called SubformatPattern. Take a look at this example.
import java.text.MessageFormat;
import java.util.Locale;
import java.util.Date;

public class SampleClass{

  public static void main(String[] args){
    String message = 
    "Date: {0, date, dd-MM-yyyy}\n" +
    "Number: {1, number, #,###,###}";
    
    MessageFormat mf = 
    new MessageFormat(message);
    
    System.out.println(
    mf.format(new Object[]{new Date(), 1234567}));
  }
}

Result
Date: 20-05-2022
Number: 1,234,567
"dd-MM-yyyy" pattern comes from SimpleDateFormat. "#,###,###" pattern comes from DecimalFormat


ChoiceFormat Class

A ChoiceFormat allows you to attach a format to a range of numbers. It is generally used in a MessageFormat for handling plurals. The choice is specified with an ascending list of doubles, where each item specifies a half-open interval up to the next item:
X matches j if and only if limit[j] ≤ X < limit[j+1]
If there is no match, then either the first or last index is used, depending on whether the number (X) is too low or too high. If the limit array is not in ascending order, the results of formatting will be incorrect. ChoiceFormat also accepts \u221E as equivalent to infinity(INF).

This example demonstrates simple usage of ChoiceFormat.
import java.text.ChoiceFormat;

public class SampleClass{

  public static void main(String[] args){
  
    double[] limits = {1, 10, 100, 1000};
    String[] formats = 
    {"Ones", "Tens", 
     "Hundredths", "Thousandths"};
    
    ChoiceFormat cf = 
    new ChoiceFormat(limits, formats);
    
    System.out.println(cf.format
    (Double.NEGATIVE_INFINITY));
    System.out.println(cf.format(1));
    System.out.println(cf.format(10));
    System.out.println(cf.format(100));
    System.out.println(cf.format(150));
    System.out.println(cf.format(160));
    System.out.println(cf.format(1000));
    System.out.println(cf.format
    (Double.POSITIVE_INFINITY));
  }
}

Result
Ones
Ones
Hundredths
Hundredths
Hundredths
Thousandths
Thousandths
ChoiceFormat(double[] limits, String[] formats) constructs with the limits and the corresponding formats. Remember that the length of limits must be the same as the length of formats. Take note that Double.NEGATIVE_INFINITY and Double.POSITIVE_INFINITY are not real numbers.

ChoiceFormat is generally used in conjunction with MessageFormat. Take a look at this example.
import java.text.ChoiceFormat;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.Format;
import java.util.Locale;

public class SampleClass{

  public static void main(String[] args){
    double[] limits = {.25, .5, .9};
    String[] formats = 
    {"Low", "Average", "Above Average"};
    
    ChoiceFormat cf = 
    new ChoiceFormat(limits, formats);
    
    MessageFormat mf = 
    new MessageFormat
    ("{0} is {0}.", Locale.US);
    
    Format[] mfFormats = {
    NumberFormat.getPercentInstance(Locale.US), cf};
    mf.setFormats(mfFormats);
    
    System.out.println(mf.format(new Object[]{.25}));
    System.out.println(mf.format(new Object[]{.2}));
    System.out.println(mf.format(new Object[]{.5}));
    System.out.println(mf.format(new Object[]{.9}));
    System.out.println(mf.format(new Object[]{.99}));
  }
}

Result
25% is Low.
20% is Low.
50% is Average.
90% is Above Average.
99% is Above Average.
setFormats(Format[] newFormats) sets the formats to use for the format elements in the pattern of MessageFormat instance. The order of formats in newFormats corresponds to the order of format elements in the pattern string. In the example above NumberFormat.getPercentInstance formats the first "{0}" argument index in the pattern.

Then, cf formats the second "{0}" argument index. More information can be found in the documentation.

We can create a pattern for ChoiceFormat. These are the available symbols that we can use in the pattern.
import java.text.ChoiceFormat;

public class SampleClass{

  public static void main(String[] args){
    ChoiceFormat cf = new ChoiceFormat(
    "-1#is negative| 0#is zero or fraction "+
    "| 1#is one |1.0<is 1+ |2#is two "+
    "|2<is more than 2.");
    
    System.out.println(cf.format(-1));
    System.out.println(cf.format(1.5));
    System.out.println(cf.format(0.9));
    System.out.println(cf.format(1));
    
  }
}

Result
is negative
is 1+
is zero or fraction
is one
Let's examine the pattern and I'm gonna explain the pattern symbols. "|" is an OR symbol. We put this symbol after we're done writing a limit and format. OR symbol functions as an OR operator. "#" and "<" symbols are limiter symbols. "#" is typically used to create a limit. "<" is typically used to create a limit in-between limits created by "#". For example:
import java.text.ChoiceFormat;

public class SampleClass{

  public static void main(String[] args){
    ChoiceFormat cf = new ChoiceFormat(
    "| 1#is one |1.5<is 1+ |2#is two "+
    "|2<is more than 2.");
    
    System.out.println(cf.format(1));
    System.out.println(cf.format(1.4));
    System.out.println(cf.format(1.5));
    System.out.println(cf.format(1.9));
    System.out.println(cf.format(2));
    
  }
}

Result
is one
is one
is one
is 1+
is two
"\u2264" or "≤" symbol is similar to "<" symbol. However, "≤" symbol is inclusive. It means that the specified number after the symbol is included in the limit. For example:
import java.text.ChoiceFormat;

public class SampleClass{

  public static void main(String[] args){
    ChoiceFormat cf = new ChoiceFormat(
    "| 1#is one |1.5\u2264is 1+ |2#is two "+
    "|2<is more than 2.");
    
    System.out.println(cf.format(1));
    System.out.println(cf.format(1.4));
    System.out.println(cf.format(1.5));
    System.out.println(cf.format(1.9));
    System.out.println(cf.format(2));
    
  }
}

Result
is one
is one
is 1+
is 1+
is two
We can also parse a limit number from an input text using parse method. Take a look at this example.
import java.text.ChoiceFormat;
import java.text.ParsePosition;

public class SampleClass{

  public static void main(String[] args){
    ChoiceFormat cf = new ChoiceFormat(
    "| 1#is one |1.5<is 1+ |2#is two "+
    "|2<is more than 2.");
    
    System.out.println(cf.parse(
    "is one ", new ParsePosition(0)));
    
  }
}

Result
1.0
Make sure that the string in the parse method matches the string, excluding the limit number and symbol, in one of the choices in the ChoiceFormat.

Formatting Date&Time

We can format date/time by using DateFormat or SimpleDateFormat class. These two classes have distinct features that we can use in certain situations.

DateFormat Class

DateFormat class is an abstract class that provides methods and fields which help us to format date/time. DateFormat has fields that control the format of the formatted result. These fields are called "Style Patterns".
  • SHORT is completely numeric, such as 12.13.52 or 3:30pm
  • MEDIUM is longer, such as Jan 12, 1952
  • LONG is longer, such as January 12, 1952 or 3:30:32pm
  • FULL is pretty completely specified, such as Tuesday, April 12, 1952 AD or 3:30:42pm PST.
import java.util.Date;
import java.util.Locale;
import java.text.DateFormat;
import java.text.ParseException;
public class SampleClass{

  public static void main(String[]args){
    //This Date constroctor allocates
    //a Date object and initializes it
    //so that it represents the time at
    //which it was allocated, measured
    //to the nearest millisecond
    Date date = new Date();
    
    System.out.println("Date/Time Default Format: "+
                       date.toString());
    
    //getInstance() gets a default date/time
    //formatter that uses the SHORT style for
    //both the date and the time.
    DateFormat df = DateFormat.getInstance();
    System.out.println("getInstance(): " + df.format(date));
    
    //getDateInstance() gets the date formatter
    //with the default formatting style for the
    //default FORMAT locale. 
    df = DateFormat.getDateInstance();
    System.out.println("getDateInstance(): "+
                       df.format(date));
    
    //This form of getDateInstance() gets the time
    //formatter with the given formatting style
    //for the given locale.
    df = DateFormat.getDateInstance(DateFormat.LONG,
                                    Locale.US);
    System.out.println("getDateInstance(): "+
                       df.format(date));
    
    //getTimeInstance() gets the time formatter
    //with the default formatting style for the
    //default FORMAT locale. 
    df = DateFormat.getTimeInstance();
    System.out.println("getTimeInstance(): "+
                       df.format(date));
    
    //getDateTimeInstance() gets the date/time
    //formatter with the default formatting style
    //for the default FORMAT locale.
    df = DateFormat.getDateTimeInstance();
    System.out.println("getDateTimeInstance(): "+
                       df.format(date));
                       
    try{
      df = DateFormat.getDateInstance(DateFormat.LONG,
                                      Locale.US);
      
      //parse() Parses text from the beginning of
      //the given string to produce a date.
      //The method may not use the entire text
      //of the given string due to some reasons
      //like for example, some characters in the
      //given string can't be parsed
      Date pDate = df.parse("September 1, 1994");
      System.out.println(pDate.toString());
    }catch(ParseException e){
      e.printStackTrace();
    }
  }
}

Note
Some parts of the results below are based on the
system's current date/time where
the example is executed
Result
Date/Time Default Format: Thu Jul 29 07:02:31 CST 2021
getInstance(): 7/29/21, 7:02 AM
getDateInstance(): Jul 29, 2021
getDateInstance(): July 29, 2021
getTimeInstance(): 7:02:31 AM
getDateTimeInstance(): Jul 29, 2021, 7:02:31 AM
Thu Sep 01 00:00:00 CST 1994
SimpleDateFormat Class

SimpleDateFormat class is a concrete class that can format date/time by using a pattern. Though, methods like getTimeInstance(), getDateInstance(), or getDateTimeInstance() and parse() can still be used here.
import java.text.SimpleDateFormat;
import java.util.Date;
public class SampleClass{

  public static void main(String[]args){
    String pattern1 = "MM/dd/yyyy hh:mm:ss a";
    String pattern2 = "hh:mm:ss a | MM-dd-yy";
    String pattern3 = "'hh': hh";
    Date date = new Date();
    SimpleDateFormat sdf =
    new SimpleDateFormat(pattern1);
    
    String result = sdf.format(date);
    System.out.println("Pattern1: " + result);
    
    sdf = new SimpleDateFormat(pattern2);
    result = sdf.format(date);
    System.out.println("Pattern2: " + result);
    
    sdf = new SimpleDateFormat(pattern3);
    result = sdf.format(date);
    System.out.println("Pattern3: " + result);
  }
}

Note
the results below are based on the
system's current date/time where
the example is executed
Result
Pattern1: 07/29/2021 11:34:51 AM
Pattern2: 11:34:51 AM | 07-29-21
Pattern3: hh: 11
First off, let's examine pattern1. Some letters have special meanings in SimpleDateFormat's pattern. For example, "M" represents month in year(context sensitive), "m" represents minute in hour, "s" represents second in minute, etc.

We can see in the patterns above that some letters are repeated. We repeat letters in order to set the minimum number that those letters represent. For example, "mm" denotes seconds with minimum of two digits. If the given numberis less than the minimum number, additional zero will be added to meet the minimum number. For example, if the given second is "1" it will be formatted to "01".

Some characters have special formats when repeated. For example, "y", which represents year, has different formats when repeated two or four times. For example, the given year is "2021". "yy" formats "2021" to "21", "yyyy" formats "2021" to "2021".

Another examples are the "z","Z" and "X". "z","zz" and "zzz" formats general timezone to an abbreviation. For example, "z" formats "Central Standard Time" to "CST". "zzzz" formats general time zone to its full name. Any repetitions of "z"'s that are more than four have the same result as "zzzz". "Z" formats timezone using "RFC 822 4-digit time zone format". Any repetitions of "Z"'s that are more than two have the same result as "Z".

"X" formats timezone using "ISO 8601 Time zone format". "X" can be repeated up to 3 times. each repitition increases the characters of the formatted timezone. An exception will be thrown if "X" is repeated more than 4 times.

Letters from A-Z(case insensitive) are reserved for SimpleDateFormat. It means, we can't directly use them as literals in SimpleDateFormat's pattern. Though, there's a way to use letters as literals in the pattern. Use the single quote(') and enclose the string that you wanna be used as literals. Pattern3 demonstrated this concept.

Check the documentation of SimpleDateFormat for more information.

DateTimeFormatter Class

DateTimeFormatter is a class used for formatting date and time in some date-time-related classes in java.time like LocalDate, LocalDateTime, etc. This formatter has predefined constants that contain predefined formats. Additionally, this formatter allows patterns and localized styles.

This topic assumes that readers are knowledgeable about java.time package. If you're not familiar with java.time package, you may wanna read this blogpost I created before reading this topic. This example demonstrates DateTimeFormatter class.
import java.time.*;
import java.time.format.*;
import java.util.Locale;

public class SampleClass{

  public static void main(String[] args){
    LocalDateTime date = 
    LocalDateTime.of(2021, 9, 20, 16, 35, 25);
    
    System.out.println("LocalDate Default Format");
    System.out.println(date);
    System.out.println("With ISO_WEEK_DATE Format");
    System.out.println(
    date.format(DateTimeFormatter.ISO_WEEK_DATE));
    System.out.println();
    
    System.out.println("Using Pattern");
    System.out.println(date.format(
    DateTimeFormatter.
    ofPattern("MM/dd/uu hh:mm:ss", Locale.US)));
    System.out.println();
    
    System.out.println("Using Localized Style");
    DateTimeFormatter dtf = 
    DateTimeFormatter.ofLocalizedDateTime(
    FormatStyle.MEDIUM).withLocale(Locale.US);
    System.out.println("Style #1: " + date.format(dtf));
    
    dtf = DateTimeFormatter.ofLocalizedDateTime(
    FormatStyle.LONG, FormatStyle.MEDIUM).
    withLocale(Locale.US);
    System.out.println("Style #2: " + 
    date.format(dtf));
    
  }
}

Result
LocalDate Default Format
2020-09-20T16:35:25
With ISO_WEEK_DATE Format
2020-W38-1

Using Pattern
09/20/21 04:35:25

Using Localized Style
Style #1: Sep 20, 2021, 4:35:25 PM
Style #2: September 20, 2021, 4:35:25 PM
First off, format method can be found in some date-time-related classes in java.time like LocalDate, LocalDateTime, etc. format method returns a formatted text of date and time information of a date-time-related class.

DateTimeFormatter has predefined constants that we can use. Most of these constants are used by some date-time-related classes. For example, LocalDateTime.toString uses DateTimeFormatter.ISO_LOCAL_DATE_TIME as its default formatter. More information about constant formatters can be found in the documentation.

DateTimeFormatter can use patterns as formatters. There are characters that have special meaning in a pattern like 'm', 'h', 'M', etc. DateTimeFormater.ofPattern is used to create a formatter via pattern. Some characters have the same meaning but different presentation.

For example, "M" and "L" characters are the symbols for month-of-year. However, "M" presents month-of-year as a number e.g. 7 or 07. "L" presents month-of-year as a text e.g. Jul or July. More information about pattern and characters with special meaning can be found in the documentation.

Also, don't be confused with year and year-of-era. year-of-era represents the concept of the year within the era. There are two eras: BC(Before Christ) or BCE(Before Common Era) and AD(Anno Domini) or CE(Common Era).

CE starts at 1 and moves forward as moving forward to the present time. BCE starts at 1 and moves backward as moving forward to the past time. year, starting at 1, is equivalent to years in CE. Year 0 is equal to 1 BCE. Proleptic year is commonly used when handling standard dates. year-of-era is commonly used in historical timeline.

methods with 'ofLocalized' prefix returns a locale specific date format for the ISO chronology. In the example above I used two overloaded forms of ofLocalizedDateTime:

ofLocalizedDate(FormatStyle dateStyle)
ofLocalizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle)

The first form uses dateStyle to format both date and time. The second form uses dateStyle to format date and timeStyle to format time. FormatStyle is an enumeration of the style of a localized date, time or date-time formatter.

withLocale returns a DateTimeFormatter instance with the given locale. Note that the localized pattern is looked up lazily. In the example above, DateTimeFormatter holds FormatStyle given by method with 'ofLocalized' prefix and the locale given by withLocale.

Actual formatting happens if the formatted text is requested. If withLocale is not invoked, your system's default locale is used. If the locale you're using has Unicode Extensions, you may wanna use localizedBy().

Take note that format method may throw an exception if the given FormatStyle can't find necessary information it needs. For example, FormatStyle.FULL may throw an exception when used as formatter in LocalDateTime because FormatStyle.FULL may look for offset and zone id which LocalDateTime don't have.

Parsing

Next, we can use DateTimeFormatter to parse text based on its formatter. This example demonstrates DateTimeFormatter.parse and its overloaded forms.
import java.text.ParsePosition;
import java.time.*;
import java.time.format.*;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalQueries;
import java.util.Locale;

public class SampleClass{

  public static void main(String[] args){
    
    TemporalAccessor ta = DateTimeFormatter.
    ofPattern("MM/dd/uu hh:mm:ss", Locale.US).
    parse("09/20/21 04:35:25");
    
    if(ta.isSupported(ChronoField.DAY_OF_MONTH))
      System.out.println("Day of month: "+
      ta.get(ChronoField.DAY_OF_MONTH));
    
    ta = DateTimeFormatter.
    ofPattern("hh:mm:ss", Locale.US).
    parse("09/20/21 04:35:25", new ParsePosition(9));
    
    System.out.println("Hour of AM/PM: " + 
    ta.get(ChronoField.HOUR_OF_AMPM));
    System.out.println("Minutes of hour: " + 
    ta.get(ChronoField.MINUTE_OF_HOUR));
    System.out.println("Seconds of minute: " + 
    ta.get(ChronoField.SECOND_OF_MINUTE));
    
    LocalDate ld = DateTimeFormatter.
    ofPattern("MM/dd/uu hh:mm:ss", Locale.US).
    parse("09/20/21 04:35:25", 
          TemporalQueries.localDate());
    System.out.println("LocalDate: " + ld);
  }
}

Result
Day of month: 20
Hour of AM/PM: 4
Minutes of hour: 35
Seconds of minute: 25
LocalDate: 2021-09-20
parse has three overloaded forms:
parse(CharSequence text)
parse(CharSequence text, ParsePosition position)
parse(CharSequence text, TemporalQuery<T> query)

The first form has a text parameter that contains the text to be parsed. This form returns a TemporalAccessor instance.

isSupported returns true if a TemporalField instance is supported by the instance that calls this method. Otherwise, returns false. ChronoField implements TemporalField thus its constant fields are compatible with TemporalField.

The second form has a text parameter that contains the text to be parsed and position parameter that contains the position or index of where the parsing starts. ParsePosition is a simple class used by Format and its subclasses to keep track of the current position during parsing.

The third form has a text parameter that contains the text to be parsed and a query that contains a query command. TemporalQuery is a functional interface used for querying a temporal object. Queries are a key tool for extracting information from temporal objects. TemporalQueries provides common implementations of TemporalQuery.

Resolving

Parsing is implemented as a two-phase operation. First, the text is parsed using the layout defined by the formatter, producing a Map of field to value, a ZoneId and a Chronology. Second, the parsed data is resolved, by validating, combining and simplifying the various fields into more useful ones. More information can be found in the documentation.

In this topic, we're gonna be discussing withResolverFields and withResolverStyle methods. This example demonstrates the two methods.
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;

public class SampleClass{

  public static void main(String[] args){
    TemporalAccessor ta = DateTimeFormatter.
    ofPattern("MM/dd/uu DDD", Locale.US).
    withResolverFields(ChronoField.YEAR, 
                       ChronoField.DAY_OF_YEAR).
    withResolverStyle(ResolverStyle.LENIENT).
    parse("09/20/21 489");
    
    System.out.println(ta);
  }
}

Result
{}, ISO resolved to 2022-05-04
withResolverFields acts as a filter between phase 1 and phase 2 of parsing. The fields in withResolverFields are the fields that are going to phase 2. If the fields are possible to be resolved to a date, the new date will form. In the example above, the year(2021) and day-of-year(489) are extracted, resolved and form the new date(2022-05-04).

This is valid because in java, a combination of YEAR and DAY_OF_YEAR can be resolved and consequently produces a date. More information on how java resolves fields can be found in this documentation.

withResolverStyle sets a new resolve style. There are three resolver styles:

LENIENT
SMART
STRICT

These constants can be found in the ResolverStyle enum. Lenient mode is the least strict mode of the resolver styles. It accepts out-of-range values. For example, lenient mode allows the month in the ISO calendar system to be outside the range 1 to 12. For example, month 15 is treated as being 3 months after month 12.

Strict mode is the most strict mode of the resolver styles. all field values must not be out-of-range, otherwise, it will be treated as invalid value. For example, month 15 is invalid because it's outside of the range 1 to 12. In the example above, day-of-year is outside the range 1 to 365 in standard years and 1 to 366 in leap years. However, the resolver mode is lenient that's why 489 is still a valid value. Try changing the resolver mode to strict mode and you will encounter an exception.

Smart mode is a mix between lenient and strict mode with some modifications. Some fields may accept out-of-range values, some may not and the resolving process of this mode may be different from lenient and strict modes. For example, let's parse 2019-03-32 using the resolver modes. In lenient mode, the result is 2019-04-01. As you can see the month moved to 4 and the day is set to 1.

In strict mode, an exception is thrown because 32 is outside the range 1 to 31 of month of march of ISO calendar. In smart mode, the result is 2019-03-01. As you can see, the month didn't move and the day is reset to 1. Take note the the smart mode is the default mode of DateTimeFormatter class.

DateTimeFormatterBuilder

DateTimeFormatterBuilder allows a DateTimeFormatter to be created. All DateTimeFormatter are created ultimately using this builder. This example demonstrates some methods of DateTimeFormatterBuilder.
import java.time.*;
import java.time.format.*;
import java.time.temporal.ChronoField;

public class SampleClass{

  public static void main(String[] args){
    DateTimeFormatterBuilder builder =
    new DateTimeFormatterBuilder();
    
    DateTimeFormatter formatter = 
    builder.
    optionalStart().
    appendLiteral("Date: ").
    optionalEnd().
    appendValue(ChronoField.YEAR).
    appendLiteral("/").
    appendValue(ChronoField.MONTH_OF_YEAR).
    appendLiteral("/").
    appendValue(ChronoField.DAY_OF_MONTH, 2).
    parseStrict().
    toFormatter(java.util.Locale.US);
    
    LocalDate ld = LocalDate.of(2020, 4, 5);
    
    System.out.println("Formatting");
    System.out.println(ld.format(formatter));
    System.out.println();
    
    System.out.println("Parsing");
    ld = LocalDate.parse("2015/10/02", 
                         formatter);
    System.out.println("Date: " + ld);
  }
}

Result
Formatting
Date: 2020/4/05

Parsing
Date: 2015-10-02
appendLiteral(String literal) appends plain text to the formatter. appendValue(TemporalField field) appends the value of a date-time field to the formatter using a normal output style.

appendValue(TemporalField field, int width) appends the value of a date-time field to the formatter with fixed width. If the value of the date-time field is less than the fixed width, zeroes will be added to compensate.

parseStrict changes the parse style to strict mode for the remainder of the formatter. optionalStart marks the start of an optional section and optionalEnd ends the optional section. All elements in the optional section are treated as optional that may affect formatting or parsing.

In the example above, the literal "Date: " without quotes will be output during formatting. During parsing, the literal "Date: " without quotes doesn't need to be an input. Another example, consider this builder setup:

optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()

During formatting, the minute will only be output if its value can be obtained from the date-time. During parsing, the input will be successfully parsed whether the minute is present or not.

toFormatter(Locale locale) completes this builder by creating the DateTimeFormatter using the specified locale. Let's try another example.
import java.time.*;
import java.time.format.*;
import java.time.temporal.ChronoField;

public class SampleClass{

  public static void main(String[] args){
    DateTimeFormatterBuilder builder =
    new DateTimeFormatterBuilder();
    
    DateTimeFormatter formatter =
    builder.
    appendPattern("[yyyy][yyyyMMdd]").
    optionalStart().
    parseDefaulting(
      ChronoField.MONTH_OF_YEAR, 1).
    parseDefaulting(
      ChronoField.DAY_OF_MONTH, 1).
    optionalEnd().
    toFormatter(java.util.Locale.US);
    
    System.out.println("Parsing");
    LocalDate ld = LocalDate.parse(
                   "2015", 
                    formatter);
    System.out.println("Date: " + ld);
    
  }
}

Parsing
Date: 2015-01-01
parseDefaulting appends a default value for a field to the formatter for use in parsing. As the name implies, this method affects the parsing process of the formatter. In the example above, we only put year in the parse method. Because there are no day and month in the parse method, the formatter uses the values assigned in the parseDefaulting method.

appendPattern method appends the elements defined by the specified pattern to the builder. The "[" and "]" characters are similar to optionalStart and optionalEnd methods.

This pattern "[yyyy][yyyyMMdd]" accepts four-digit year or four-digit year, two-digit month and two-digit day. More details about reserved characters for date-time patterns can be found in this documentation.

Let's do one more example.
import java.time.*;
import java.time.format.*;
import java.time.temporal.ChronoField;

public class SampleClass{

  public static void main(String[] args){
    DateTimeFormatterBuilder builder =
    new DateTimeFormatterBuilder();
    
    DateTimeFormatter formatter =
    builder.
    appendText(
      ChronoField.MONTH_OF_YEAR,
      TextStyle.SHORT_STANDALONE).
    appendLiteral(" ").
    appendValue(ChronoField.DAY_OF_MONTH).
    appendLiteral(", ").
    appendValueReduced(ChronoField.YEAR, 
                       2, 2, 
                       LocalDate.of(1940,1,1)).
    toFormatter(java.util.Locale.US);
    
    LocalDate ld = LocalDate.of(1990, 5, 10);
    System.out.println("Formatting");
    System.out.println(ld.format(formatter));
    System.out.println();
    
    System.out.println("Parsing");
    ld = LocalDate.parse("Feb 20, 90", 
                         formatter);
    System.out.println(ld);
  }
}

Result

Formatting
May 10, 90

Parsing
1990-02-20
appendText(TemporalField field, TextStyle textStyle) appends the text of a date-time field to the formatter. TextStyle is an enumeration of the style of text formatting and parsing.

appendValueReduced appends the reduced value of a date-time field to the formatter. This method is typically used for formatting and parsing a two-digit year. When formatting or parsing a year such as YEAR or YEAR_OR_ERA, It's recommended to use this form:
appendValueReduced(TemporalField field, int width, int maxWidth, ChronoLocalDate baseDate)
For other fields, use this form:
appendValueReduced(TemporalField field, int width, int maxWidth, int baseValue)

field is a date-time field that we wanna format or parse. width and maxWidth determines the number of characters to be parsed or formatted. baseDate or baseValue determines the range of where to get the full value of reduced value.

In my opinion, to get the number of digits of full value of reduced value with two or more digits, full value digits should be two digits higher than the reduced value digits. For example, the number of digits of full value of reduced value with two digits is 4 digits. To get the full value range, add 9*10 to the given full value if the reduced value has two digits, 9*100 is the reduced value has three digits and so on.

For example, the given full value is 1940 and the reduced value is two digits such as 40. Add 9*10 to 1940 and we get 2030. Thus, full value range starts from 1940 to 2030. In the example above, appendValueReduced only use the YEAR field. Fields like MONT_OF_YEAR and DAY_OF_MONTH in LocalDate are not used.

During formatting, width and maxWidth determines the number of characters to format. If they are equal then the format is fixed width. If the value of the given field is within the full value range then the reduced value is formatted. If the value of the given field is out-of-range and number of digits is greater than maxWidth, the value of the given field is truncated to fit maxWidth. The rightmost characters are output to match the value of the ginve field, left padding with zero.

Examples:
#1: Given field value is out-of-range and number of digits is greater than maxWidth.
...
appendValueReduced(ChronoField.YEAR, 
                   2, 2, 
                   LocalDate.of(1940,1,1))
...
LocalDate ld = LocalDate.of(2040, 5, 10);
System.out.println(ld.format(formatter));

Result: May 10, 40

...
appendValueReduced(ChronoField.YEAR, 
                   2, 2, 
                   LocalDate.of(1940,1,1))
...
LocalDate ld = LocalDate.of(1910, 5, 10);
System.out.println(ld.format(formatter));

Result: May 10, 10
#2: Given field value is within full value range.
...
appendValueReduced(ChronoField.YEAR, 
                   2, 2, 
                   LocalDate.of(1940,1,1))
...
LocalDate ld = LocalDate.of(2020, 5, 10);
System.out.println(ld.format(formatter));

Result: May 10, 20
#3: The rightmost characters are output to match the value of the ginve field, left padding with zero.
...
appendValueReduced(ChronoField.YEAR, 
                   2, 2, 
                   LocalDate.of(1940,1,1))
...
LocalDate ld = LocalDate.of(9, 5, 10);
System.out.println(ld.format(formatter));

Result: May 10, 09
#4: Given field value is out-of-range and number of digits is equal or less than maxWidth.
...
appendValueReduced(ChronoField.YEAR, 
                   2, 4, 
                   LocalDate.of(1940,1,1))
...
LocalDate ld = LocalDate.of(1900, 5, 10);
System.out.println(ld.format(formatter));

Result: May 10, 1900
During parsing, the created DateTimeFormatter gets the value; check if the value is within width and maxWidth; and check if the value has a full value match in the full value range.

Examples:
#1: parse reduced year value to full year value.
...
appendValueReduced(ChronoField.YEAR, 
                   2, 2, 
                   LocalDate.of(1940,1,1))
...
LocalDate ld = LocalDate.
               parse("Feb 20, 90", 
                     formatter);
System.out.println(ld);

Result: 1990-02-20
#2: given value can't be matched with full value range.
...
appendValueReduced(ChronoField.YEAR, 
                   2, 4, 
                   LocalDate.of(1940,1,1))
...
LocalDate ld = LocalDate.
               parse("Feb 20, 1900", 
                     formatter);
System.out.println(ld);

Result: 1900-02-20

Tuesday, July 20, 2021

Java Tutorial: Scanner Class

Chapters

Scanner Class

Scanner class is a simple text scanner that can parse primitive types and string. In other words, scanner can break down primitive types and string into multiple parts called tokens. By default, scanner uses whitespace characters like "\n", "\t" and the good ol' whitespace(" ") as delimiter to separate primitive types and string.

Tokens in this topic means, parts of the input that are separated by delimiter.

Delimiter is a sequence of one or more characters for specifying the boundary between separate, independent regions in plain text, mathematical expressions or other data streams.

Check out Character.isWhiteSpace() to know the different kinds of whitespaces. Scanner can scan inputstreams, String or a text file. Let's use Scanner with Strings.

Scan String Input Using Scanner Class
import java.util.Scanner;
public class SampleClass{

  public static void main(String[]args){
    Scanner scanner = null;
    
    try{
      scanner = new Scanner("A Big Brown Fox");
    
      //hasNext() returns true if the
      //scanner has another token in
      //its input
      while(scanner.hasNext()){
      
        //next() returns the next token
        //in this scanner as a string type.
        //To understand the function of this
        //method, imagine the tokens are
        //stored in an array, and next()
        //iterates through that array
        //until it reaches the last index
        //of the array.
        System.out.println(scanner.next());
      }
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
      //Close the scanner once
      //you're done using it
      if(scanner != null)
        scanner.close();
    }
    
  }
}

Result
A
Big
Brown
Fox
Methods Similar To next()

If we have numbers as input, we can use the nextInt() if the numbers are integer, nextFloat() if the numbers are floating-point, etc.
import java.util.Scanner;
public class SampleClass{

  public static void main(String[]args){
    Scanner scanner = null;
    
    try{
    
      scanner = new Scanner("10 100 1000 2000 50000");
     
      //hasNextInt() checks if the current token
      //matches an integer type
      //
      //nextInt() returns the current token as
      //integer type
      while(scanner.hasNextInt())
        System.out.println(scanner.nextInt());
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
      //Close the scanner once
      //you're done using it
      if(scanner != null)
        scanner.close();
    }
    
  }
}

Result
10
100
1000
2000
50000
Note: If we use nextXXX(); methods like nextInt() and a token doesn't match the type that the method is expecting, java will throw InputMismatchException.
import java.util.Scanner;
import java.util.InputMismatchException;
public class SampleClass{

  public static void main(String[]args){
    Scanner scanner = null;
    
    try{
      scanner = new Scanner("10.50");
      while(scanner.hasNext())
        System.out.println(scanner.nextInt());
    }
    catch(InputMismatchException e){
      System.out.println("Token is not an"+
                         " integer type!");
    }
    finally{
      //Close the scanner once
      //you're done using it
      if(scanner != null)
        scanner.close();
    }
    
  }
}

Result
Token is not an integer type!
Scanner can also read other number formats that are locale specific like "10,000". "Numbers with commas" format is supported by US locale. So, we need to set the locale of our scanner to US locale.
import java.util.Scanner;
import java.util.Locale;
public class SampleClass{

  public static void main(String[]args){
    Scanner scanner = null;
    
    try{
      scanner = new Scanner("1,000,000");
      scanner.useLocale(Locale.US);
      
      while(scanner.hasNextInt())
        System.out.println(scanner.nextInt());
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
      //Close the scanner once
      //you're done using it
      if(scanner != null)
        scanner.close();
    }

  }
}

Result
1000000
If we don't specify a locale then, the scanner uses the default locale of JVM. To get the default locale of our JVM, use the getDefault() static method of Locale class. Try changing the locale in the example to Locale.FRENCH and the scanner won't recognize "1,000,000" as an integer type.

Check out the Scanner class if you want to know more variations of the next() method in the Scanner class.

useDelimiter() Method

We can use useDelimiter() method to change the scanner's delimiter.
import java.util.Scanner;
public class SampleClass{

  public static void main(String[]args){
    Scanner scanner = null;
    
    try{
      scanner = new Scanner("Tomato, Potato, Falsetto");
      
      //useDelimiter changes scanner's delimiter
      //to the specified string
      scanner.useDelimiter(", ");
      
      while(scanner.hasNext())
        System.out.println(scanner.next());
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
      //Close the scanner once
      //you're done using it
      if(scanner != null)
        scanner.close();
    }
    
  }
}

Result

Tomato
Potato
Falsetto
If we are knowledgeable about Regular Expressions then, we can use a pattern as a delimiter in our scanner.
import java.util.Scanner;
import java.util.regex.Pattern;
public class SampleClass{

  public static void main(String[]args){
    Scanner scanner = null;
    
    try{
      scanner = new Scanner("-Tomato-0-Potato-1-Falsetto-");
      
      //useDelimiter changes scanner's delimiter
      //to the specified pattern
      scanner.useDelimiter(Pattern.compile("\\d"));
      
      while(scanner.hasNext())
        System.out.println(scanner.next());
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
      //Close the scanner once
      //you're done using it
      if(scanner != null)
        scanner.close();
    }
    
  }
}

Result
-Tomato-
-Potato-
-Falsetto-
Scan File Using Scanner Class

We can use the Scanner class to read files like text files.
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
public class SampleClass{

  public static void main(String[]args) throws IOException{
    Scanner scanner = null;
    
    try{
      //test.txt must exist in the folder where
      //this file is running
      scanner = new Scanner(new File("test.txt"));
      
      while(scanner.hasNext())
        System.out.println(scanner.next());
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
      //Close the scanner once
      //you're done using it
      if(scanner != null)
        scanner.close();
    }
    
  }
}

Assume the words are written in test.txt
like this: Big Brown Fox

Result
Big
Brown
Fox
Scan Console Input Using Scanner Class

We can put System.in of System class as argument in a scanner. Though, there's a problem. Take a look at this example.
Note: This example will ask for input forever. This program needs to be terminated forcefully. If you're using CMD on windows, just close the console or press CTRL+C to terminate the program.
import java.util.Scanner;
public class SampleClass{

  public static void main(String[]args){
    Scanner scanner = null;
    
    try{
      System.out.println("Enter Input: ");
      scanner = new Scanner(System.in);
      
      while(scanner.hasNext())
        System.out.println(scanner.next());
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
     //closing the scanner also closes the stream
     //that is connected to the scanner. Always
     //close streams if they're not gonna be used
     //anymore to free up resources
     scanner.close();
    }
    
  }
}
First off, System.in is the "standard" input stream. This stream is already open and ready to supply input data. Typically this stream corresponds to keyboard input or another input source specified by the host environment or user.

The example above asks for input forever no matter how many inputs you type. The reason why the example is not stopping to ask for input is because System.in is a continuous stream. Thus, it doesn't give an EOL or End Of Line. So, the next() method can't verify if the stream ends. For example, we type "A B", after the program displays "B", next() still waits for an input 'cause it can't find an EOL.

There's a solution to bypass this problem. Since we're only inputting one line on the console, we can use the nextLine() method in the Scanner class.
import java.util.Scanner;
public class SampleClass{

  public static void main(String[]args){
    Scanner scanner = null;
    
    try{
      System.out.println("Enter Input: ");
      scanner = new Scanner(System.in);
      
      if(scanner.hasNextLine())
        System.out.println(scanner.nextLine());
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
      //Close the scanner once
      //you're done using it
      if(scanner != null)
        scanner.close();
    }
    
  }
}

Input: Big Brown Fox
Result
Big Brown Fox
Notice that we use if statement when checking if the scanner has a line of strings. We use if statement and not loop statement to avoid the never ending input loop. Since we only use one line when typing an input on the console, it's alright if we just get a single line.

The problem here is that "Big Brown Fox" is not broken down into three parts 'cause we use nextLine() which skips an entire line. Well, we can use the split() method in the String class to break down the string into multiple parts.
import java.util.Scanner;
public class SampleClass{

  public static void main(String[]args){
    Scanner scanner = null;
    String str = "";
    
    try{
      System.out.println("Enter Input: ");
      scanner = new Scanner(System.in);
      
      if(scanner.hasNextLine())
        str = scanner.nextLine();
        
      String[] tokens = str.split(" ");
      
      for(String s : tokens)
        System.out.println(s);
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
      //Close the scanner once
      //you're done using it
      if(scanner != null)
        scanner.close();
    }
    
  }
}

Input: Big Brown Fox
Result
Big
Brown
Fox
split(String regex) method in string splits strings based on a pattern which is a regular expression. That's right, the string that we put in this method as an argument is a regular expression.

There are characters that have special meanings in regular expression so, some characters that we put in split() might not behave as normal literal like the dot(.) character. If you wanna learn regular expressions then, visit my "Regular Expressions" blogpost.