我希望传递一个日期字符串并检查该日期是否早于当前 MST 时间。
我希望能够在本地和部署时可靠地测试它。
我本地位于英国。当我部署它时,服务器位于凤凰城。
英国现在的时间是2024-06-28 15.45.00
,当我传入时,此逻辑当前对 isValidDate 产生 true 2024-06-28 15.00.00
。
但是我将区域设置为 MST。我以为这是错误的。MST
时间现在是早上 8 点。所以不是之前。它似乎继续与英国时间相反。
我该如何更新它,以便在部署时,它会根据 MST 时间检查日期字符串。并在本地继续运行 MST?本质上,如果我最终在澳大利亚的另一台服务器上,逻辑应该继续根据 MST 时间工作。
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH.mm.ss");
private static final ZoneId MST_TIMEZONE = ZoneId.of("America/Phoenix");
// Spring bean set to Clock.systemDefaultZone(); Can't change this.
// using a clock for unit testing purposes.
private final Clock clock;
private ZonedDateTime parseDate(String dateStr) {
try {
return LocalDateTime
.parse(dateStr, DATE_FORMATTER)
.atZone(MST_TIMEZONE);
} catch (DateTimeParseException e) {
return null;
}
}
private boolean isValidDate(String startDateTime) {
ZonedDateTime start = parseDate(startDateTime);
return start != null
&& start.isBefore(LocalDateTime.now(clock).atZone(MST_TIMEZONE));
}
3
最佳答案
4
我认为你在这里遇到的问题是这样的:
LocalDateTime.now(clock).atZone(MST_TIMEZONE)
根据运行它的 JVM 的时区,这将执行不同的操作。
LocalDateTime.now(clock)
将为您提供 JVM 时区中的本地时间 – 因为我们都在伦敦,所以我们假设2024-06-28 16:46:23
。调用atZone(MST)
会为您提供 ,ZonedDateTime
即2024-06-28 16:46:23 -08:00
。
如果你在凤凰城的服务器上运行该程序,LocalDateTime.now(clock)
就会得到2024-06-28 08:46:23
;调用atZone(MST)
该程序会给你2024-06-28 08:46:23 -08:00
。
如果您的目的是获取当前时间MST_TIMEZONE
,请将其更改为:
clock.now().atZone(MST_TIMEZONE)
clock.now()
为您提供一个Instant
,它是与时区无关的类型。Instant
与我上面写的时间相对应的是Instant.ofSeconds(1719593183L)
。将其转换为ZonedDateTime
可得到LocalDateTime
该时区的 ,加上时区。
1
-
1调用
LocalDateTime.now
很令人困惑,因为该类不能表示时刻。而且这种调用是不必要的。有更好的方法可用。
–
|
总结
LocalDateTime // Represent a date with time-of-day but lacking the context of an offset-from-UTC or a time zone. Does *not* represent a moment, is *not* a point on the timeline.
.parse(
"2024-06-28 15.00.00" , // Best to *not* invent custom formats for such textual inputs. Better to exchange date-time values textually using only ISO 8601 standard formats.
DateTimeFormatter.ofPattern( "uuuu-MM-dd HH.mm.ss" )
) // Returns a `LocalDateTime` object.
.atZone( ZoneId.of( "America/Phoenix" ) ) // Returns a `ZonedDateTime` object. Determines a moment, a point on the timeline, by applying the context of a time zone.
.toInstant() // Returns a `Instant` object. Same point on the timeline, but seen through an offset-from-UTC of zero hours-minutes-seconds.
.isBefore( Instant.now() ) // Compares to the current moment as seen in UTC (an offset of zero).
UTC 是你的朋友
来思考和工作,即日期时间时刻与UTC 时间子午线之间的
因此我们将您的问题重新定义为两个阶段:
- 我们收到一个包含日期和时间的文本输入。不幸的是,这个输入有两个缺陷。(A)文本不是标准格式。(B)文本表示特定时区()的挂钟/日历中显示的时刻,
America/Phoenix
但这个关键事实并未包含在输入文本中。 - 我们必须将此文本输入所代表的时刻与当前时刻进行比较。我们想知道预期的时刻是过去还是将来。
ISO 8601
解决输入错误的理想方法是教育数据发布者了解 标准。共享的输入2024-06-28 15.00.00
则不是2024-06-28T15:00:00-07:00
。
OffsetDateTime
java.time类默认使用 ISO 8601 格式。因此,这样的标准字符串可以直接解析,而无需定义任何格式模式。我们得到一个对象。
OffsetDateTime odt = OffsetDateTime.parse( "2024-06-28T15:00:00-07:00" ) ;
使用 ISO 8601 标准格式来更正已发布的数据确实是理想的解决方案。ISO 8601 的发明专门用于以文本形式交换日期时间值,这种方式既适合机器,也适合跨文化的人类。
LocalDateTime
如果无法修复输入,那么我们必须使用表示日期和时间的类来解析输入,但缺少偏移量或时区的上下文。该类将是。
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd HH.mm.ss" ) ;
LocalDateTime ldt = LocalDateTime.parse( "2024-06-28 15.00.00" , f ) ;
请注意,LocalDateTime 并不代表某个时刻,也不是时间线上的一个点。
ZonedDateTime
我们已经确保输入的日期和时间文本可以通过时区看到America/Phoenix
。因此,指定该时区以创建对象。
ZoneId z = ZoneId.of( "America/Phoenix" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;
Instant
现在我们有一个时刻,时间线上的一个点。从该时区调整为 UTC,即我们的唯一真实时间。
调整为 UTC 的最简单方法是提取一个Instant
对象。该类是java.timeInstant
框架的构建块。它表示以 UTC 表示的时刻,始终以 UTC 表示。
Instant instant = zdt.toInstant() ;
捕捉当前瞬间。
Instant now = Instant.now() ;
比较。
Boolean isPast = instant.isBefore( now ) ;
关注 UTC
检查该日期是否早于当前 MST 时间。
不。改变你的思维,关注 UTC。
因此,我们需要检查过去的时刻是否在当前时刻之前。
伪时区
移动性
“MST” 不是真正的时区。该文本是一个伪时区,仅暗示了真正的时区,并表明该日期和时间是否遵守夏令时 (DST)。此类伪时区不标准化;它们甚至不是唯一的!
在您的讨论和逻辑中仅使用真实时区。伪时区应仅用于生成的本地化文本中,以显示给用户。
实时区域的名称格式为Continent/Region
。
永不打电话LocalDateTime.now
LocalDateTime.now(
我无法想象在什么情况下调用LocalDateTime.now
是正确的或最佳的做法。“现在”意味着你打算在时间线上指定一个特定的点。类LocalDateTime
不能表示时间线上的某个特定点。
将服务器设置为 UTC
基本上,如果我最终进入澳大利亚的另一台服务器
服务器的默认时区应设置为 UTC。
另外,请记住,服务器可以有多个默认时区。其中包括操作系统中的默认时区、任何数据库服务器中的默认时区以及 JVM 中的默认时区。
您应该始终以这样的方式编写代码,避免依赖外部因素,例如主机操作系统的当前默认时区。您不希望您的代码仅因为某些系统管理员更改时区设置而中断。关键是始终明确指定所需/预期时区;始终传递可选时区或偏移参数,而不是隐式依赖默认值。
请注意,此答案中的示例代码如何不受操作系统默认区域更改的影响。
2
-
输入 2024-06-28 15.00.00 共享改为 2024-06-28T15:00:00-07:00。这样更好
2024-06-28T15:00:00-07:00[America/Phoenix]
。
– -
@g00se 附加时区名称是明智的,但不符合标准。ISO 8601 仅处理偏移量,而不处理时区。这里也没有必要,因为时区仅在进行日期时间数学运算、增加/减去时间量时才有用。
–
|
您需要确保所有与时间相关的操作都使用MST时区完成。这可以通过为ZoneId
您的Clock
对象设置适当的设置并在该时区内执行所有时间比较来实现。
这是代码的更新版本,它确保无论在何处运行,日期字符串都能被解析并与MST时间正确进行比较:
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH.mm.ss");
private static final ZoneId MST_TIMEZONE = ZoneId.of("America/Phoenix");
private final Clock clock;
public DateValidator(Clock clock) {
this.clock = clock;
}
private ZonedDateTime parseDate(String dateStr) {
try {
return LocalDateTime
.parse(dateStr, DATE_FORMATTER)
.atZone(MST_TIMEZONE);
} catch (DateTimeParseException e) {
return null;
}
}
public boolean isValidDate(String startDateTime) {
ZonedDateTime start = parseDate(startDateTime);
if (start == null) {
return false;
}
// Get the current time in MST
ZonedDateTime nowInMST = ZonedDateTime.now(clock.withZone(MST_TIMEZONE));
return start.isBefore(nowInMST);
}
public static void main(String[] args) {
Clock clock = Clock.systemDefaultZone();
DateValidator validator = new DateValidator(clock);
// Testing with a date string
String dateStr = "2024-06-28 15.00.00";
boolean isValid = validator.isValidDate(dateStr);
System.out.println("Is valid date: " + isValid);
}
希望能帮助到你
|
我推荐一个更通用的 SpringBoot 应用程序解决方案
application.yaml
创建一个 Bean,该 Bean 是根据中定义的参数进行配置的ZonedId
。该 Bean 调用类似ZoneId.of(zoneId)
- 创建从该bean
Clock
配置的第二个 BeanZoneId
然后,在任何服务类中,您都需要注入一个时钟,clock
并确保它具有正确的区域。然后您可以像这样使用它:ZonedDateTime.now(clock)
当您想要编写一个依赖于时钟的单元测试时,您可以使用“固定时钟”,例如,Clock.fixed(Instant.parse("2021-01-01T08:20:50Z"), ZoneOffset.UTC)
它为您的测试提供可预测和可测试的时钟/区域值。
以下是如何在 Kotlin 中实现 (1) 和 (2);是的,我知道您的问题是 Java,但我相信您可以从中弄清楚这个想法:
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Clock
import java.time.ZoneId
@Configuration
class ClockConfig {
@Bean
fun zoneId(@Value("\${zone-id}") zoneId: String): ZoneId {
return ZoneId.of(zoneId)
}
@Bean
fun clock(zoneId: ZoneId): Clock {
return Clock.system(zoneId)
}
}
1
-
这可能有效,但相信 OP 正在寻找 Java。另外,有一条评论提到已经有一个指向系统默认值的时钟 bean,无法更改。如果采用这种方法,可能需要将其设为主要。
–
|
parseDate
返回了一个ZonedDateTime
;您isValidDate
接受了一个称为的值*DateTime
。您应该选择能够准确描述您正在做的事情的名称,尤其是在日期和时间这种容易混淆的空间中。–
–
java.time.Instant
是你的朋友。–
|