我有一些TimeStamp,日期格式是"EEEE, MMM dd, yyyy hh:mm a zzz"。 但是我不知道如何显示带有时区的时间戳。 当我尝试显示它时,我得到了错误的DateTime或错误的时区

范例1:

Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date(device.getUpdatedDate().getTime())); // here is 2018-07-09 20:02:26.506000
SimpleDateFormat sdf = new SimpleDateFormat(EMAIL_DATE_FORMAT);
sdf.format(calendar.getTime()); // i have wrong timezone

我得到Monday, Jul 09, 2018 08:02 PM EEST

但是当我添加sdf.setTimeZone(TimeZone.getTimeZone("HST"));时,我有正确的时区和错误的时间

Monday, Jul 09, 2018 07:02 AM HST

预期结果Monday, Jul 09, 2018 08:02 PM HST

实际结果:Monday, Jul 09, 2018 08:02 PM EEST

或Monday, Jul 09, 2018 07:02 AM HST

好吧,您的代码中可能有错误。您想展示吗?这里的某些人能够找到他们正在查看的代码中的错误。但是没有代码,这很难做到……

到目前为止您尝试过的内容,请发布您的代码

请研究并应用制作最少可复制示例的概念。

请阅读MCVE链接,然后再尝试创建一个。

将一些样本数据集,输出和所需的输出进行比较。

@SharonBenAsher更新了问题

请注意,Date没有时区。请参阅有关java.util.Date的全部内容。 device.getUpdatedDate()也是Date吗?

我建议您避免使用Calendar,Date和SimpleDateFormat类。它们不仅已经过时,而且设计欠佳,特别是SimpleDateFormat麻烦。今天,我们在现代Java日期和时间API java.time中有了很多改进。

假设您有一个旧版Date对象,首先转换为现代的Instant类,然后从那里进行进一步的操作:device.getUpdatedDate().toInstant().atZone(ZoneId.of("PacificHonolulu")).format(DateTimeFormatter.ofPattern("EEEE, MMM dd, yyyy hh:mm a zzz", Locale.US))。

什么TimeStamp?我在您的示例代码中仅看到Calendar。

tl; dr

您看到的是功能,而不是错误,它会将同一时刻调整到另一个时区。

东欧的晚上8点,夏威夷的早上7点。

好。

使用实时区域名称,而不是3-4个字母的伪区域。

使用现代的java.time类,而不是设计不良的旧类。

好。

同一时刻:呼叫withZoneSameInstant

您从UTC之前3个小时的一个时区调整为比UTC落后10个小时的另一个时区,总共相差13个小时。了解一个地方的8 PM同时在另一个地方的7 AM。

好。

请注意以下代码中对withZoneSameInstant("即时")的调用。

好。

ZonedDateTime.of(
2018 , 7 , 9 , 20 , 2 , 0 , 0 , ZoneId.of("Europe/Athens" )  // 8 PM in Greece.
)
.withZoneSameInstant(                                             // Adjust into another time zone to view the same moment with another wall-clock time.
ZoneId.of("Pacific/Honolulu" )                               // 7 AM in Hawaii.
)
.toString()
2018-07-09T07:02-10:00[Pacific/Honolulu]
Ok.

不同的时刻:呼叫withZoneSameLocal

显然,您想要一个不同的时刻,时间轴上的一个不同的点,它具有相同的日期和时间,但是具有不同的时区。

好。

请注意以下代码中对withZoneSameLocal的调用("本地",而不是"即时")。

好。

ZonedDateTime.of(
2018 , 7 , 9 , 20 , 2 , 0 , 0 , ZoneId.of("Europe/Athens" )  // 8 PM in Greece.
)
.withZoneSameLocal(                                               // Different moment, coincidentally having the same date and same time-of-day. But different time zone means this is a different point on the timeline.
ZoneId.of("Pacific/Honolulu" )                               // Also 8 PM in Hawaii, which happens many hours later than 8 PM in Greece. Different moment, same wall-clock time.
)
.toString()
2018-07-09T20:02-10:00[Pacific/Honolulu]
Ok.

