将时间字符串转换为ISO 8601格式

7

我正在尝试创建一个类似于2015-08-20T08:26:21.000Z格式的字符串,转换成2015-08-20T08:26:21Z

我知道可以使用一些字符串分割技术来完成,但我想知道是否有更优雅的解决方案(代码改动最小)。

上述两者都是时间字符串,我需要的最终结果是ISO 8601日期。 https://www.rfc-editor.org/rfc/rfc3339#section-5.6

我尝试过一些类似的问题,例如将日期字符串转换为毫秒数,但它们实际上并没有解决我的问题。

也尝试使用:

SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ");
String nowAsString = df.format(new Date());

但是它仍然没有进行任何字符串转换。出现以下错误:

23:04:13,829 警告 [RuntimeExceptionMapper] 捕获 RuntimeException: {}: java.lang.IllegalArgumentException: Cannot format given Object as a Date

是否有一些库可以建议?

谢谢。


使用Joda-Time。参见:[tag:jodatime]。 - durron597
输入和输出都是字符串。但这些字符串实际上代表不同的时区。 - CodeMonkey
一个DateFormat可以将字符串转换为日期,也可以将日期转换为字符串。但是你需要进行字符串到字符串的转换。因此,你可以使用一个DateFormat来进行字符串到日期的转换,再使用另一个DateFormat来进行日期到字符串的转换。它们并不代表不同的时区。一个有毫秒部分,而另一个没有。这是唯一的区别。最后的Z表示Zulu时间,即UTC时区。 - JB Nizet
那么 org.joda.time.format.DateTimeFormatter 呢? - CodeMonkey
@CodeMonkey 所以你的目标是消除“.000”吗?你的标题有点令人困惑,因为你第一句话中的两个日期时间字符串都是有效的ISO 8601字符串。 - Basil Bourque
3个回答

11

简短概述

Instant.parse( "2015-08-20T08:26:21.000Z" )
       .toString()

2015年08月20日08时26分21秒

日期时间格式化程序

如果你只想消除.000,那么使用日期时间对象解析您的输入字符串值,然后生成一个新的日期时间值的字符串表示形式以不同的格式。

ISO 8601

顺便说一句,如果这是您的目标,问题标题在第一句中提到的两个字符串都是有效的 ISO 8601格式化字符串,因此标题并没有意义。

  • 2015-08-20T08:26:21.000Z
  • 2015-08-20T08:26:21Z

java.time

Java 8及更高版本具有新的java.time包。这些新类取代了旧的java.util.Date/.Calendar和java.text.SimpleDateFormat类。那些旧类令人困惑、麻烦且存在缺陷。

Instant

如果您只需要UTC时区,则可以使用Instant类。此类表示时间线上的一个点,而不考虑任何特定的时区(基本上是UTC)。

DateTimeFormatter.ISO_INSTANT

调用Instant的toString方法,使用DateTimeFormatter.ISO_INSTANT格式化实例生成日期时间值的字符串表示形式。该格式化程序自动具有分数秒的灵活性。如果值为整秒,则不会生成小数位(显然是问题所需的)。对于分数秒,数字以3、6或9个为一组出现,以表示值达到纳秒分辨率。请注意:此格式可能超过ISO 8601毫秒(3位小数位)的限制。

示例代码

这是Java 8 Update 51中的一些示例代码。

String output = Instant.parse( "2015-08-20T08:26:21.000Z" ).toString( );
System.out.println("output: " + output );

输出:2015-08-20T08:26:21Z

将其更改为小数秒,.08

String output = Instant.parse( "2015-08-20T08:26:21.08Z" ).toString( );

输出: 2015-08-20T08:26:21.080Z

如果您对UTC以外的任何时区感兴趣,则可以从该Instant创建一个ZonedDateTime对象。

ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , ZoneId.of( "America/Montreal" ) ) ;

2
您的格式不正确,请尝试以下格式:-
try {
        String s = "2015-08-20T08:26:21.000Z";
         SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
    Date d = df.parse(s);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
        System.out.println(sdf.format(d));
    } catch (ParseException e) {
        e.printStackTrace();
    }

不要使用“Z”,请使用“X”。最终的“Z”表示这是UTC时区的时间。因此,结果完全不同。顺便说一下,由于您默认时区中存在DST而UTC中不存在DST,因此某些字符串在本地时区中不表示任何现有日期,但在UTC中却表示。 - JB Nizet
@JBNizet 非常有用,谢谢。我会编辑代码的。 - karim mohsen

2
将未知格式的日期字符串转换为使用已知格式的日期字符串,可以使用两个DateFormat对象-一个动态配置以解析输入字符串的格式,另一个配置以生成格式化的输出字符串。对于您的情况,输入字符串格式未指定,必须由调用者提供,但是,输出字符串格式可以配置为使用ISO 8601格式而无需其他输入。基本上,生成ISO 8601格式的日期字符串输出需要调用者提供的两个输入-包含格式化日期的字符串和包含SimpleDateFormat格式的另一个字符串。
以下是Java代码中所描述的转换(我故意省略了空值检查和验证,请根据您的代码添加这些):
private String formatDateAsIso8601(final String inputDateAsString, final String inputStringFormat) throws ParseException {

     final DateFormat iso8601DateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.ENGLISH);
     iso8601DateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));

     final DateFormat inputDateFormatter = new SimpleDateFormat(inputStringFormat, Locale.ENGLISH);
     final Date inputDate = inputDateFormatter.parse(inputDateAsString);

     return iso8601DateFormatter.format(inputDate);
}

如果您想修改该方法,请注意SimpleDateFormat不是线程安全的,并且不应在静态上下文中使用它而没有针对多线程代码的解决方法(ThreadLocal通常用于为SimpleDateFormat执行此类解决方法)。
另一个需要注意的问题是在构建SimpleDateFormat对象时使用Locale - 不要删除Locale配置。允许系统选择使用默认Locale是不安全的,因为那是用户/机器特定的。如果您允许其使用默认Locale,则会出现短暂错误的风险,因为您的开发机器使用的Locale与您最终用户的Locale不同。您不必使用我选择的ENGLISH Locale,使用不同的Locale也是完全可以的(但是您应该了解该Locale的规则并相应地修改代码)。然而,未指定Locale并利用系统默认值是不正确的,可能会导致许多令人沮丧的小时来诊断难以捉摸的错误。
请理解,截至Java 8和包括基于JodaTime的类(如Instant),这个解决方案并不理想。我选择使用过时的API来回答,因为这是您在问题中似乎关心的内容。如果您正在使用Java 8,则强烈建议学习和利用新类,因为它们在几乎所有方面都有所改进。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接