这个日期格式是什么?2011-08-12T20:17:46.384Z

666
我有以下日期:2011-08-12T20:17:46.384Z。这是什么格式?我正在尝试通过DateFormat.getDateInstance().parse(dateStr)在Java 1.4中解析它,但我收到了“java.text.ParseException:无法解析的日期:“2011-08-12T20:17:46.384Z” ”的错误信息。
我认为我应该使用SimpleDateFormat进行解析,但我必须先知道格式字符串。到目前为止,我只有yyyy-MM-dd,因为我不知道此字符串中的T是什么意思——与时区相关的东西吗?这个日期字符串来自Files CMIS下载历史媒体类型上显示的lcmis:downloadedOn标记。

95
这是ISO 8601标准。 - Tomasz Nurkiewicz
5
@TomaszNurkiewicz,不是这样的。ISO8601结尾没有Z。 - t1gor
8
ISO8601允许在结尾加上一个Z。请参考上面的链接,查找UTC。 - Jonathan Rosenne
8
@t1gor,末尾的“Z”代表“Zulu”,意思是协调世界时(UTC)。这种格式当然是ISO 8601标准日期时间文本格式的一种。顺便说一下,这些标准格式默认在java.time类中使用。 - Basil Bourque
5
FYI,旧日期时间类(如java.util.Datejava.util.Calendarjava.text.SimpleDateFormat)现在已经不再使用,已被集成到Java 8和Java 9中的Java.time类所取代。请参阅Oracle的教程 - Basil Bourque
12个回答

742

T只是一个字面量,用于将日期与时间分隔开,而Z表示“零时区偏移”,也称为“Zulu时间”(UTC)。如果您的字符串始终带有“Z”,则可以使用:

