我正在创建一个后端服务,该服务应该向第三方服务发出一些请求并根据响应进行更新。单一的工作是由数千个微小的工作组成的。主要工作以及每个子工作的确切begin时间应存储在报告中。主要作业也可以从网络用户界面运行,这意味着其他时区取决于登录的用户。endbeginend
最初的想法(和实现)是在主作业的一开始就存储ZoneId(如果作业按计划启动,则存储在本地服务器中;如果用户手动启动作业,则存储在登录用户中),然后用于LocalDateTime存储begin和end主要工作和子工作。或者在这种情况下我应该使用Instant而不是LocalDateTime?
private ZoneId zoneId;
private LocalDateTime begin;
private LocalDateTime end;
Run Code Online (Sandbox Code Playgroud)
我不确定这是否是正确的使用方式,或者我应该为每个ZoneId使用它,并忘记独立的属性。ZonedDateTimebeginendZoneId
private ZonedDateTime begin;
private ZonedDateTime end;
Run Code Online (Sandbox Code Playgroud)
编辑:只是为了为未来的读者提供更多信息:
情况 1将是 cron 作业,它将每天在某个时间运行一次(具体时间并不重要,但每天的同一时间)并且报告将显示“开始作业于”、“完成作业于”。
情况 2是当单击“立即运行更新”时,相同的作业将运行并生成相同的报告。
LocalDateTime或多或少准确地表达了它的名字。它代表人类计算时间概念(例如,年、月、日、小时等。与计算机计算时间概念“自某个纪元以来的毫秒”形成对比)。
而且根本没有时区。因此,给定一个LocalDateTime对象,不可能将其与纪元(例如您从 获得的System.currentTimeMillis())或另一个 LDT 对象进行比较,除非您可以保证它源自同一时区。在你的问题文本中,你不是- 这取决于系统是否启动了作业(在这种情况下,你得到系统默认的 tz。这也可以改变!),或者用户启动它(在这种情况下,你得到他们的兹)。因此,如果您的系统中有 2 个 LDT,您无法判断哪一个出现在之前或之后。
您甚至不需要涉及多个 TZ。我们所需要的只是夏令时。
假设我在欧洲/阿姆斯特丹做某事:我开始一份工作。
您使用 LDT 并尽职尽责地记录它:
ZoneId getUserZone() {
return ZoneId.of("Europe/Amsterdam");
}
ZoneId zone = getUserZone();
LocalDateTime eventStamp = LocalDateTime.now(zone);
logSystem.write(eventStamp, "User logged in");
Run Code Online (Sandbox Code Playgroud)
半个小时后,我退出了。相同的代码。
一天后,您检查日志消息。你见证了这一点:
[2023-March-26 02:40] User logged in
[2023-March-26 02:10] User logged out
Run Code Online (Sandbox Code Playgroud)
现在什么?注销事件怎么会出现在具有较早时间戳的日志文件中?就这个问题而言,为什么日志文件的词汇顺序与时间戳顺序不一致?我……我发明了时光机吗?
不 - 实行夏令时。时钟拨回一小时。
LDT 不存储任何内容。
因此,用它们来精确计时是很糟糕的事情。
它们代表即兴言论,例如“我出生于 1975 年 4 月 14 日”。一般来说,你不会用“哦,真的吗?”来回答这个问题。那么,哪个时区呢?如果我于 4 月 14 日 23:00 出生在纽约,然后搬到欧洲,那么我不会在 15 日开始庆祝生日。即使我们进行精确的时区簿记,我也应该这样做;纽约比欧洲早6到7个小时,所以你出生的时候,是欧洲15日凌晨05:00 。
尽管如此,您仍然没有更新您的生日。因此,如果您将“出生”跟踪为 ZonedDateTime,然后尝试计算ZonedDateTime.now("Europe/Amsterdam")您的出生日期之间的年差,那么在 15 日,它会增加 1。这是错误的。
这是一个语义模型(你的生日用年、月和日期来表示 - 并且没有时区组件,并且没有人像它一样行事),它说:使用LocalDate/LDT代表我。
相比之下,假设您预约了 2026 年 4 月 30 日下午 1 点在阿姆斯特丹的理发店。如果您将其存储为 LocalDateTime 2026-04-30 13:00:00,那么如果您不在阿姆斯特丹,那么当您试图告诉您距理发预约还有多少小时时,任何闹钟或议程系统都会陷入混乱。
如果您将其存储为 epochmillis,那也是错误的。想象一下(这一点也不奇怪,欧盟已经决定必须实行夏令时,只是还没有时间表)荷兰政治会议厅敲响了“从 2025 年开始,我们将永久保持冬季”的锤子。假设您有一个时钟,它正在滴答作响,直至预约理发师。
当锤子敲下的那一刻,时钟应该将“预约理发师的时间”准确地调低 1 小时。因为您的约会仍然是“2026 年 4 月 30 日下午 1 点在阿姆斯特丹”。但现在要提前一个小时。
因此,存储约会尖叫:“使用 ZonedDateTime”。因为 ZDT会正确更新(不是随着锤子落下,而是随着 tzdata 文件更新以反映该政治决策)。
日志事件发生在准确的时刻。因此,then 的正确表示既不是 ZDT 也不是 LDT。它是java.time.Instant。
然而,将 Instant 转换为 ZDT 并返回(过去发生的事件)通常是安全的。因此,如果 Instant 不在表中,ZDT 在语义上是错误的,但不会导致错误。
瞬间也没有时区,但它们确实代表了一个确切的时刻。这使得它们具有可比性(时钟来回移动并不重要 - 瞬间根本不“做”时区,并且地球上每个人的 java 虚拟机都返回相同的long值,加上由于时钟不存在而减去一些值当您致电System.currentTimeMillis()或时,完全正确Instant.now()。
当向用户呈现日志文件时,人类往往非常不擅长将“此日志写于 1698159191068。我们喜欢那些年份之类的”。但是,您存储的语义概念是一个Instant. 因此,存储它,并在将其呈现给用户时,将该时刻转换为合适的值。例如,在向用户显示日志时,使用用户的已知时区将该 Instant 转换为 LDT 并不疯狂。
| 归档时间: |
|
| 查看次数: |
139 次 |
| 最近记录: |