Home > Enterprise >  Two-digit dates in JFormattedTextFields
Two-digit dates in JFormattedTextFields

Time:09-17

I have what is essentially a JFormattedTextField which uses a DateFormatter, which gets its format from a user-controlled locale setting. The code below is an attempt to reproduce this without a ton of proprietary code. (I apologize for my ham-fisted attempts at top-level Swing stuff...)

My issue is, that if the user has chosen a locale with two-digit years, it becomes impossible to enter dates in the "wrong" century. For example, if I run the program below and edit the year to 2047 and click Ok, the program prints a date in 1947.

The reason seems to be that JFormattedTextField likes to "normalize" its data by round-tripping it through the text representation. One workaround is to override commitEdit() with a method that tests the roundtrip and, if it turns out to clobber the year, replaces the format with a hard-coded YYYY-MM-DD. This seems a bit heavy-handed, though. Is there a better way, except asking the users to choose a sane locale or hard-wiring the format for this particular field? (I know there are ways to chose which 100-year window is the right one, but that still limits me to a 100-year window).

import javax.swing.*;
import javax.swing.text.DateFormatter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;

public class Main {
    private static void createAndShowGUI() {
        JFrame frame = new JFrame("HelloWorldSwing");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        var gbl = new GridBagLayout();
        frame.setLayout(gbl);

        final Container contentPane = frame.getContentPane();

        Date date = new Date();
        JFormattedTextField dateField = new JFormattedTextField(date);
        dateField.setFormatterFactory(new JFormattedTextField.AbstractFormatterFactory() {
            @Override
            public JFormattedTextField.AbstractFormatter getFormatter(JFormattedTextField tf) {
                return new DateFormatter(DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.GERMANY));
            }
        });
        contentPane.add(dateField);

        JButton button = new JButton("Ok");
        contentPane.add(button);
        button.addActionListener((ActionEvent e) -> {
            try {
                dateField.commitEdit();
            } catch (ParseException ex) {
                ex.printStackTrace();
            }
            System.out.println(dateField.getValue());
        });
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(Main::createAndShowGUI);
    }
}

CodePudding user response:

The following works for me but I'm not saying that it's the best or only way.

Your problem is with the date only and not the time, so in the below code I disregard the time and handle the date only.

In your situation, method getValue, of class JFormattedTextField, returns an instance of java.util.Date. This class has been superseded by classes in package java.time. One of the reasons is because of the [problematic] behavior you are seeing.

Hence, in the below code, I take the text contents of the JFormattedTextField, extract the date part and convert that to a java.time.LocalDate which displays the year that you want.

I added lots of println statements just to demonstrate what I wrote, above.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.text.DateFormat;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;

import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.text.DateFormatter;

public class FmTxtFld {
    private void createAndShowGUI() {
        JFrame frame = new JFrame("FmTxtFld");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        var gbl = new GridBagLayout();
        frame.setLayout(gbl);

        final Container contentPane = frame.getContentPane();

        Date date = new Date();
        JFormattedTextField dateField = new JFormattedTextField(date);
        dateField.setFormatterFactory(new JFormattedTextField.AbstractFormatterFactory() {
            @Override
            public JFormattedTextField.AbstractFormatter getFormatter(JFormattedTextField tf) {
                return new DateFormatter(DateFormat.getDateTimeInstance(DateFormat.SHORT,
                                                                        DateFormat.SHORT,
                                                                        Locale.GERMANY));
            }
        });
        contentPane.add(dateField);

        JButton button = new JButton("Ok");
        contentPane.add(button);
        button.addActionListener(e -> {
            String text = dateField.getText();
            System.out.println("Text: "   text);
            int ndx = text.indexOf(',');
            String str = text.substring(0, ndx);
            System.out.println("Date: "   str);
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yy", Locale.GERMANY);
            LocalDate ld = LocalDate.parse(str, formatter);
            System.out.println("java.time.LocalDate = "   ld);
            try {
                dateField.commitEdit();
            }
            catch (ParseException ex) {
                ex.printStackTrace();
            }
            Object obj = dateField.getValue();
            System.out.println("Value type: "   obj.getClass().getName());
            System.out.println("Value: "   obj);
        });
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> new FmTxtFld().createAndShowGUI());
    }
}

When I run the above code and change the year to 47, then when I press the OK button, I get the following output:

Text: 16.09.47, 15:16
Date: 16.09.47
java.time.LocalDate = 2047-09-16
Value type: java.util.Date
Value: Tue Sep 16 15:16:00 IST 1947

In other words, java.util.Date displays the year as 1947 whereas java.time.LocalDate displays the year as 2047 (which is what you want, right?)

CodePudding user response:

If you need four digit year to be sure to have the right century and your locale uses two digit year in its SHORT date format, I see two options:

  1. Use the MEDIUM format for date instead. I suspect that in many locales this will force users to do more typing, about which they probably will not be satisfied.
  2. Modify the locale’s format to require 4 digit year. It may be regarded as brutal or a funny compromise depending on viewpoint, but it gives us what we need.

I recommend that you use java.time, the modern Java date and time API, also when working with a JFormattedTextField. To get and modify the format pattern for your user’s locale:

    Locale userSelectedLocale = Locale.GERMAN;
    String formatPattern = DateTimeFormatterBuilder
            .getLocalizedDateTimePattern(FormatStyle.SHORT, FormatStyle.SHORT,
                    IsoChronology.INSTANCE, userSelectedLocale);
    
    System.out.println("Localized format:          "   formatPattern);
    formatPattern = formatPattern.replaceFirst("y ", "yyyy");
    System.out.println("Modified localized format: "   formatPattern);

Output so far is:

Localized format:          dd.MM.yy, HH:mm
Modified localized format: dd.MM.yyyy, HH:mm

To use this with a JFormattedTextField we need an instance of the old Format class (the superclass of DateFormat). To build that:

    Format formatForFormattedTextField 
            = DateTimeFormatter.ofPattern(formatPattern, userSelectedLocale)
                    .toFormat(LocalDateTime::from);

I am specifying that the formatter should parse into LocalDateTime objects. LocalDateTime is the java.time class for a date and time of day without time zone or offset. Since the user neither enters time zone nor offset, this seems to be the right class for our purpose.

For how to use Format and JFormattedTextField together, you may for example see another answer of mine, there is a link below.

Links

  • Related