Java - 将时间戳转换为日期并返回时间戳会更改日期

Java - convert timestamp to Date and back to timestamp changes the date

我正在从当前时间创建一个字符串,我想再次将它转换为时间戳,但问题是,它在转换时减去了 2 小时。

这是我正在执行的步骤 -

    DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder().append(DateTimeFormatter.ofPattern("uuuu-MM-dd")).appendLiteral(" ")
                .append(DateTimeFormatter.ofPattern("HH:mm:ss")).parseLenient();
    long ts = Clock.systemUTC().millis();
    System.out.println(ts);
    Instant instant = Instant.ofEpochMilli(ts);
    ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
    String str = zonedDateTime.format(dateTimeFormatterBuilder.toFormatter());


    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    try {
        long timestamp = simpleDateFormat.parse(str).getTime();
        System.out.println(timestamp);
    } catch (ParseException e) {
        e.printStackTrace();
    }

输出- 1639065502667 1639058302000

(2021-12-09 15:58:22 2021-12-09 13:58:22)

为什么两个小时相差这么大? 我怎样才能解析它以使输出相等?

让我猜猜,你的时区是 UTC+2?

simpleDateFormat.parse(str) 假设您的日期在当前系统时区,但它是 UTC。

dateTimeFomatter 生成器使用没有毫秒和时区的格式。
这就是 str 值不包含有关它们的信息的原因。

然后 simpleDateFormat.parse(str) 使用 JVM 的时区,在本例中为 UTC+02:00

跟踪发生了什么:

Instant instant = Instant.now(); 
    // => 2021-12-09 15:58:22.798 +00:00

String str = zonedDateTime.format(dateTimeFormatterBuilder.toFormatter());
    // => "2021-12-09 15:58:22"

simpleDateFormat.parse(str);
    // => 2021-12-09 15:58:22.000 +02:00

您只需修复模式(添加毫秒 .SSS 和时区 XXX 部分)以使结果与预期一致:

DateTimeFormatter.ofPattern("HH:mm:ss.SSSXXX")

// and something similar for SimpleDateFormat if needed

从自定义格式的字符串解析 Instant。

此示例说明如何从序列化时间解析 Instant,假设所有情况都有固定时区。

var serializedDateTime = "2020-01-01 10:20:30";
var zoneId = ZoneOffset.UTC; // may be any other zone
    
var format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
var instant = LocalDateTime
                 .parse(serializedDateTime, format)
                    // LocalDateTime is unzoned, just representation of (date + time) numbers in a single object 
                 .atZone(zoneId)  
                    // apply a zone to get ZonedDateTime (point in space-time on Earth)
                 .toInstant(); 
                    // convert to Instant (the same point adjusted to UTC+00:00)

答案只是将时区设置为 UTC -

DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder().append(DateTimeFormatter.ofPattern("uuuu-MM-dd")).appendLiteral(" ")
            .append(DateTimeFormatter.ofPattern("HH:mm:ss")).parseLenient();
long ts = Clock.systemUTC().millis();
System.out.println(ts);
Instant instant = Instant.ofEpochMilli(ts);
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
String str = zonedDateTime.format(dateTimeFormatterBuilder.toFormatter());


SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
    *******
    simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    *******
    long timestamp = simpleDateFormat.parse(str).getTime();
    System.out.println(timestamp);
} catch (ParseException e) {
    e.printStackTrace();
}

tl;博士

试图理解 DateCalendarSimpleDateFormat 是在浪费大量时间。仅使用它们的替代品,即 java.time classes.

