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)选项。默认情况下,SimpleDateFormat
将 setLenient
设置为 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 );
我想像这样解析时间戳 - "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)选项。默认情况下,SimpleDateFormat
将 setLenient
设置为 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 );