时区的Java时间格式

时间:2017-10-04 21:54:20

标签: java datetime timezone simpledateformat datetime-format

我正在尝试以格式(String)输出日期:

  

20170801 123030 America / Los_Angeles

但是使用这段代码:

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd hhmmss Z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println(sdf.format(new java.util.Date()));

我得到输出为(注意区域部分):

  

20174904 024908 -0700

知道怎么解决吗?我需要打印"​America/Los_Angeles"而不是"-0700"

3 个答案:

答案 0 :(得分:4)

看起来SimpleDateFormat不支持您想要的内容。

以下是可能的格式:

z   Time zone   General time zone   Pacific Standard Time; PST; GMT-08:00  
Z   Time zone   RFC 822 time zone   -0800  
X   Time zone   ISO 8601 time zone  -08; -0800; -08:00  

您可以在格式化日期后添加“America / Los_Angeles”:

TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
DateFormat sdf = new SimpleDateFormat("yyyyMMdd hhmmss", Locale.US);
sdf.setTimeZone(timeZone);
System.out.println(sdf.format(new Date()) + " " + timeZone.getID());

DateFormat sdfWithTimeZone = new SimpleDateFormat("yyyyMMdd hhmmss zzzz", Locale.US);
sdfWithTimeZone.setTimeZone(timeZone);
System.out.println(sdfWithTimeZone.format(new Date()));

输出:

20171004 031611 America/Los_Angeles
20171004 031611 Pacific Daylight Time

答案 1 :(得分:4)

如果您可以使用Java 8,则可以使用DateTimeFormatter及其符号V来显示区域ID:

Instant now = Instant.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd hhmmss VV")
    .withLocale(Locale.US)
    .withZone(ZoneId.of("America/Los_Angeles"));
System.out.println(fmt.format(now));

打印:

  

20171004 032552 America / Los_Angeles

请注意,SimpleDateFormat不支持Alexandr's answer中提到的此标记。

如果您 java.util.Date开始但仍可以使用Java 8,则可以先将其转换为Instant

Instant now = new java.util.Date().toInstant();
...

答案 2 :(得分:4)

正如@Alexandr's answer指出的那样,SimpleDateFormat中没有用于打印时区ID的内置模式。但是有一种方法可以覆盖它。

首先,使用z模式创建格式化程序,该格式符对应于时区短名称。 我不确定语言环境是否对此很重要(我知道它会影响长名称,不确定短名称,但无论如何我都保留它)。

然后我从格式化程序中获取java.text.DateFormatSymbols并覆盖与短名称对应的字符串:

// use "z" (short timezone name)
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd hhmmss z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));

// get the java.text.DateFormatSymbols
DateFormatSymbols symbols = sdf.getDateFormatSymbols();
// get the zones names
String[][] zones = symbols.getZoneStrings();
// overwrite zone short names
for (int i = 0; i < zones.length; i++) {
    String zoneId = zones[i][0];
    if ("America/Los_Angeles".equals(zoneId)) {
        zones[i][2] = zoneId; // short name for standard time
        zones[i][4] = zoneId; // short name for Daylight Saving Time
    }
}
// update the symbols in the formatter
symbols.setZoneStrings(zones);
sdf.setDateFormatSymbols(symbols);

System.out.println(sdf.format(new java.util.Date()));

这将打印:

  

20171005 045317 America / Los_Angeles

请注意,我仅为America/Los_Angeles时区更改了区域名称。您可以修改if以更改所需的任何区域 - 或者只需删除if即可为所有区域进行更改。

另一个细节是您在几个小时内使用hhAccording to javadoc,这是上午/下午小时字段(值从1到12)。如果没有AM / PM指示符(模式a),输出可能不明确。如果需要,您可以将此更改为HH一天中的小时字段,值从0到23)或kk(值从1到24)。

Java新日期/时间API

旧类(DateCalendarSimpleDateFormat)有lots of problemsdesign issues,并且它们被新API取代

如果您使用 Java 8 ,请考虑使用new java.time API。它更容易,less bugged and less error-prone than the old APIs

如果您使用 Java 6或7 ,则可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。对于 Android ,您还需要ThreeTenABP(更多关于如何使用它here)。

以下代码适用于两者。 唯一的区别是包名称(在Java 8中为java.time而在ThreeTen Backport(或Android的ThreeTenABP中)为org.threeten.bp),但类和方法名称是一样的。

实际上,这个新的API非常简单,代码与@Juan和@Jens发布的其他答案完全相同。但这些之间存在细微差别。

假设我有2个不同的ZonedDateTime个对象:一个代表America/Los_Angeles时区中的当前日期/时间,另一个代表Asia/Tokyo中相同的当前日期/时间时区:

// current date/time
Instant now = Instant.now();
// get the same instant in different timezones
ZonedDateTime nowLA = now.atZone(ZoneId.of("America/Los_Angeles"));
ZonedDateTime nowTokyo = now.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println(nowLA); // 2017-10-05T05:04:31.253-07:00[America/Los_Angeles]
System.out.println(nowTokyo); // 2017-10-05T21:04:31.253+09:00[Asia/Tokyo]

这些日期是:

  

2017-10-05T05:04:31.253-07:00 [美国/洛杉矶]
  2017-10-05T21:04:31.253 + 09:00 [亚/东京]

现在让我们看看差异。 @Jens's answer在格式化程序中设置时区。这意味着格式化时所有日期都将转换为特定时区(我只是稍微修改了一下代码,直接设置了语言环境 - 而不是使用withLocale - 但生成的格式化程序是等效的) :

// set the timezone in the formatter
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd hhmmss VV", Locale.US)
    // use Los Angeles timezone
    .withZone(ZoneId.of("America/Los_Angeles"));
// it converts all dates to Los Angeles timezone
System.out.println(nowLA.format(fmt)); // 20171005 050431 America/Los_Angeles
System.out.println(nowTokyo.format(fmt)); // 20171005 050431 America/Los_Angeles

由于在格式化程序中设置了时区,因此两个日期都会转换为此时区(包括日期和时间值):

  

20171005 050431 America / Los_Angeles
  20171005 050431 America / Los_Angeles

@Juan's answer中,格式化程序没有设置时区,这意味着它会保留ZonedDateTime个对象中使用的时区:

// formatter without a timezone set
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd hhmmss VV", Locale.US);
// it keeps the timezone set in the date
System.out.println(nowLA.format(fmt)); // 20171005 050431 America/Los_Angeles
System.out.println(nowTokyo.format(fmt)); // 20171005 090431 Asia/Tokyo

现在保留时区(以及日期和时间值):

  

20171005 050431 America / Los_Angeles
  20171005 090431亚洲/东京

这是一个微妙的差异,你必须选择最适合你的情况的方法。请注意,hhHH相同的问题也适用于此:变量nowTokyo保存的值相当于21:04:31(9:04:31 PM 在东京),但它的格式为090431 - 没有AM / PM指示符,这个时间不明确,因此IMO模式应该使用HH(因此输出将为{ {1}})。但是由你决定。

In the javadoc您可以看到有关所有可用模式的详细信息。