细节

实时区

HST和EEST是伪区域,不是实时区域。避免使用这3-4个字母代码,因为它们不是标准化的,甚至也不是唯一的(!)。

好。

使用IANA在tzdata中定义的实时区域名称。查看Wikipedia中的列表(可能已过时)。这些名称采用Continent/Region格式,例如America/Montreal或Europe/Tallinn。

好。

ZoneId z = ZoneId.of("Europe/Vilnius" ) ;

避免使用旧的日期时间类

避免麻烦的类Calendar和SimpleDateFormat。这些在几年前被java.time类所取代。使用java.time更加清晰和容易。

好。

让我们开始吧。我猜您通过EEST考虑到了东欧时区之一。我随便选一个。

好。

ZoneId zAthens = ZoneId.of("Europe/Athens" ) ;
LocalDate ld = LocalDate.of( 2018 , Month.JULY , 9 ) ;  // 2018-07-09.
LocalTime lt = LocalTime.of( 20 , 2 ) ;  // 8:02 PM.
ZonedDateTime zdtAthens = ZonedDateTime.of( ld , lt , zAthens ) ;

生成表示该ZonedDateTime对象值的String。默认情况下,明智地使用标准ISO 8601格式进行扩展,以将时区的名称附加在方括号中。

好。

String outputAthens = zdtAthens.toString() ;  // Generate `String` in a format extending standard ISO 8601 format.

2018-07-09T20:02+03:00[Europe/Athens]

Ok.

我猜你是指夏威夷时间。该区域的专有名称是Pacific/Honolulu。

好。

ZoneId zHonolulu = ZoneId.of("Pacific/Honolulu" ) ;

让我们将雅典的时刻调整到夏威夷的另一个区域。时间轴上的相同时刻,相同点,但挂钟时间不同。想象一下,每个地方都有一对朋友打电话给他们,同时抬头看着墙上的时钟。每个人看到的是不同的时间,可能还有不同的日期,尽管如此,他们在时间轴上遇到的是同一时刻,同一时间点。

好。

ZonedDateTime zdtHonolulu = zdtAthens.withZoneSameInstant( zHonolulu ) ;  // Same moment (same `Instant` inside the `ZonedDateTime`) but a different time zone.

那时,檀香山比世界标准时间晚10个小时,而雅典比世界标准时间早3个小时。那是总共十三个小时的增量。因此,晚上8点(20:00)减去13是早上7点。我们希望在夏威夷看到早上7点。让我们通过生成另一个ISO 8601格式的字符串来进行验证。

好。

String outputHonolulu = zdtHonolulu.toString() ;  // Generate `String` representing the value of the `ZonedDateTime` object.

2018-07-09T07:02-10:00[Pacific/Honolulu]

Ok.

果然是早上7点。

好。

也许您想要的是位于夏威夷的相同日期和同一时间。这意味着您不代表同一时刻。您将在时间轴上代表另一个时间点,相差几个小时。

好。

ZonedDateTime确实提供了此功能。从概念上调用ZonedDateTime::withZoneSameLocal的含义:使用相同的内部LocalDate和相同的内部LocalTime,但使用不同的分配的ZoneId。

好。

ZonedDateTime eightPmOnJuly9InPacificHonolulu = zdtAthens.withZoneSameLocal( zHonolulu) ;

String outputDifferentMoment= eightPmOnJuly9InPacificHonolulu.toString() ;

2018-07-09T20:02-10:00[Pacific/Honolulu]

Ok.

世界标准时间

在时区之间翻转的所有这些动作都可以驱动一个人。通过专注于UTC来扎根。可以将UTC视为唯一的真实时间,所有其他区域仅是变化而已。

好。

要从时区调整为UTC,请从我们的ZonedDateTime对象中提取一个Instant对象。根据定义,Instant始终为UTC。

好。

