This example has 2 columns of data.
Column 1 is a date without milliseconds and Column 2 is a data with milliseconds.
The display allows the user to change the Date format. The key is that column 1 should never include milliseconds while column 2 should always include milliseconds.
Granted if the date format is dd-MM-yyyy HH:mm:ss that the code could simply append ".SSS" when processing the data in Column 2, but the format can vary widely. For example, it could even be "MM-dd-yy" without including any time reference, yet the 2nd column should always include the time along with milliseconds.
Here is the code. There is extra miscellaneous code just to ensure that the milliseconds are visible the first time.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
public class DateFormatRendererExample
{
public static void main(String[] args)
{
DateExampleSetup dateExampleSetup = new DateExampleSetup ();
}
}
class DateExampleSetup extends JFrame
{
final static Class[] columnClass = new Class[] { Date.class, Date.class };
String currentDateFormat = "MM-dd-yyyy HH:mm:ss";
String oldDateFormat = "MM-dd-yyyy HH:mm:ss";
MyTableModel model;
public DateExampleSetup()
{
model = new MyTableModel();
model.addRow("05-22-2020 12:12:12", "06-02-2020 20:11:55.123");
model.addRow("10-18-2020 14:16:17", "01-02-2020 09:09:49.567");
JTable table = new JTable(model);
table.setDefaultRenderer(Date.class, new MyDateCellRenderer(this));
JTextField dateFormatField = new JTextField(20);
dateFormatField.setText(currentDateFormat);
JButton activateDateFormat = new JButton("Activate new Date Format");
activateDateFormat.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent arg0)
{
setCurrentDateFormat(dateFormatField.getText());
MyTableModel newModel = new MyTableModel ();
newModel.addRow("05-22-2020 12:12:12", "06-02-2020 20:11:55.1234");
newModel.addRow("10-18-2020 14:16:17", "02-02-2020 09:09:49.5678");
table.setModel(newModel);
newModel.fireTableDataChanged();
setOldDateFormat(getCurrentDateFormat());
}
});
setLayout(new BorderLayout());
add(dateFormatField, BorderLayout.NORTH);
add(table, BorderLayout.CENTER);
add(activateDateFormat, BorderLayout.SOUTH);
setVisible(true);
setSize(500,300);
}
public String getCurrentDateFormat()
{
return currentDateFormat;
}
public void setCurrentDateFormat(String newCurrent)
{
currentDateFormat = newCurrent;
}
public String getOldDateFormat()
{
return oldDateFormat;
}
public void setOldDateFormat(String newCurrent)
{
currentDateFormat = newCurrent;
}
}
class MyDateCellRenderer extends JLabel implements TableCellRenderer
{
DateExampleSetup des;
boolean firstTimeRow1 = true;
boolean firstTimeRow2 = true;
public MyDateCellRenderer(DateExampleSetup des)
{
super();
this.des = des;
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int col) {
try
{
///Just to get the milliseconds to show the first time.
if (firstTimeRow1 && row == 0 && col == 1)
{
SimpleDateFormat tempDateFormat = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss.SSS");
Date tempDate = null;
tempDate = tempDateFormat.parse((String)value);
value = tempDateFormat.format(tempDate);
setText(value.toString());
firstTimeRow1 = false;
}
else if (firstTimeRow2 && row == 1 && col == 1)
{
SimpleDateFormat tempDateFormat = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss.SSS");
Date tempDate = null;
tempDate = tempDateFormat.parse((String)value);
value = tempDateFormat.format(tempDate);
setText(value.toString());
firstTimeRow2 = false;
}
else
{
SimpleDateFormat oldDateFormat = new SimpleDateFormat(des.getOldDateFormat());
SimpleDateFormat newDateFormat = new SimpleDateFormat(des.getCurrentDateFormat());
Date date = null;
date = oldDateFormat.parse((String)value);
value = newDateFormat.format(date);
setText(value.toString());
}
}
catch (ParseException e)
{
e.printStackTrace();
}
return this;
}
}
class MyTableModel extends AbstractTableModel {
private List<String[]> rows;
private String columnNames[] = { "Regular Date ", "Date with milliseconds"};
public MyTableModel() {
rows = new ArrayList<>(25);
}
@Override
public int getRowCount() {
return rows.size();
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return Date.class;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
String[] row = rows.get(rowIndex);
return row[columnIndex];
}
@Override
public String getColumnName(int col) {
return columnNames[col];
}
public void addRow(String s1, String s2)
{
String[] row = new String[getColumnCount()];
row[0] = s1;
row[1] = s2;
rows.add(row);
fireTableRowsInserted(getRowCount(), getRowCount());
}
}
When execute the following Frame is displayed. Notice the 2nd columns has the milliseconds.
When the date is change to "dd-MM-yyyy" and the "Activate new Date Format" button is pressed, the Dates in the 2nd column lose their hours, minutes and seconds and especially the milliseconds.
Is it possible to use a single Date format (that can be just about anything) for one column while using that same format for another column while ensuring the HH:mm:ss.sss always exists?
You'll first need to clearly define the rules of the input, the requirements of the output, and the rules for adapting the input to meet those output requirements.
It seems like the user input has no rules, which makes this difficult.. they can enter whatever they want! Maybe require at least years, months, and days to simplify this a little? Another input requirement could be that if they include milliseconds then they must also include seconds, and if seconds then minutes, if minutes then hours, etc.. This means you wouldn't have to worry about only having minutes without hours or something like that.
It seems like your output requirement is that there's year, month, day, hour, minute, second, milliseconds included, all in the user format for whatever they did define but adding to the format for whatever wasn't defined. But what's your specification for adding those remaining fields?
Here's some code to get you started. Something else to add might be further checks for what delimiters the input uses if you are going to be adding more fields to the pattern.
public static void main(String[] args) throws Exception {
// try with additional valid input patterns, such as 'MM/dd/yyyy' etc
String[] patterns = {
"yyyy-MM-dd HH:mm:ss.SSS",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm",
"yyyy-MM-dd HH",
"yyyy-MM-dd",
"HH:mm:ss.S yyyy-MM-dd",
"HH:mm:ss yyyy-MM-dd",
"HH:mm yyyy-MM-dd", };
LocalDateTime date = LocalDateTime.now();
for (String pattern : patterns) {
String newPattern = fillInPattern(pattern);
System.out.println(pattern + "\t\t" + newPattern + "\t\t" + DateTimeFormatter.ofPattern(newPattern).format(date));
}
// Output:
// yyyy-MM-dd HH:mm:ss.SSS yyyy-MM-dd HH:mm:ss.SSS 2021-05-04 11:15:53.743
// yyyy-MM-dd HH:mm:ss yyyy-MM-dd HH:mm:ss.SSS 2021-05-04 11:15:53.743
// yyyy-MM-dd HH:mm yyyy-MM-dd HH:mm:ss.SSS 2021-05-04 11:15:53.743
// yyyy-MM-dd HH yyyy-MM-dd HH:mm:ss.SSS 2021-05-04 11:15:53.743
// yyyy-MM-dd yyyy-MM-dd HH:mm:ss.SSS 2021-05-04 11:15:53.743
// HH:mm:ss.S yyyy-MM-dd HH:mm:ss.SSS yyyy-MM-dd 11:15:53.743 2021-05-04
// HH:mm:ss yyyy-MM-dd HH:mm:ss.SSS yyyy-MM-dd 11:15:53.743 2021-05-04
// HH:mm yyyy-MM-dd HH:mm:ss.SSS yyyy-MM-dd 11:15:53.743 2021-05-04
}
private static String fillInPattern(String pattern) {
// Get the range of each of the pattern fields you need to check
// Parsing like this should be mostly safe since field pattern characters are generally adjacent to each other
// and you don't expect repeats of a field. You could invalidate any pattern which violates this assumption.
Map<Character, Range> fields = new HashMap<>();
for (int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
if (c == 'H' || c == 'm' || c == 's' || c == 'S') {
int start = i;
while (i < pattern.length() && pattern.charAt(i) == c) {
i++;
}
fields.put(c, new Range(start, i));
}
}
// Add the missing fields:
// Works when you guarantee that if there's millis then there's seconds, if seconds then minutes, and if minutes then hours..
StringBuilder builder = new StringBuilder(pattern);
if (!fields.containsKey('H')) {
builder.append(" HH:mm:ss.SSS");
} else if (!fields.containsKey('m')) {
builder.insert(fields.get('H').end(), ":mm:ss.SSS");
} else if (!fields.containsKey('s')) {
builder.insert(fields.get('m').end(), ":ss.SSS");
} else if (!fields.containsKey('S')) {
builder.insert(fields.get('s').end(), ".SSS");
} else {
// it contains all the fields but make sure there's enough fractional seconds
Range fSecs = fields.get('S');
if (fSecs.length() < 3) {
builder.replace(fSecs.start(), fSecs.end(), "SSS");
}
}
return builder.toString();
}
public static class Range {
private final int start;
private final int end;
public Range(int start, int end) {
this.start = start;
this.end = end;
}
public int start() { return start; }
public int end() { return end; }
public int length() { return end - start; }
@Override public String toString() { return start + "-" + end; }
}