安排给定的时间,但适用于 SQL Server 中的不同时区并使用 Java 客户端

red*_*c01 -1 java sql sql-server

我需要允许系统在本地安排一些时间并在不同时区正确显示该时间。

因此,如果我在英国安排下午 1 点到下午 3 点之间的时间,那么如果我要在美国看到这个时间表,那么如您所知,美国会提前 7 小时,因此美国的安排时间将为下午 7 点到晚上 10 点

我之前在 sql server 中使用过 datetimeoffset ,它允许 UTC 日期适用于不同的时区。

我确实需要使用 sql server 来解决这个问题。

那么,当用户选择开始日期和结束日期时,我将如何采用这种方法来获取时间差呢?

我将使用的客户端是java来显示不同时区的开始和结束日期。

我已经在 sql server 中尝试过这个,但我使用了 getutcdate。这是为了另一个项目在输入条目后最初存储某个日期时初始化。但正如我上面所说,我需要用户在开始日期和结束日期之间选择日期。

请注意,存储日期的数据库提前 1 小时,因为它位于另一个国家/地区的服务器上。

Bas*_*que 7

您尚未指定是否要跟踪时间轴上的特定点,或者是否要跟踪当政客更改您所在时区规则时根据需要进行调整的时间。第一种情况(如火箭发射)更简单,但我明白你的意思是第二种情况(如预约医疗/牙科预约)。让我们涵盖两者。

\n

固定时间点

\n

为了安排火箭发射,飞行工程师研究天气预报。一旦他们选择了合适的发射时间,时间线上的那个点就固定不变了。如果政治家改变时区规则,发射就不会改变。尽管周边地区的挂钟时间可能从 15:33 更改为 14:33,但计划时间保持不变。天气和火箭并不关心一天中的时间,他们关心的是时间线上的一个点。

\n

如果您要跟踪时间线上的特定点,请使用标准 SQL 类型TIMESTAMP WITH TIME ZONE。在MS SQL Server中,就是这种类型datetimeoffset。在 Java 中,Instant一般使用,但对于JDBC数据库调用,请使用OffsetDateTime.

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
数据种类标准SQL微软SQL服务器爪哇
时间线上的点TIMESTAMP WITH TIME ZONEdatetimeoffset一般使用Instant.
在 JDBC 中,使用OffsetDateTime.
期间VARCHARvarcharDuration
\n
\n

假设我们的火箭发射飞行指挥已确定发射时间为 2024 年 1 月 23 日下午 3:33,与 UTC 时间子午线的偏移为零时分秒。在标准 ISO 8601 文本中,这将是2024-01-23T15:33:00Z. 末尾的Z表示偏移量为零,发音为 \xe2\x80\x9cZulu\xe2\x80\x9d。

\n

java.time类在解析/生成文本时默认使用 ISO 8601 格式

\n
Instant launchWindowStart = Instant.parse( "2024-01-23T15:33:00Z" ) ; \n
Run Code Online (Sandbox Code Playgroud)\n

想象一下发射窗口长一小时。我们使用 Duration 类来表示与时间线无关的时间跨度。

