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

我想解析一个时间戳,像这样 – "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(); } 

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

检查这些,它得到了很好的解释: String-Date转换为纳秒 Java日期解析,具有微秒或纳秒精度

SimpleDateFormat SS是毫秒。 你有6739毫秒,这意味着你的时间增加了6.7秒。 也许您可以截断6739673 (或者如果您愿意,将其四舍五入到674 ),这样就可以正确解析为毫秒。

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类的分辨率为纳秒级 ,最高可达一位小数的位数。 例如:

2016-03-17T05:19:24.123456789Z

ISO 8601

在解析/生成日期时间值的文本表示时,您的字符串输入几乎是标准的ISO 8601格式,在java.time中默认使用。 用T替换中间的空间以符合ISO 8601。

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

不分区

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

 LocalDateTime ldt = LocalDateTime.parse( input ); 

世界标准时间

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

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

如果用于特定时区,请指定ZoneId以获取ZoneDateTime

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

你可以使用String newdate = sf.format(outputdate); String newdate = outputdate.toString();

有点偏离主题但是SimpleDateFormat类不是线程安全的 – 不仅在解析上有些可理解,而且在格式化方面。 网上有很多关于此的信息,这里有一个例子: http : //javarevisited.blogspot.co.il/2012/03/simpledateformat-in-java-is-not-thread.html 。 这个问题不会得到解决。 在Java 8中,有一个全新的java.time包, java.time具有很棒的新function,可以处理完整或部分日期和时间。 还有一个新类DateTimeFormatter ,它提供了大大改进的格式和解析function。 但是,如果你使用旧版本的Java然后使用Java 8,那么建议使用Joda时间库或Apache FastDateFormat

如果必须将字符串作为最终输出,为什么不使用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 

正如上面的评论中提到的,你的问题已经包含了答案:毫秒不得超过3位,否则它至少代表一整秒。

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

如果在代码中将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位的所有信息……