Instant instantAthens = zdtAthens.toInstant() ;

Instant instantHonolulu = zdtHonolulu.toInstant() ;

Instant instantDifferentMoment = eightPmOnJuly9InPacificHonolulu.toInstant() ;

2018-07-09T17:02:00Z

Ok.

2018-07-09T17:02:00Z

Ok.

2018-07-10T06:02:00Z

Ok.

最后的Z表示UTC,发音为Zulu,由ISO 8601和其他标准定义。

好。

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧的旧式日期时间类,例如java.util.Date,Calendar和SimpleDateFormat。

好。

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

好。

要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310。

好。

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

好。

在哪里获取java.time类?

好。

Java SE 8,Java SE 9,Java SE 10和更高版本

内置的

标准Java API的一部分,具有捆绑的实现。

Java 9添加了一些次要功能和修复。

好。

Java SE 6和Java SE 7

java.time的许多功能在ThreeTen-Backport中都被反向移植到Java 6和7。

好。

安卓系统

更高版本的Android捆绑了java.time类的实现。

对于早期的Android(<26),ThreeTenABP项目改编了ThreeTen-Backport(如上所述)。请参阅如何使用ThreeTenABP…。

好。

好。

ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如Interval,YearWeek,YearQuarter等。

好。

好。

据我了解,device.getUpdatedDate()返回一个java.sql.Timestamp,例如2018-07-09 20:02:26.506000。我假设(与时间戳的概念相反)此日期和时间是在没有时区的情况下产生的,但是您知道它是在太平洋/檀香山时区(夏威夷的标准时间或HST,全年在夏威夷使用)。

如果要从SQL数据库获取Timestamp(通常会如此),请不要。 Timestamnp类早已过时且令人困惑。假设您至少使用Java 8和JDBC 4.2,则可以从数据库中获取现代LocalDateTime的实例。假设rs是您的结果集,并且您的列名为updated_date(我相信您可以根据自己的情况定制代码):

DateTimeFormatter formatter
= DateTimeFormatter.ofPattern(EMAIL_DATE_FORMAT, Locale.US);
LocalDateTime updatedDateTime
= rs.getObject("updated_date", LocalDateTime.class);
ZonedDateTime timeOnHawaii
= updatedDateTime.atZone(ZoneId.of("Pacific/Honolulu"));
String formattedDateTime = timeOnHawaii.format(formatter);

如果您的时间戳记与时区信息一起存储在数据库中,则可以通过使用Instant.class来做得更好,并确保获取与数据库中相同的时间点。确切的可能性取决于JDBC驱动程序的功能。

如果您无法避免获取Timestamp,请像这样转换它:

ZonedDateTime timeOnHawaii = device.getUpdatedDate()

.toLocalDateTime()

.atZone(ZoneId.of("Pacific/Honolulu"));

使用您的示例Timestamp和上面第一个我的代码片段中的格式化程序,将输出:

Monday, Jul 09, 2018 08:02 PM HST

(假设EMAIL_DATE_FORMAT被定义为"EEEE, MMM dd, yyyy hh:mm a zzz")。

您的代码出了什么问题?

关于Timestamp的一个令人困惑的事情是,它表示一个时间点(无时区),但通常用于表示日期和时间(也没有时区)。因此,您的2018-07-09 20:02:26.506000的Timestamp确实拥有等于2018-07-09 20:02:26.506000东欧夏令时(EEST)的值(时间点),如Украина/ Ukraine中使用的),但device.getUpdatedDate()混淆地表示夏威夷的2018-07-09 20:02:26.506000。 Date和SimpleDateFormat都不会更改时间点,这就是为什么它们会给您Monday, Jul 09, 2018 08:02 PM EEST或等效的Monday, Jul 09, 2018 07:02 AM HST的原因。

链接:

在我对以下问题的回答中,更多关于Timestamp的混乱:Java-将java.time.Instant转换为java.sql.Timestamp(不带区域偏移)

Oracle教程:日期时间,说明如何使用java.time。