\n
Duration launchWindowSpan = Duration.ofHours( 1 ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

我们可以确定结束时间:

\n
Instant launchWindowEnd = launchWindowStart.plus( launchWindowSpan ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

在我们的数据库中,我们可以存储开始和跨度,或开始和结束,或所有三个(尽管所有三个都会违反规范化)。对于 JDBC,请将Instant对象调整为OffsetDateTime,因为 SQL 标准没有到 的概念映射Instant

\n
OffsetDateTime odtLaunchWindowStart = launchWindowStart.atOffset( ZoneOffset.UTC ) ;\nOffsetDateTime odtLaunchWindowEnd = launchWindowEnd.atOffset( ZoneOffset.UTC ) ;\n\nmyPreparedStatement.setObject( \xe2\x80\xa6 , odtLaunchWindowStart ) ;\nmyPreparedStatement.setString( \xe2\x80\xa6 , launchWindowSpan.toString() ) ;\nmyPreparedStatement.setObject( \xe2\x80\xa6 , odtLaunchWindowEnd ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

恢复。

\n
Instant launchWindowStart = myResultSet.getObject( \xe2\x80\xa6 , OffsetDateTime.class).toInstant() ;\nDuration launchWindowSpan = Duration.parse( myResultSet.getString() ) ;\nInstant launchWindowEnd = myResultSet.getObject( \xe2\x80\xa6 , OffsetDateTime.class).toInstant() ;\n
Run Code Online (Sandbox Code Playgroud)\n

移动时间点

\n

对于约会,您需要将日期和时间与预期时区分开存储。所以你的表中有两列。

\n

对于 MS SQL Server 中的日期和时间,请使用datetime2type。在Java中,LocalDateTime.

\n

对于时区,存储Continent/Region区域名称,例如Europe/London。在Java中,ZoneId.

\n

仅记录约会的开始时间,而不是停止时间。存储一个持续时间而不是停止时间。停止时间可能不是您期望的时间。例如,如果约会在下午 1 点开始,但碰巧发生在“提前”时钟切换期间,则停止时间可能是下午 4 点,而不是您预期的下午 3 点,因此 2 小时的会议可能会持续 1-下午4点。

\n

SQL Standard 和 MS SQL Server 都不提供持续时间的数据类型。因此以标准 ISO 8601 格式存储为文本。对于数据库中的持续时间,以标准ISO 8601格式存储为文本,PnYnMnDTnHnMnS其中P标记开始,并将T年-月-日与小时-分钟-秒分开。对于 Java,请使用Duration对象。持续时间为两个小时PT2H

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n
数据种类标准SQL微软SQL服务器爪哇
日期 + 时间TIMESTAMP WITHOUT TIME ZONEdatetime2LocalDateTime
时区名称VARCHARvarcharZoneId
期间VARCHARvarcharDuration
\n
\n
\n

在英国安排下午 1 点至 3 点之间的时间

\n
\n

举个例子,让我们从今天开始预订一周。

\n
ZoneId zLondon = ZoneId.of( "Europe/London" ) ;\nLocalDate today = LocalDate.now( zLondon ) ;\nLocalDate apptDate = today.plusWeeks( 1 ) ;\nLocalTime apptTime = LocalTime.of( 13 , 0 ) ;  // 1 PM.\nLocalDateTime apptStart = LocalDateTime.of( apptDate , apptTime ) ;\nDuration duration = Duration.ofHours( 2 ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

将它们写入您的datetime2VARCHAR、 和VARCHAR列。

\n
Instant launchWindowStart = Instant.parse( "2024-01-23T15:33:00Z" ) ; \n
Run Code Online (Sandbox Code Playgroud)\n

恢复。

\n
Duration launchWindowSpan = Duration.ofHours( 1 ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

请非常清楚这个关键点:LocalDateTime&datetime2不代表某个时刻,也不是时间轴上的一个点它们仅代表日期和时间。他们故意缺乏任何时区或与 UTC 的偏移量的概念。所以他们的价值观本质上是模糊的。

\n

当需要制定时间表时,将时区应用于日期时间。非常重要:不要存储该值,仅使用它来临时动态构建时间表,例如用于呈现给用户。

\n
ZonedDateTime zdtLondon = appt.atZone( zLondon ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们有一个时刻,时间轴上的一个特定点。

\n

您可以调整到另一个时区。

\n
Instant launchWindowEnd = launchWindowStart.plus( launchWindowSpan ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

和 都zdtLondon代表zdtEdmonton同一时刻,即时间线上的同一点。通过两个不同的镜头来观察这一单一时刻,英国人使用的挂钟和日历的镜头与加拿大艾伯塔省人民使用的挂钟和日历的镜头。

\n

通常最好使用半开放方法来处理时间跨度。在半开中,开始是包容性的,而结束是排他性的。因此,从下午 1 点到下午 3 点的约会从 13 点的第一时刻开始,然后一直到但不包括 15 点的第一时刻(大约如此,取决于上述政治计时异常情况) 。提示:在 SQL 中,不要BETWEEN半开放查询中使用BETWEEN全封闭查询(包括开始和结束)。

\n

为了表示整个时间跨度,java.time框架没有定义任何此类。您可以定义自己的。例如:

\n
record SpanOfTimeZoned ( ZonedDateTime start , ZonedDateTime end ) {}\n
Run Code Online (Sandbox Code Playgroud)\n

或者更实用:

\n
OffsetDateTime odtLaunchWindowStart = launchWindowStart.atOffset( ZoneOffset.UTC ) ;\nOffsetDateTime odtLaunchWindowEnd = launchWindowEnd.atOffset( ZoneOffset.UTC ) ;\n\nmyPreparedStatement.setObject( \xe2\x80\xa6 , odtLaunchWindowStart ) ;\nmyPreparedStatement.setString( \xe2\x80\xa6 , launchWindowSpan.toString() ) ;\nmyPreparedStatement.setObject( \xe2\x80\xa6 , odtLaunchWindowEnd ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

用法:

\n
Instant launchWindowStart = myResultSet.getObject( \xe2\x80\xa6 , OffsetDateTime.class).toInstant() ;\nDuration launchWindowSpan = Duration.parse( myResultSet.getString() ) ;\nInstant launchWindowEnd = myResultSet.getObject( \xe2\x80\xa6 , OffsetDateTime.class).toInstant() ;\n
Run Code Online (Sandbox Code Playgroud)\n

或者,您可能会发现将ThreeTen-Extra库添加到您的项目中很有用。该库提供了Interval包含一对java.time.Instant对象的类。AnInstant表示与 UTC 时间子午线偏移零小时-分钟-秒的时刻。

\n
Interval apptSpan = Interval.of( zdtEdmonton.toInstant() , duration ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,org.threeten.extra.Interval始终采用 UTC(偏移量为零),而不是特定时区。当然,您始终可以应用时区来获取ZonedDateTime.

\n
ZonedDateTime apptStartLondon = apptSpan.getStart().atZone( zLondon ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

你说:

\n
\n

我将使用的客户端是java来显示不同时区的开始和结束日期。

\n
\n

在您的应用程序中,您需要从用户收集:

\n
    \n
  • 日期
  • \n
  • 开始时间
  • \n
  • 时区
  • \n
  • 持续时间(或根据用户提供的结束时间计算)
  • \n
\n

由此,您将用 Java 生成:

\n
    \n
  • LocalDateTime目的
  • \n
  • ZoneId目的
  • \n
  • Duration目的
  • \n
\n

听起来您想在存储到数据库之前将用户的时区转换为您的首选时区。请注意,这种转换是有风险的。例如,我们可以从埃德蒙顿时间调整为伦敦时间。但这种调整必须使用这两个区域目前存在的规则。在这一刻和那个任命之间的时间里,如果艾伯塔省的政治家或英格兰的政治家要改变他们的时区规则,那么从池塘另一边的人的角度来看,我们存储的任命将显得是错误的。政客们的这种改变似乎不太可能。历史证明并非如此。世界各地的政治家都表现出了玩弄其管辖范围内的时区规则的倾向。

\n
ZoneId zLondon = ZoneId.of( "Europe/London" ) ;\nLocalDate today = LocalDate.now( zLondon ) ;\nLocalDate apptDate = today.plusWeeks( 1 ) ;\nLocalTime apptTime = LocalTime.of( 13 , 0 ) ;  // 1 PM.\nLocalDateTime apptStart = LocalDateTime.of( apptDate , apptTime ) ;\nDuration duration = Duration.ofHours( 2 ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

此时您可以存储apptStart, zLondon, 和duration如本答案前面所讨论的。

\n

  • @DaleK因为SQL标准和MS SQL Server都不提供持续时间的数据类型。因此以标准 ISO 8601 格式存储为文本。SQL 标准几乎没有涉及日期时间问题,即使如此,它们也做得很差。 (2认同)