将Java日期转换为UTC字符串

52

java.util.DatetoString()方法会显示本地时区的日期。

有几种常见情况需要将数据打印为UTC,包括日志记录、数据导出和与外部程序通信。

  • 什么是在UTC中创建java.util.Date字符串表示的最佳方法?
  • 如何替换j.u.Date的toString()格式(感谢@JonSkeet!)以获得更好的格式?

附加说明

我认为以自定义格式和时区打印日期的标准方式相当繁琐:

final Date date = new Date();
final String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS zzz";
final SimpleDateFormat sdf = new SimpleDateFormat(ISO_FORMAT);
final TimeZone utc = TimeZone.getTimeZone("UTC");
sdf.setTimeZone(utc);
System.out.println(sdf.format(date));

我正在寻找一行代码,例如:
System.out.println(prettyPrint(date, "yyyy-MM-dd'T'HH:mm:ss.SSS zzz", "UTC"));

1
“normal String format” 是什么意思? - Jon Skeet
3
亲爱的投票者,请提供有信息量的反馈。 - Adam Matan
2
那个格式似乎不适合用于您提供的所有示例。为什么不使用扩展的ISO-8601表示形式?(yyyy-MM-ddTHH:mm:ss.SSS)它是与语言环境无关的、可排序的、固定长度的,而且更容易解析。 - Jon Skeet
@JonSkeet同意。我只使用它是因为它是Java的默认值,这意味着在日志和其他简单打印Date对象的应用程序中可能很常见。 - Adam Matan
2
默认设置太差了,我希望很少有人使用它。使用SimpleDateFormat做得更好并不难。 - Jon Skeet
显示剩余3条评论
7个回答

53

简述

您的问题:

我正在寻找像这样的一句话:

问就有答。将可怕的遗留类 Date 转换为其现代替代品 Instant

myJavaUtilDate.toInstant().toString()

2020-05-05T19:46:12.912Z

java.time

在Java 8及以后的版本中,我们有了新的java.time package内置(Tutorial)。它受到Joda-Time的启发,由JSR 310定义,并由ThreeTen-Extra项目扩展。
最好的解决方案是对日期时间对象进行排序,而不是字符串。但如果你必须使用字符串,请继续阅读。

Instant表示时间轴上的某一时刻,基本上是在UTC(有关详细信息,请参见类文档)。 toString实现默认使用DateTimeFormatter.ISO_INSTANT格式。此格式包括零、三、六或九个数字,以显示精度高达纳秒的秒分数。

String output = Instant.now().toString(); // Example: '2015-12-03T10:15:30.120Z'

如果您必须与旧的Date类交互,可以通过向旧类添加新方法来将其转换为/从java.time。例如:Date :: toInstant
myJavaUtilDate.toInstant().toString()

你可能需要使用其他格式化程序,如果你需要小数部分有一致的数字或者不需要小数部分。
如果你想截断秒的小数部分,另一种方法是使用 ZonedDateTime 代替 Instant,调用它的 方法将小数部分更改为零
请注意,我们必须为ZonedDateTime指定一个时区(因此是这个名称)。在我们的情况下,这意味着UTC。ZoneID的子类ZoneOffset提供了一个方便的常量用于UTC。如果我们省略时区,则JVM的当前默认时区会隐式应用。
String output = ZonedDateTime.now( ZoneOffset.UTC ).withNano( 0 ).toString();  // Example: 2015-08-27T19:28:58Z

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


关于 java.time

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

要了解更多,请参见Oracle教程。在Stack Overflow上搜索许多示例和说明。规范是JSR 310Joda-Time项目现在处于维护模式,建议迁移到java.time类。

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

在哪里获取 java.time 类?


Joda-Time

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

我正在寻找一个一行代码的解决方案

如果使用 Joda-Time 2.3 库,这非常容易。ISO 8601 是默认格式。

时区

在下面的代码示例中,请注意我指定了时区而不是依赖于默认时区。在本例中,我根据您的问题指定了 UTC。末尾的 Z,发音为“Zulu”,表示没有与 UTC 的时区偏移。

示例代码

// import org.joda.time.*;

String output = new DateTime( DateTimeZone.UTC );

输出...

2013-12-12T18:29:50.588Z