SimpleDateFormat format = new SimpleDateFormat(
    "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("UTC"));

或者使用Joda Time,您可以使用ISODateTimeFormat.dateTime()


16
为什么我们需要在 TZ 周围加上单引号? - Maroun
13
基本上我们需要那些字面字符。对于 T,可能并不必要(我记不清 SimpleDateFormat 如何处理未知的占位符),但是对于 Z,我们希望它是字符“Z”,而不是一个 UTC 偏移值(例如“00”)。 - Jon Skeet
2
@nyedidikeke:在您提供的维基百科页面中,UTC显示为“祖鲁时间”。我不确定您认为自己正在纠正什么。 - Jon Skeet
11
@JonSkeet说可能会引起不必要的争议;我并不质疑你的回答,只是想提醒一下字母Z是由“零UTC偏移量”得到的字母开头。字母Z在北约音标中被称为“祖鲁”。相应地,军方用于参考零UTC偏移量的方法是以他们所识别的祖鲁字母Z作为锚定点,因此得到了其编码名称:祖鲁时区。需要注意的是,Z仍然保留着它的含义,并且仍然是零UTC偏移量的区域指示器,而祖鲁时区(来自Z)只是一个继承的编码语言,用于参考它。 - nyedidikeke
4
@nyedidikeke:我仍然不同意其他人是否会关心我的回答中的区别,但我已经更新了它。不过,我不会详细说明所有历史,因为对于回答来说,这些历史大体上是无关紧要的。 - Jon Skeet
显示剩余16条评论

185

简述

您的输入字符串使用标准ISO 8601格式。

Instant.parse ( "2011-08-12T20:17:46.384Z" ) 

ISO 8601

这种格式是由实用的标准ISO 8601定义的。

T分隔了日期部分和时间部分。末尾的Z表示UTC(即与UTC偏移为零小时-分钟-秒)。Z的发音为“Zulu”

java.time

早期版本的Java中捆绑的旧日期时间类被证明设计不佳,令人困惑和麻烦。请避免使用它们。

相反,使用内置于Java 8及更高版本中的java.time框架。 java.time类替代了旧的日期时间类和非常成功的Joda-Time库。

java.time类在解析/生成日期时间值的文本表示时默认使用ISO 8601

Instant类以UTC上的时间线上的时刻表示,分辨率为 纳秒。该类可以直接解析您的输入字符串,而无需定义格式化模式。

Instant instant = Instant.parse ( "2011-08-12T20:17:46.384Z" ) ;

Table of date-time types in Java, both modern and legacy


关于 java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了老旧的 遗留 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

想要了解更多,请查看Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

您可以直接使用与 JDBC 4.2 或更高版本兼容的 JDBC驱动程序,直接与数据库交换 java.time 对象。不需要字符串,也不需要 java.sql.* 类。Hibernate 5 和 JPA 2.2 支持 java.time

如何获取 java.time 类?

Table of which java.time library to use with which version of Java or Android


5
“祖鲁时间”来自军事和航空传统,字母表中的25个字母A-Z(不含“J”),每个字母都有一个可发音的名称,代表了他们版本的时区。“祖鲁”时区与协调世界时相差零小时。请参见此链接此链接 - Basil Bourque
LocalDateTime.parse("2021-11-22T09:00:00.000Z") 在解析时会抛出异常。有什么建议,如何使用 java.time 将相同的输入格式化为 "EEEE, MMMM dd"?谢谢。 - Faisal
@Faisal,你试图将一个带有时间和偏移量的日期解析为 LocalDateTime 是不合逻辑的。LocalDateTime 不是一个瞬间,也不是时间轴上的特定点,因为它没有时区或UTC偏移量的概念。你应该将输入解析为 Instant,像这样:Instant.parse("2021-11-22T09:00:00.000Z") - Basil Bourque
有道理。一个实例能否被解析为预期的格式? - Faisal
@Faisal 通过应用一个时区(ZoneId),您可以感知日期。这将为您提供一个ZonedDateTime。从中提取一个LocalDate。使用DateTimeFormatter生成任何格式的文本,或自动本地化。所有这些都已经在Stack Overflow上多次讨论过了。 - Basil Bourque

29

这个“2016-01-27T17:44:55UTC”,也是ISO8601格式吗? - user1997292
我不这么认为。虽然很接近,但是“UTC”作为后缀是不被允许的。它必须是Z或者是一个时区偏移量,例如+0100。虽然Z和UTC意义相同,但是将“UTC”改成“Z”可以产生有效的ISO 8601格式。 - smparkes
@user1997292 不可以。在ISO 8601中,UTC可以表示为 Z+00:00+0000+00 - Ole V.V.

14

除了第一个答案,还有其他方法可以解析它。要解析它:

(1) 如果您想获取有关日期和时间的信息,则可以将其解析为ZonedDateTime(自Java 8)或Date(旧版)对象:

// ZonedDateTime's default format requires a zone ID(like [Australia/Sydney]) in the end.
// Here, we provide a format which can parse the string correctly.
DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
ZonedDateTime zdt = ZonedDateTime.parse("2011-08-12T20:17:46.384Z", dtf);

或者

// 'T' is a literal.
// 'X' is ISO Zone Offset[like +01, -08]; For UTC, it is interpreted as 'Z'(Zero) literal.
String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX";

// since no built-in format, we provides pattern directly.
DateFormat df = new SimpleDateFormat(pattern);

Date myDate = df.parse("2011-08-12T20:17:46.384Z");

(2) 如果您不关心日期和时间,只想将信息视为以纳秒为单位的时刻,则可以使用Instant

// The ISO format without zone ID is Instant's default.
// There is no need to pass any format.
Instant ins = Instant.parse("2011-08-12T20:17:46.384Z");

10

java.time

您不需要使用DateTimeFormatter来解析给定的日期时间字符串。

Java SE 8 日期时间 API(java.time API或现代日期时间 API)基于ISO 8601,只要日期时间字符串符合 ISO 8601 标准,就不需要显式地使用 DateTimeFormatter 对象。

字符串中的Z是表示零时区偏移量的时区标识符。它代表Zulu,并指定了Etc/UTC时区(其时区偏移量为+00:00小时)。

字符串中的T只是根据 ISO-8601 标准的日期时间分隔符

演示:

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;

public class Main {
    public static void main(String[] args) {
        String strDateTime = "2011-08-12T20:17:46.384Z";

        Instant instant = Instant.parse(strDateTime);
        OffsetDateTime odt = OffsetDateTime.parse(strDateTime);
        ZonedDateTime zdt = ZonedDateTime.parse(strDateTime);
        
        System.out.println(instant);
        System.out.println(odt);
        System.out.println(zdt);
    }
}

输出:

2011-08-12T20:17:46.384Z
2011-08-12T20:17:46.384Z
2011-08-12T20:17:46.384Z

了解更多关于java.time现代日期时间API*的信息,请参考教程:日期时间

传统日期时间API

传统的日期时间API(java.util日期时间API及其格式化APISimpleDateFormat)已经过时且容易出错。建议完全停止使用它们,并切换到现代日期时间API*

为了完整起见,我写了一个使用传统API解析此日期时间字符串的解决方案。

在日期时间解析/格式化API中,不要在模式中使用'Z'

如上所述,Z(不带引号)是零时区偏移的时区标识符,而'Z'只是一个字符字面量,它没有任何含义。请使用格式y-M-d'T'H:m:s.SSSXXX。查看文档以了解更多关于这些符号的信息。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class Main {
    public static void main(String[] args) throws ParseException {
        String strDateTime = "2011-08-12T20:17:46.384Z";

        SimpleDateFormat sdf = new SimpleDateFormat("y-M-d'T'H:m:s.SSSXXX", Locale.ENGLISH);
        Date date = sdf.parse(strDateTime);
        // ...
    }
}

请注意,java.util.Date 对象并不是类似于现代日期时间类型的真正日期时间对象;相反,它表示自"纪元"即1970年1月1日 00:00:00 GMT(或UTC)起的毫秒数。由于它不包含任何格式和时区信息,它应用了格式EEE MMM dd HH:mm:ss z yyyy和JVM的时区来返回基于这个毫秒值导出的Date#toString的值。如果您需要以不同的格式和时区打印日期时间,您需要使用带有所需格式和适用时区的SimpleDateFormat
sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
String formatted = sdf.format(date);
System.out.println(formatted); // 2011-8-12T20:17:46.384Z

Joda 日期时间 API

下面是在 Joda-Time 主页上引用的通知:

请注意,从 Java SE 8 开始,建议用户迁移到 java.time(JSR-310)- 它是 JDK 的核心部分,取代了该项目。

为了完整起见,我已经编写了一个使用 Joda 日期时间 API 解析此日期时间字符串的解决方案。

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        String dateTimeStr = "2011-08-12T20:17:46.384Z";
        DateTimeFormatter dtf = DateTimeFormat.forPattern("y-M-d'T'H:m:s.SSSZ").withOffsetParsed();
        DateTime dateTime = dtf.parseDateTime(dateTimeStr);
        System.out.println(dateTime);
    }
}

