将非 UTC 日期字符串解析为 UTC Instant,并在另一个时区打印出来

Parse non-UTC date string as UTC Instant, and print it out in another TimeZone

提问人:peter.petrov 提问时间:4/21/2023 最后编辑:Anonymouspeter.petrov 更新时间:4/29/2023 访问量:297

问:

我正在使用 Java 11。

我有一个日期时间字符串,看起来像这样

“2023-02-17T03:56:50.254”

但我知道它代表了 UTC 时区的一个时间点。

考虑到它实际上是 UTC 格式,我如何解析这个字符串(解析必须独立于我的默认 JVM 时区)?

然后我如何将其转换为另一个表示相同日期/时间的字符串,例如美国/纽约时区?

这里有很多解析示例,但似乎没有一个完全代表我的情况。

java datetime time-time-parsesing datetime-conversion

评论

0赞 Jon Skeet 4/21/2023
“然后我该怎么做” - 请将自己限制在每篇文章一个问题上。我怀疑有很多关于如何将即时转换为特定时区的帖子。
1赞 Jon Skeet 4/21/2023
“是的,但此瞬间不包含 Z 后缀”——即时没有 Z 或没有 Z。当您将原始字符串转换为 Instant 时,该原始字符串的格式完全无关紧要......所以这确实是一个单独的问题,不应该在同一篇文章中提出。
2赞 Jon Skeet 4/21/2023
@g00se:那将是一个糟糕的方法,因为会错误地解释它。扫地工的答案是正确的。atZone(ZoneId.systemDefault())
2赞 Jon Skeet 4/21/2023
@g00se:我认为你误解了这个问题。不得调整字符串,因为它已表示 UTC 值。Sweeper 的回答没有这样做,因为它不应该这样做。
1赞 Anonymous 4/21/2023
@g00se您正在进行与我认为要求的相反的转换(无论如何您是正确的,如果默认时区在相关日期已经是 UTC,则实际上不会发生转换)。

答:

4赞 Sweeper 4/21/2023 #1

首先,将字符串解析为 ,因为这就是字符串中的全部内容 - 日期和时间。LocalDateTime

然后,您可以将其转换为 a 或 ,具体取决于您的解释 - 这是区域中的日期和时间,还是时间点。ZonedDateTimeInstantUTC

var parsedResult =
    LocalDateTime.parse(string/*, DateTimeFormatter.ISO_DATE_TIME*/)
        .toInstant(ZoneOffset.UTC); // for an Instant
//      .atZone(ZoneOffset.UTC); // for a ZonedDateTime

在你拥有其中任何一个之后,你可以分别使用 / 将其放入另一个区域。withZoneSameInstantatZone

// if parsedResult is Instant
var atAnotherZone = parsedResult.atZone(anotherZone);
// if parsedResult is ZonedDateTime
var atAnotherZone = parsedResult.withZoneSameInstant(anotherZone);

评论

1赞 Jon Skeet 4/21/2023
哦,我错过了 LocalDateTime 上的继承方法。这比通过 ZonedDateTime 要好。toInstant
2赞 Anonymous 4/21/2023
这里没有用处,但根据进一步的要求,可以考虑获得一个 .但前提是 an 不足以满足这种情况。ZonedDateTime.atOffset(ZoneOffset.UTC)OffsetDateTimeInstant
1赞 peter.petrov 4/21/2023 #2

感谢您的所有评论和回答。把它们放在一起,我认为这就是我需要的。

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class Main001 {

    public static void main(String[] args) {
        
        // Do first test with a winter date
        String text = "2023-02-17T03:56:50.254";
                
        LocalDateTime local = LocalDateTime.parse(text);
        ZonedDateTime zdt = local.atZone(ZoneId.of("UTC"));
        Instant instant = zdt.toInstant();
        
        System.out.println(instant);
        
        DateTimeFormatter formatter =
                DateTimeFormatter
                .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
                .withZone(ZoneId.of("America/New_York"));
        
        System.out.println(formatter.format(instant));
        
        // Do second test with a summer date
        text = "2023-07-17T03:56:59.257";
        
        local = LocalDateTime.parse(text);
        zdt = local.atZone(ZoneId.of("UTC"));
        instant = zdt.toInstant();
        
        System.out.println(instant);
        
        formatter =
                DateTimeFormatter
                .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
                .withZone(ZoneId.of("America/New_York"));
        System.out.println(formatter.format(instant));

    }

}