4
没有评论地点踩?该问题要求输出一个单行日期时间的协调世界时(UTC),而我已经给出了答案。请问还有其他需要翻译的内容吗? - Basil Bourque
2
使用Joda-Time的一行代码来处理现有的日期实例:new DateTime(date).withZone(DateTimeZone.UTC).toString() - Stan Kurdziel

18

在有用的评论的基础上,我已经完全重建了日期格式化程序。使用应该:

  • 简短(一行)
  • 将可处理对象(时区、格式)表示为字符串
  • 支持有用的、可排序的ISO格式和盒子中的传统格式

如果您认为此代码有用,我可以在github上发布源代码和JAR。

用法

// The problem - not UTC
Date.toString()                      
"Tue Jul 03 14:54:24 IDT 2012"

// ISO format, now
PrettyDate.now()        
"2012-07-03T11:54:24.256 UTC"

// ISO format, specific date
PrettyDate.toString(new Date())         
"2012-07-03T11:54:24.256 UTC"

// Legacy format, specific date
PrettyDate.toLegacyString(new Date())   
"Tue Jul 03 11:54:24 UTC 2012"

// ISO, specific date and time zone
PrettyDate.toString(moonLandingDate, "yyyy-MM-dd hh:mm:ss zzz", "CST") 
"1969-07-20 03:17:40 CDT"

// Specific format and date
PrettyDate.toString(moonLandingDate, "yyyy-MM-dd")
"1969-07-20"

// ISO, specific date
PrettyDate.toString(moonLandingDate)
"1969-07-20T20:17:40.234 UTC"

// Legacy, specific date
PrettyDate.toLegacyString(moonLandingDate)
"Wed Jul 20 08:17:40 UTC 1969"

代码

(这段代码也是Code Review Stack Exchange上的一个问题的主题)

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

/**
 * Formats dates to sortable UTC strings in compliance with ISO-8601.
 * 
 * @author Adam Matan <adam@matan.name>
 * @see https://dev59.com/Z2gu5IYBdhLWcg3we3Ct#11294308
 */
public class PrettyDate {
    public static String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS zzz";
    public static String LEGACY_FORMAT = "EEE MMM dd hh:mm:ss zzz yyyy";
    private static final TimeZone utc = TimeZone.getTimeZone("UTC");
    private static final SimpleDateFormat legacyFormatter = new SimpleDateFormat(LEGACY_FORMAT);
    private static final SimpleDateFormat isoFormatter = new SimpleDateFormat(ISO_FORMAT);
    static {
        legacyFormatter.setTimeZone(utc);
        isoFormatter.setTimeZone(utc);
    }

    /**
     * Formats the current time in a sortable ISO-8601 UTC format.
     * 
     * @return Current time in ISO-8601 format, e.g. :
     *         "2012-07-03T07:59:09.206 UTC"
     */
    public static String now() {
        return PrettyDate.toString(new Date());
    }

    /**
     * Formats a given date in a sortable ISO-8601 UTC format.
     * 
     * <pre>
     * <code>
     * final Calendar moonLandingCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
     * moonLandingCalendar.set(1969, 7, 20, 20, 18, 0);
     * final Date moonLandingDate = moonLandingCalendar.getTime();
     * System.out.println("UTCDate.toString moon:       " + PrettyDate.toString(moonLandingDate));
     * >>> UTCDate.toString moon:       1969-08-20T20:18:00.209 UTC
     * </code>
     * </pre>
     * 
     * @param date
     *            Valid Date object.
     * @return The given date in ISO-8601 format.
     * 
     */

    public static String toString(final Date date) {
        return isoFormatter.format(date);
    }

    /**
     * Formats a given date in the standard Java Date.toString(), using UTC
     * instead of locale time zone.
     * 
     * <pre>
     * <code>
     * System.out.println(UTCDate.toLegacyString(new Date()));
     * >>> "Tue Jul 03 07:33:57 UTC 2012"
     * </code>
     * </pre>
     * 
     * @param date
     *            Valid Date object.
     * @return The given date in Legacy Date.toString() format, e.g.
     *         "Tue Jul 03 09:34:17 IDT 2012"
     */
    public static String toLegacyString(final Date date) {
        return legacyFormatter.format(date);
    }