LocalDateTime                        // Represent a date with time-of-day, but lacking the context of a time zone or offset-from-UTC. So *not* a moment, *not* a point on the timeline.
.parse(                              // Parse your text string input into a date-time object.
    "2021-12-09 15:58:22"            // Your input of date with time-of-day but no offset/zone.
    .replace( " " , "T" )            // Replace SPACE with a `T` to comply with standard ISO 8601 format.
)                                    // Returns a `LocalDateTime` object.
.atZone(                             // Place that date-with-time into the context a particular time zone.
    ZoneId.of( "America/Montreal" )  // Specify a time zone by its `Continent/Region` name.
)                                    // Returns a `ZonedDateTime` object, a date with time-of-day as seen through the wall-clock time used by the people of a particular region. This *does* represent a moment, *is* a specific point on the timeline.
.toInstant()                         // Adjust from time zone to UTC (an offset of zero hours-minutes-seconds). This represents the very same moment as the `ZonedDateTime` object above, but as seen through a different wall-clock time.
.toEpochMilli()                      // Get a count of milliseconds from first moment of 1970 in UTC (1970-01-01T00:00Z) to the moment of our `Instant` object (& `ZonedDateTime` object).

看到这个 code run live at IdeOne.com。在那里你可以点击fork复制,修改,然后运行。

1639083502000

避免遗留日期时间 classes

关于您关于两个小时差异的具体问题,明显的原因是时区差异。

解析不完整信息

你的解析,SimpleDateFormat("yyyy-MM-dd HH:mm:ss") 是一个通配符。结果将是一个 java.util.Date 对象,它代表一个时刻,一个带有偏移量为零的时间的日期。但是您的输入缺少偏移量或区域的指示符。由于 ,您正在使用部分输入进行解析,信息不完整。

因此将应用一些默认 zone/offset。我不知道具体是什么区域或偏移量,因为我不在乎。那个可怕的 class 有可悲的缺陷,应该避免。

此外,Date class 的另一个问题是它的 toString 方法具有应用 JVM 当前默认时区以偏离 UTC 的反特征class 表示的值。非常令人困惑,因为这会产生该区域是 Date 对象一部分的错觉,但事实并非如此。正如我所说,一个可怕的 class,可悲的缺陷。

仅使用 java.time classes。

java.time

了解带有时间的日期本质上是模棱两可的,不是片刻。

如果您跟踪的是 9 日下午 4 点,我们不知道那是指日本东京下午 4 点、法国图卢兹下午 4 点还是美国俄亥俄州托莱多下午 4 点——三个截然不同的时刻发生了几个小时分开。

LocalDateTime ldt = LocalDateTime.parse( "2021-12-09 16:00:00" ) ;

要跟踪时刻,时间线上的一个点,您必须在与 UTC 或时区的偏移量的上下文中放置新的时间日期。

偏移量只是比现代计时基准线(格林威治皇家天文台的本初子午线)提前或落后的小时-分钟-秒数。

时区更多。时区是特定地区的人们使用的偏移量的过去、现在和未来变化的命名历史。每个区域都有一个格式为 Continent/Region 的名称,例如 Europe/BerlinAsia/Tokyo.

要跟踪 UTC 中的时刻,偏移量为零,请使用 Instant

Instant instant = Instant.now() ;

要通过某个地区人们使用的挂钟时间查看同一时刻,请应用 ZoneId 以获得 ZonedDateTime

ZoneId z = ZoneId.of( "America/Edmonton" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

至于你使用SimpleDateFormatDateCalendar,不要。避免使用这些遗留的日期时间 classes。嘿,它们是由不了解日期时间处理的人设计的。多年前,它们被 JSR 310 中定义的现代 java.time classes 所取代。Sun、Oracle 和 JCP 社区都放弃了那些 class是的。我建议你也这样做。

在您的代码中:

long ts = Clock.systemUTC().millis();
Instant instant = Instant.ofEpochMilli(ts);

这和这样做是一样的:

Instant.now().truncatedTo( ChronoUnit.MILLISECONDS )

在您的代码中:

ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);

(A) 如果仅使用偏移量而不是时区,请使用 OffsetDateTime class。 ZonedDateTime class 用于处理时区。

(B) 上面显示了从 Instant 调整到分区时刻的更简单方法:

myInstant.atZone( z )