输出:

2023-02-17T03:56:50.254Z
2023-02-16T22:56:50.254
2023-07-17T03:56:59.257Z
2023-07-16T23:56:59.257

评论

2赞 Anonymous 4/21/2023
我推荐(当然还有)。在 UTC 中用于日期和时间是矫枉过正的。OffsetDateTime odt = local.atOffset(ZoneOffset.UTC);Instant instant = odt.toInstant();ZonedDateTime
2赞 Basil Bourque 4/22/2023 #3

tl;博士

LocalDateTime                           // Represent a date with time-of-day, but lacking the context of a time zone or offset from UTC.
.parse( "2023-02-17T03:56:50.254" )     // Parse text in standard ISO 8601 format.
.atOffset(                              // Assign an offset to this date-with-time.
    ZoneOffset.UTC                      // Constant for an offset-from-UTC of zero hours-minutes-seconds.
)                                       // Returns an `OffsetDateTime` object.
.atZoneSameInstant(                     // Adjust from a moment as seen with an offset of zero to being seen with a time zone some number of hours-minutes-seconds ahead/behind UTC.
    ZoneId.of( "America/New_York" )     // Represent a time zone. Name is in format of Continent/Region. 
)                                       // Returns a `ZonedDateTime` object.
.toString()                             // Generate text in standard ISO 8601 format extended wisely to append the name of the zone in square brackets.

请参阅在 Ideone.com 上运行的此代码。请注意日期和时间与输入的日期和时间有何不同。

2023-02-16T22:56:50.254-05:00[America/New_York]

偏移量与区域

根据 Ole V.V. 的评论,这里的适当类是 而不是 .OffsetDateTimeZonedDateTime

  • 偏移量只是 UTC 时间子午线之前或之后的小时、分钟和秒数。
  • 时区过去、现在和未来对特定地区人民使用的偏移量的命名历史,由他们的政治家决定。以 的格式命名。Continent/Region

java.time 类提供了两个类来表示每个概念:

  • ZoneOffset
  • ZoneId

java.time 类提供了两个类来表示以每种方式看到的某个时刻。

  • OffsetDateTime
  • ZonedDateTime

顺便说一句,Instant 类是 java.time 中一个更基本的构建块类,表示以 UTC 格式显示的时刻,始终以 UTC 格式表示(偏移量为零)。

LocalDateTime

所以,是的,将您的输入解析为日期和一天中的时间,一个 .LocalDateTime

LocalDateTime ldt = LocalDateTime.parse( "2023-02-17T03:56:50.254" ) ;

代表时刻,因为它缺少偏移或区域的上下文。LocalDateTime

ZoneOffet & OffsetDateTime

你说:

我如何解析这个字符串,考虑到它实际上是在UTC中

如果确定日期和时间旨在表示“UTC”中的时刻,即偏移量为零小时-分钟-秒,则应用 ZoneOffset 而不是 ZoneId 来生成 OffsetDateTime 而不是 ZonedDateTime

Java 附带了一个为零偏移量定义的常量 ZoneOffset.UTC

OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ;

ZoneId & ZonedDateTime

你问:

然后我如何将其转换为另一个表示相同日期/时间的字符串,例如美国/纽约时区?

定义所需的时区。用于区域(仅用于偏移量)。ZoneIdZoneOffset

ZoneId zoneId = ZoneId.of( "America/New_York" ) ;

应用该时区以生成 .ZonedDateTime

ZonedDateTime zdt = odt.atZoneSameInstant( zoneId ) ;

在此示例中,odtzdt 表示相同的同时时刻,即时间轴上的同一点。那一刻就是方法名称和类中“Instant”的含义。和变量在挂钟时间和日历上有所不同。就像冰岛雷克雅未克的某个人给加利福尼亚州洛杉矶的某个人打电话一样——当双方同时瞥一眼挂在各自墙上的时钟和日历时,他们会看到不同的时间和日期。atZoneSameInstantInstantodtzdt

相反,命名的变量表示时刻,也不是时间轴上的一个点。该类故意缺少时区或偏移量的上下文。因此,该类不能代表某个时刻。LocalDateTimeldtLocalDateTime