    /**
     * Formats a date in any given format at UTC.
     * 
     * <pre>
     * <code>
     * final Calendar moonLandingCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
     * moonLandingCalendar.set(1969, 7, 20, 20, 17, 40);
     * final Date moonLandingDate = moonLandingCalendar.getTime();
     * PrettyDate.toString(moonLandingDate, "yyyy-MM-dd")
     * >>> "1969-08-20"
     * </code>
     * </pre>
     * 
     * 
     * @param date
     *            Valid Date object.
     * @param format
     *            String representation of the format, e.g. "yyyy-MM-dd"
     * @return The given date formatted in the given format.
     */
    public static String toString(final Date date, final String format) {
        return toString(date, format, "UTC");
    }

    /**
     * Formats a date at any given format String, at any given Timezone String.
     * 
     * 
     * @param date
     *            Valid Date object
     * @param format
     *            String representation of the format, e.g. "yyyy-MM-dd HH:mm"
     * @param timezone
     *            String representation of the time zone, e.g. "CST"
     * @return The formatted date in the given time zone.
     */
    public static String toString(final Date date, final String format, final String timezone) {
        final TimeZone tz = TimeZone.getTimeZone(timezone);
        final SimpleDateFormat formatter = new SimpleDateFormat(format);
        formatter.setTimeZone(tz);
        return formatter.format(date);
    }
}

13
@Michael-O:这篇帖子的最后一次编辑超出了界限,应该改为评论。 - Cerbrus
6
@Michael-O:如果这是事实,并且在回答的上下文中很重要,请留下评论,您的评论将获得赞同,以便每个人都可以清楚地看到它。 - hakre
7
“CDT”、“UDT”等确实不是ISO-8601时区标识符,这一点值得注意。将编辑要求修改成贬低投票的回答是有用的。 - T.J. Crowder
6
为了明确,回答声称像“2012-07-03T11:54:24.256 UTC”这样的日期时间字符串是“ISO格式”,因此应该因此而被投反对票是错误的。尽管@Michael-O以令人反感的方式引起了错误的注意,但这并不改变事实。顺便说一句,如果你还没有看过,Michael可以参考一下http://meta.stackoverflow.com/questions/303570/should-you-let-users-edit-other-peoples-answer-to-encourage-downvoting. - Mark Amery
6
@Michael-O如果您知道答案,为什么不发表呢? - baao
显示剩余5条评论

8

以下是基于上面接受的答案的简化代码,对我有效:

public class GetSync {
    public static String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS zzz";
    private static final TimeZone utc = TimeZone.getTimeZone("UTC");
    private static final SimpleDateFormat isoFormatter = new SimpleDateFormat(ISO_FORMAT);
    static {
        isoFormatter.setTimeZone(utc);
    }

    public static String now() {
        return isoFormatter.format(new Date()).toString();
    }
}

我希望这能帮助到某人。


2
请注意,与已接受的答案一样,为了实际获得有效的符合ISO 8601标准的输出,您应该使用“ISO_FORMAT =“yyyy-MM-dd'T'HH:mm:ss.SSSXX”。 - Ilmari Karonen
更新:糟糕的DateSimpleDateFormat类已被现代java.time类所取代,这些类在JSR 310中定义。 - Basil Bourque

3

java.time.Instant

只需使用java.timeInstant即可。

    System.out.println(Instant.now());

这刚刚被打印出来了:

2018-01-27T09:35:23.179612Z

Instant.toString总是给出UTC时间。

输出通常是可排序的,但有不幸的例外。 toString 给出了足够多的三位小数组来呈现它所持有的精度。在我的Mac上的Java 9中,Instant.now() 的精度似乎是微秒,但我们应该期望大约每千次中有一次会达到整毫秒数并仅打印三个小数。小数位数不相等的字符串将以错误的顺序排序(除非您编写自定义比较器以考虑此问题)。

Instantjava.time中的一个类,这是现代Java日期和时间API之一,我强烈建议您使用它,而不是过时的Date类。 java.time已内置于Java 8及更高版本中,并已向后移植到Java 6和7。


1
如果XStream是一个依赖项,请尝试:
new com.thoughtworks.xstream.converters.basic.DateConverter().toString(date)

0

如果你只想使用java.util.Date,这里有一个小技巧可以用:

String dateString = Long.toString(Date.UTC(date.getYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()));


-2

8
转换为UTC在哪里?OP已经使用SimpleDateFormat了。 - tak3shi
更新:糟糕的DateSimpleDateFormat类已被现代java.time类所取代,这些类在JSR 310中定义。 - Basil Bourque

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