SimpleDateFormat 无法解析超过 4 位的毫秒数

SimpleDateFormat cannot parse milliseconds with more than 4 digits

我想像这样解析时间戳 - "2016-03-16 01:14:21.6739"。但是当我使用 SimpleDateFormat 解析它时,我发现它输出了一个不正确的解析值。它会将 6739 毫秒转换为 6 秒,还剩 739 毫秒。它将日期转换为这种格式 - Wed Mar 16 01:14:27 PDT 2016。为什么秒部分从 21 秒变为 27 秒(增加 6 秒?)。以下是我的代码片段:

final SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS");
String parsedate="2016-03-16 01:14:21.6739";
try {
    Date outputdate = sf.parse(parsedate);
    String newdate = outputdate.toString();  //==output date is: Wed Mar 16 01:14:27 PDT 2016 
    System.out.println(newdate);
} catch (ParseException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

SS in SimpleDateFormat 是毫秒。您有 6739 毫秒,这意味着您将额外增加 6.7 秒。也许您可以将 6739 截断为 673(或者如果您愿意,将其四舍五入为 674),以便可以将其正确解析为毫秒。

您可以使用 String newdate = sf.format(outputdate); 代替 String newdate = outputdate.toString();

如果您必须将字符串作为最终输出,为什么不使用 format 而不是 parse

        final SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");
        sf.setTimeZone(TimeZone.getTimeZone("UTC")); 
        Date curDate = new Date();

        String outputdate = sf.format(curDate);
        // 2016-03-17 09:45:28.658+0000
        System.out.println(outputdate);

        Date strToDate = new Date();
        try {
            strToDate = sf.parse(outputdate);
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //Thu Mar 17 17:11:30 MYT 2016
        System.out.println(strToDate);

而不是 "yyyy-MM-dd HH:mm:ss.SSSS" 使用 "yyyy-MM-dd HH:mm:ss.SSSZ" 在这里检查 https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

"yyyy-MM-dd'T'HH:mm:ss.SSSZ"    2001-07-04T12:08:56.235-0700

似乎无法使用 SimpleDateFormat 来表达比毫秒更细粒度的时间。 发生的事情是,当你输入 6739 时,Java 将其理解为 6739 毫秒,即 6 秒和 739 毫秒,因此观察到 6 秒的差异。

查看这些,解释得很好: String-Date conversion with nanoseconds

有点题外话,但是 SimpleDateFormat class 不是线程安全的——不仅在某种程度上可以理解的解析上,而且在格式化上。网上有很多这方面的信息,这里举一个例子:http://javarevisited.blogspot.co.il/2012/03/simpledateformat-in-java-is-not-thread.html. This problem will not be fixed. In Java 8 there is a whole new package java.time with wonderful new features allowing to work with full or partial dates and times. Also there is a new class DateTimeFormatter that provides vastly improved formatting and parsing features. However, if you use java older then java 8 then the recommendation would be to use Joda time library or Apache FastDateFormat

正如上面的评论所述,您的问题已经包含答案:毫秒不能超过 3 位,否则它至少代表一整秒。

您的代码可以正常工作仅仅是因为 java.text.SimpleDateFormat 的一个非常可疑的特性,即 lenient(参见 here)选项。默认情况下,SimpleDateFormatsetLenient 设置为 true,这意味着解析器将尝试解释与模式 100% 不匹配的字符串,并通过一些方法将它们转换为日期对象启发式。例如,它将接受日期 31.04.2016 并将其转换为 01.05.2016。此功能在某些情况下可能很好,但在大多数情况下会产生有问题的结果。

如果您在代码中将 lenient 设置为 false,将不再解析日期字符串。使模式中的错误更加明显:

final SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS");
sf.setLenient(false);
String parsedate="2016-03-16 01:14:21.6739";
...

由于 java.util.Date 无法表示任何低于毫秒的精度,我认为解析日期的最佳选择是简单地去除输入日期的最后一位数字,如果部分点后有超过四位数。您的代码可能如下所示:

final SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
sf.setLenient(false);
String parsedate="2016-03-16 01:14:21.6739";
try {
    // 23 is the length of the date pattern
    if (parsedate.length() > 23) {
        parsedate = parsedate.substring(0, 23);
    }
    Date outputdate = sf.parse(parsedate);
    String newdate = sf.format(outputdate);  //==output date is: 2016-03-16 01:14:21.673
    System.out.println(newdate);
} catch (ParseException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

您也可以尝试添加一些舍入逻辑,以免丢失第 4 位的所有信息...

tl;博士

LocalDateTime.parse(
    "2016-03-16 01:14:21.6739".replace( " " , "T" )  // Comply with ISO 8601 standard format.
)

毫秒与微秒

正如其他人指出的那样,java.util.Date 具有毫秒分辨率。这意味着秒的小数部分最多 3 位。

您的输入字符串中有 4 位数字,多了一位。您的输入值需要更精细的分辨率,例如微秒或纳秒。

java.time

与其使用有缺陷、令人困惑和麻烦的 java.util.Date/.Calendar 类,不如继续使用它们的替代品:Java 8 中内置的 java.time 框架及以后。

java.time类的分辨率为nanosecond,最多9位秒的小数部分。例如:

2016-03-17T05:19:24.123456789Z

ISO 8601

您的字符串输入几乎是 java.time 默认使用的标准 ISO 8601 格式,当 parsing/generating 日期时间值的文本表示时。将中间的 space 替换为 T 以符合 ISO 8601。

String input = "2016-03-16 01:14:21.6739".replace( " " , "T" );

未分区

A LocalDateTime 是日期时间的近似值,没有任何时区上下文。 时间轴上没有片刻。

LocalDateTime ldt = LocalDateTime.parse( input );

UTC

通过应用预期的时区,使 LocalDateTime 成为时间轴上的实际时刻。如果用于 UTC,请创建 Instant.

Instant instant = ldt.toInstant( ZoneOffset.UTC );

分区

如果针对特定时区,请指定 ZoneId to get a ZoneDateTime

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ldt.atZone( zoneId );