提问人:redoc01 提问时间:11/9/2023 最后编辑:Bohemianredoc01 更新时间:11/15/2023 访问量:93
计划给定的时间,但在 SQL Server 中使用不同的时区并使用 Java 客户端
Schedule a time given but works with different time zones in SQL Server and using Java client
问:
我需要允许系统在本地安排一些时间,并在不同的时区正确显示此时间。
因此,如果我在英国安排一个下午 1 点到下午 3 点之间的时间,那么如果我在美国看到这个时间表,那么如您所知,美国提前 7 小时,因此美洲的时间表时间将是晚上 7 点到晚上 10 点
我之前在sql server中使用过datetimeoffset,它允许UTC日期使用不同的时区。
我真的需要使用sql server来解决这个问题。
那么,当用户选择开始日期和结束日期时,我该如何采用这种方法来获取时差呢?
我将使用的客户端是 java 来显示不同时区的开始和结束日期。
我在sql服务器中尝试过这个,但我使用了getutcdate。这是另一个项目在最初存储某个日期时初始化的,温已进行输入。但就像我上面说的,我需要用户在开始日期和结束日期之间选择日期。
请注意,存储日期的数据库提前 1 小时,因为它位于另一个国家/地区的服务器上。
答:
您尚未指定是否要跟踪时间轴上的特定点,或者是否要跟踪一天中的时间,如果政客更改了您的时区规则,该时间会根据需要进行调整。第一种情况(如火箭发射)更简单,但我感觉你的意思是第二种情况(如预约医疗/牙科预约)。让我们来介绍一下。
固定时间点
为了安排火箭发射,飞行工程师研究天气预报。一旦他们选择了一个合适的发射时间,时间线上的那个点是固定的,不变的。如果政客们改变了时区的规则,发射就不会移动。即使周边地区的挂钟时间可能从 15:33 更改为 14:33,计划的时刻也保持不变。天气和火箭,不在乎一天中的时间,他们关心的是时间轴上的一个点。
如果要跟踪时间轴上的特定点,请使用标准 SQL 类型。在 MS SQL Server 中,这将是 datetimeoffset
类型。在 Java 中,通常使用 Instant
,但对于 JDBC 数据库调用,请使用 OffsetDateTime
。TIMESTAMP WITH TIME ZONE
数据类型 | 标准 SQL | MS SQL 服务器 | 爪哇岛 |
---|---|---|---|
时间线上的点 | TIMESTAMP WITH TIME ZONE |
datetimeoffset |
通常使用 Instant 。在 JDBC 中,使用 OffsetDateTime 。 |
期间 | VARCHAR |
varchar |
Duration |
假设我们的火箭发射飞行指挥员已经确定发射时间为 2024 年 1 月 23 日下午 3:33,与 UTC 的时间子午线相差 0 小时-分钟-秒。在标准 ISO 8601 文本中,这将是 .末尾表示偏移量为零,发音为“Zulu”。2024-01-23T15:33:00Z
Z
java.time 类在解析/生成文本时默认使用 ISO 8601 格式。
Instant launchWindowStart = Instant.parse( "2024-01-23T15:33:00Z" ) ;
想象一下,发射窗口长达一小时。我们使用 Duration 类来表示未附加到时间线的时间跨度。
Duration launchWindowSpan = Duration.ofHours( 1 ) ;
我们可以确定结束时间:
Instant launchWindowEnd = launchWindowStart.plus( launchWindowSpan ) ;
在我们的数据库中,我们可以存储开始和跨度,或者开始和结束,或者三者兼而有之(尽管这三者都会违反规范化)。对于 JDBC,将对象调整为 ,因为 SQL 标准没有概念映射到 。Instant
OffsetDateTime
Instant
OffsetDateTime odtLaunchWindowStart = launchWindowStart.atOffset( ZoneOffset.UTC ) ;
OffsetDateTime odtLaunchWindowEnd = launchWindowEnd.atOffset( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odtLaunchWindowStart ) ;
myPreparedStatement.setString( … , launchWindowSpan.toString() ) ;
myPreparedStatement.setObject( … , odtLaunchWindowEnd ) ;
检索。
Instant launchWindowStart = myResultSet.getObject( … , OffsetDateTime.class).toInstant() ;
Duration launchWindowSpan = Duration.parse( myResultSet.getString() ) ;
Instant launchWindowEnd = myResultSet.getObject( … , OffsetDateTime.class).toInstant() ;
移动时间点
对于约会,您需要将日期和时间与预期的时区分开存储。所以表中有两列。
对于MS SQL Server中的日期和时间,请使用type。在 Java 中,LocalDateTime
.datetime2
对于时区,请存储区域的大洲/区域
名称,例如 。在 Java 中,ZoneId
.Europe/London
仅记录约会的开始时间,而不记录停止时间。存储持续时间,而不是停止时间。停止时间可能不是您期望的时间。例如,如果约会从下午 1 点开始,但恰好发生在“春季”时钟转换期间,则停止时间可能是下午 4 点,而不是您期望的下午 3 点,因此 2 小时的会议可能会在下午 1 点到 4 点运行。
SQL Standard 和 MS SQL Server 均未提供持续时间的数据类型。因此,以标准 ISO 8601 格式存储为文本。对于数据库中的持续时间,以标准 ISO 8601 格式存储为文本,其中标记开始,并将年-月-日与小时-分钟-秒分开。对于 Java,请使用 Duration
对象。两个小时的持续时间是 。PnYnMnDTnHnMnS
P
T
PT2H
数据类型 | 标准 SQL | MS SQL 服务器 | 爪哇岛 |
---|---|---|---|
日期 + 时间 | TIMESTAMP WITHOUT TIME ZONE |
datetime2 |
LocalDateTime |
时区名称 | VARCHAR |
varchar |
ZoneId |
期间 | VARCHAR |
varchar |
Duration |
在英国下午1点至3点之间
举个例子,让我们从今天开始预订一周。
ZoneId zLondon = ZoneId.of( "Europe/London" ) ;
LocalDate today = LocalDate.now( zLondon ) ;
LocalDate apptDate = today.plusWeeks( 1 ) ;
LocalTime apptTime = LocalTime.of( 13 , 0 ) ; // 1 PM.
LocalDateTime apptStart = LocalDateTime.of( apptDate , apptTime ) ;
Duration duration = Duration.ofHours( 2 ) ;
将这些内容写入 、 和 列。datetime2
VARCHAR
VARCHAR
myPreparedStatement.setObject( … , apptStart ) ;
myPreparedStatement.setString( … , zLondon.toString() ) ;
myPreparedStatement.setString( … , duration.toString() ) ;
检索。
LocalDateTime apptStart = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId zLondon = ZoneId.of( myResultSet.getString( … ) ) ;
Duration duration = Duration.parse( myResultSet.getString( … ) ) ;
在这个关键点上要非常清楚:&不代表一个时刻,不是时间线上的一个点。它们仅代表一个带有一天中时间的日期。他们故意缺乏任何时区或与UTC的偏移量的概念。因此,他们的价值观本质上是模棱两可的。LocalDateTime
datetime2
当需要制定时间表时,将时区应用于日期时间。非常重要:不要存储此值,仅将其用于动态、临时地构建计划,例如向用户展示。
ZonedDateTime zdtLondon = appt.atZone( zLondon ) ;
现在我们有一个时刻,时间线上的一个特定点。
您可以调整到其他时区。
ZoneId zEdmonton = ZoneId.of( "America/Edmonton" ) ;
ZonedDateTime zdtEdmonton = zdtLondon.atZoneSameInstant( zEdmonton ) ;
和 表示相同的同时时刻,时间轴上的同一点。通过两个不同的镜头来看待这一时刻,英国人民使用的挂钟和日历的镜头与加拿大阿尔伯塔省人民使用的挂钟和日历的镜头。zdtLondon
zdtEdmonton
通常最好使用半开方法处理时间跨度。在《半开》中,开头是包容的,而结尾是排他性的。因此,从下午 1 点到下午 3 点的任命从第 13 小时的第一刻开始,然后一直持续到第 15 小时的第一刻,但不包括(大约,取决于上面提到的政治计时异常)。提示:在 SQL 中,不要在 Half-Open 查询中使用,因为 Fully Closed 也是如此(start 和 end 都包括在内)。BETWEEN
BETWEEN
为了表示整个时间跨度,java.time 框架没有定义任何此类。你可以定义你自己的。例如:
record SpanOfTimeZoned ( ZonedDateTime start , ZonedDateTime end ) {}
或者更实用:
public record SpanOfTimeZoned( ZonedDateTime start , ZonedDateTime end ) {
public static SpanOfTimeZoned of ( ZonedDateTime start , Duration duration ) {
return new SpanOfTimeZoned ( start , start.plus ( duration ) );
}
public Duration duration ( ) {
return Duration.between ( this.start , this.end );
}
}
用法:
SpanOfTimeZoned apptSpan = SpanOfTimeZoned.of( zdtEdmonton , duration ) ;
或者,您可能会发现将 ThreeTen-Extra 库添加到您的项目中很有用。该库提供 Interval
类,其中包含一对 java.time.Instant
对象。An 表示与 UTC 的时间子午线偏移量为零时分秒的时刻。Instant
Interval apptSpan = Interval.of( zdtEdmonton.toInstant() , duration ) ;
请注意,它始终采用 UTC(偏移量为零),而不是特定时区。当然,您可以始终应用时区来获取 .org.threeten.extra.Interval
ZonedDateTime
ZonedDateTime apptStartLondon = apptSpan.getStart().atZone( zLondon ) ;
你说:
我将使用的客户端是 java 来显示不同时区的开始和结束日期。
在您的应用中,您需要从用户那里收集:
- 日期
- 开始时间
- 时区
- 持续时间(或根据用户提供的结束时间计算)
由此,您将在 Java 中生成:
LocalDateTime
对象ZoneId
对象Duration
对象
听起来您希望在存储到数据库中之前从用户的时区转换为首选时区。请注意,这种转换是有风险的。例如,我们可以从埃德蒙顿时间调整为伦敦时间。但是,这种调整必须使用目前这两个区域的规则。在这一刻和那次约会之间的时间里,如果艾伯塔省的政客或英格兰的政客要改变他们的时区规则,那么从池塘另一边的人的角度来看,我们存储的约会似乎是错误的。政客们的这种改变似乎不太可能;历史证明并非如此。世界各地的政客都表现出了摆弄其管辖范围内时区规则的偏好。
LocalDateTime userLdt = LocalDateTime.of( … ) ;
ZoneId userZoneId = ZoneId.of( … ) ;
Duration duration = Duration.of( … ) ;
ZonedDateTime userZdt = userLdt.atZone( userZoneId ) ;
ZonedDateTime zdtLondon = userZdt.atZoneSameInstant( zLondon ) ;
LocalDateTime apptStart = zdtLondon.toLocalDateTime() ;
此时,您可以存储 、 和,如本答案前面所述。apptStart
zLondon
duration
评论
java.time.ZonedDateTime
。与数据库通信时,在 UTC 和用户的时区之间进行转换。或者找到一种方法将时区存储在数据库中。另外,为了以防万一,请注意时区与偏移量不是一回事。