输出:

2011-08-12T20:17:46.384Z

* 如果由于任何原因,您必须坚持使用Java 6或Java 7,您可以使用ThreeTen-Backport将大部分java.time功能回溯到Java 6和7。如果您正在进行一个Android项目,并且您的Android API级别仍不符合Java-8,请查看通过desugaring可用的Java 8+ API如何在Android项目中使用ThreeTenABP

4

如果您正在寻找一个Android解决方案,可以使用以下代码从时间戳字符串中获取epoch秒。

public static long timestampToEpochSeconds(String srcTimestamp) {
    long epoch = 0;

    try {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            Instant instant = Instant.parse(srcTimestamp);
            epoch = instant.getEpochSecond();
        } else {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSSSS'Z'", Locale.getDefault());
            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
            Date date = sdf.parse(srcTimestamp);
            if (date != null) {
                epoch = date.getTime() / 1000;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return epoch;
}

示例输入:2019-10-15T05:51:31.537979Z

示例输出:1571128673


3
Z代表UTC时区。在Java8+中,您可以简单地使用Instant。
public static void main(String[] args) {
    String time = "2022-06-08T04:55:01.000Z";
    System.out.println(Instant.parse(time).toEpochMilli());
}

2

在JavaScript中

let isoDateTimeString = new Date().toISOString();

描述

"YYYY-MM-DDThh:mm:ss.SSSZ"这种日期时间格式是ISO 8601日期时间格式。


0
您可以使用以下示例。
    String date = "2011-08-12T20:17:46.384Z";

    String inputPattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

    String outputPattern = "yyyy-MM-dd HH:mm:ss";

    LocalDateTime inputDate = null;
    String outputDate = null;


    DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern(inputPattern, Locale.ENGLISH);
    DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(outputPattern, Locale.ENGLISH);

    inputDate = LocalDateTime.parse(date, inputFormatter);
    outputDate = outputFormatter.format(inputDate);

    System.out.println("inputDate: " + inputDate);
    System.out.println("outputDate: " + outputDate);

1
你不应该在 Z 周围加撇号。这意味着期望但忽略那个字母。但是那个字母不应该被忽略。那个字母提供了有价值的信息,即该字符串适用于UTC,偏移量为零的事实。您的格式正在丢弃此重要事实。此外,甚至无需费心定义此格式模式。该模式的格式化程序已内置。 - Basil Bourque

0

为Joda Time

String datetimeString = "2099-12-31T14:20:04Z";
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZoneUTC();
DateTime dateTime = formatter.parseDateTime(datetimeString);
System.out.println("Parsed date and time: " + dateTime);

不需要自己调整格式模式字符串,因为Joda-Time很好地了解ISO 8601格式。在格式中永远不要硬编码“Z”作为文字。它是偏移量,需要解析为偏移量。例如使用ISODateTimeFormat.dateTimeParser().withOffsetParsed().parseDateTime(datetimeString)(生成“已解析日期和时间:2099-12-31T14:20:04.000Z”)。 - Ole V.V.

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