【问题标题】:How to always include milliseconds even when Date format does not include it?即使日期格式不包含毫秒,如何始终包含毫秒?
【发布时间】:2021-05-04 12:00:31
【问题描述】:

这个例子有 2 列数据。

第 1 列是没有毫秒的日期,第 2 列是毫秒的数据。

显示允许用户更改日期格式。关键是第 1 列不应包含毫秒,而第 2 列应始终包含毫秒。

如果日期格式为 dd-MM-yyyy HH:mm:ss,则在处理第 2 列中的数据时,代码可以简单地附加“.SSS”,但格式可能会有很大差异。例如,它甚至可以是“MM-dd-yy”而不包括任何时间参考,但第二列应始终包括时间和毫秒。

这里是代码。有额外的杂项代码只是为了确保毫秒是第一次可见。

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());
    }            
} 

执行时会显示以下帧。注意第二列有毫秒。

当日期更改为“dd-MM-yyyy”并按下“激活新日期格式”按钮时,第 2 列中的日期会丢失小时、分钟和秒,尤其是毫秒。

是否可以在确保 HH:mm:ss.sss 始终存在的同时对一列使用单一日期格式(几乎可以是任何格式)同时对另一列使用相同的格式?

【问题讨论】:

  • 我建议你不要使用SimpleDateFormatDate。这些类设计不良且过时,尤其是前者,尤其是出了名的麻烦。而是使用来自java.time, the modern Java date and time APILocalDateTimeDateTimeFormatter
  • 用户或其他人是否提供格式?如果提供给您HH dd MMM yyyy,那么第 2 列应该使用什么格式?
  • 初始日期格式是默认的,因此当数据最初加载时,每个列/日期都有自己的格式,但是有一个选项允许之后更改日期格式,并且很少包括毫秒但对于第二个日期列很重要。
  • 不清楚,抱歉。默认值从何而来?对我来说,从源头修复格式以确保第 2 列的格式已经包含毫秒,这听起来很简单。

标签: java simpledateformat milliseconds


【解决方案1】:

您首先需要明确定义输入规则、输出要求以及调整输入以满足这些输出要求的规则。

似乎用户输入没有规则,这使得这很困难..他们可以输入任何他们想要的!也许至少需要几年、几个月和几天才能稍微简化一下?另一个输入要求可能是,如果它们包括毫秒,那么它们还必须包括秒,如果是秒,那么分钟,如果是分钟,那么小时,等等。这意味着你不必担心只有分钟没有小时或类似的东西.

您的输出要求似乎是包含年、月、日、小时、分钟、秒、毫秒,所有这些都以用户格式用于他们定义的任何内容,但添加到任何未定义的格式中。但是您添加这些剩余字段的规范是什么?

这里有一些代码可以帮助您入门。如果您要向模式中添加更多字段,其他要添加的内容可能是进一步检查输入使用的分隔符。

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; }
}

【讨论】:

  • 我认为部分标签“此处理在...时有效”将是最有用的,因为我可以在丢失时附加。谢谢。
  • @UnhandledException 您可以根据fields.get()中现有字段的位置附加它:)
  • @UnhandledException 注意:我还添加了一项检查以确保输入有 足够 小数秒,因为它之前只允许一个“S”,但现在确保有三个'SSS'
【解决方案2】:

只需将渲染器更改为此

try {
     if (col == 0) {
          SimpleDateFormat oldDateFormat = new SimpleDateFormat(des.getOldDateFormat());
          SimpleDateFormat newDateFormat = new SimpleDateFormat(des.getCurrentDateFormat());
          Date date = oldDateFormat.parse((String) value);
          value = newDateFormat.format(date) ;
          setText(value.toString());
    } else {
          SimpleDateFormat tempDateFormat = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss.SSS");
          Date tempDate = tempDateFormat.parse((String) value);
          value = tempDateFormat.format(tempDate);
          setText(value.toString());
    }
} catch (ParseException e) {
            e.printStackTrace();
}

但正如@Ole V.V. 所提到的。你不应该使用DateSimpleDateFormat。此外,我最好不要在您的模型中使用String,而是已经使用LocalDateTime - 所以它不允许将字符串转换为时间,反之亦然

【讨论】:

  • 谢谢。我对上述内容的唯一担心是当第一列有 dd-MM 而第二列有 MM-dd 时,这在年初有 05/03 时可能会非常误导,如果 3 月很容易混淆5 日或 5 月 3 日,这就是为什么我希望两列都保持相同的日、月、年格式。
猜你喜欢
  • 1970-01-01
  • 2011-11-08
  • 2013-02-21
  • 2014-10-12
  • 2019-01-10
  • 1970-01-01
  • 2015-12-17
  • 2013-11-03
  • 1970-01-01
相关资源
最近更新 更多