如何统一时区

Web开发过程中如何统一时区?

Date-Time API

由主包 java.time 和四个子包组成

  • java.time: 表示日期和时间的API的核心。它包括日期,时间,日期和时间相结合的类别。这些类基于 ISO-8601 中定义的日历系统, 并且不可变(方法返回新的实例)且线程安全。
  • java.time.chrono: 用于表示除默认 ISO-8601 以外的日历系统的 API。您也可以定义自己的日历系统。
  • java.time.format: 用于格式化和分析日期和时间的类。
  • java.time.temporal: 扩展API主要用于框架和库编写器,允许日期和时间类之间的互操作,查询和调整。字段(TemporalField 和 ChronoField)和单位(TemporalUnit 和 ChronoUnit)在此包中定义。
  • java.time.zone: 支持时区的类,时区的偏移和时区规则。如果使用时区,大多数开发人员只需使用 ZonedDateTime 和 ZoneId 或 ZoneOffset。

无时区的日期时间类

  • LocalDateTime: 没有时区的日期时间。因为没有偏移量和时区等附加信息,所以它无法表示时间线上的瞬间。
  • Date:Date 对象没有时区概念,但 SimpleDateFormat 的 format、parse 方法有时区概念,SimpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+0:00"));

时区类、偏移量类

  • ZoneId: 时区ID,例如Europe/Paris。标识 Instant 和 LocalDateTime 之间转换的规则。
  • ZoneOffset: 与格林威治/UTC 的时区偏移量,例如+02:00。继承ZoneId

带时区或偏移量的日期时间类

  • ZonedDateTime: 带有时区的日期时间,结合了 LocalDateTime 和 ZoneId。它用于表示具有时区(地区/城市,如欧洲/巴黎)的完整日期(年,月,日) 和时间(小时,分钟,秒,纳秒)。此类处理从LocalDateTime的本地时间线到Instant的即时时间线的转换。两条时间线之间的差异是与 UTC/格林威治 的偏移量,由ZoneOffset表示。两条时间线之间的 转换涉及到偏移量计算,使用的是从ZoneId获取的ZoneRules来确定偏移量对于特定时区的变化方式。例如,大多数时区在将时钟向前移动到夏令时时遇到间隙(通常为1小时),并且在将时钟移回标准时间和 重复转换前的最后一个小时时,时间重叠。该ZonedDateTime类适应这种情况,而OffsetDateTime和OffsetTime类,它们不具备访问 ZoneRules。
  • OffsetDateTime: 带有偏移量的日期时间,不包含时区ID。结合了 LocalDateTime 和 ZoneOffset。OffsetDateTime、ZonedDateTime和Instant都以纳秒精度存储时间线上的瞬间。Instant 是最简单的,简单的代表瞬间。OffsetDateTime添加与 UTC/格林威治的偏移量,从而可以获取本地日期时间。ZonedDateTime添加完整的时区规则。ZonedDateTime或Instant旨在用于在更简单的应用程 序中对数据进行建模。当更详细地建模日期时间概念时,或者当与数据库或网络协议进行通信时,可以使用OffsetDateTime。
  • OffsetTime: 带有偏移量的时间,不包含 日期 和 时区ID。
  • Instant: 时间线上的瞬时点。无论是 ZonedDateTime 或 OffsetTimeZone 对象可被转换为 Instant 对象,因为都映射到时间轴上的确切时刻。 但是,相反情况并非如此。要将 Instant 对象 转换为 ZonedDateTime 或 OffsetDateTime 对象,需要提供时区或时区偏移信息。
  • Duration
  • Clock

Web开发时如何统一时区?

1、MySQL服务器、Web服务器 固定时区UTC,使用网络对时。

2、MySQL、JDBC连接、JVM 统一时区UTC。

3、后端开发人员可使用 带时区的日期时间类、无时区的日期时间类。

4、前端根据用户所在时区转换显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package com.zhaolq.spark;

import java.time.Clock;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;

/**
* <p>
* Date-Time API 由主包 java.time 和四个子包组成
* <ul>
* <li>java.time: 表示日期和时间的API的核心。它包括日期,时间,日期和时间相结合的类别。这些类基于 ISO-8601 中定义的日历系统, 并且不可变(方法返回新的实例)且线程安全。
* <li>java.time.chrono: 用于表示除默认 ISO-8601 以外的日历系统的 API。您也可以定义自己的日历系统。
* <li>java.time.format: 用于格式化和分析日期和时间的类。
* <li>java.time.temporal: 扩展API主要用于框架和库编写器,允许日期和时间类之间的互操作,查询和调整。字段(TemporalField 和 ChronoField)和单位(TemporalUnit 和 ChronoUnit)在此包中定义。
* <li>java.time.zone: 支持时区的类,时区的偏移和时区规则。如果使用时区,大多数开发人员只需使用 ZonedDateTime 和 ZoneId 或 ZoneOffset。
* </ul>
* <p>
* 无时区的日期时间类
* <ul>
* <li>LocalDateTime: 没有时区的日期时间。因为没有偏移量和时区等附加信息,所以它无法表示时间线上的瞬间。
* </ul>
* <p>
* 时区类、偏移量类
* <ul>
* <li>ZoneId: 时区ID,例如Europe/Paris。标识 Instant 和 LocalDateTime 之间转换的规则。
* <li>ZoneOffset: 与格林威治/UTC 的时区偏移量,例如+02:00。继承ZoneId
* </ul>
* <p>
* 带时区或偏移量的日期时间类
* <ul>
* <li>ZonedDateTime: 带有时区的日期时间,结合了 LocalDateTime 和 ZoneId。它用于表示具有时区(地区/城市,如欧洲/巴黎)的完整日期(年,月,日)
* 和时间(小时,分钟,秒,纳秒)。此类处理从LocalDateTime的本地时间线到Instant的即时时间线的转换。两条时间线之间的差异是与 UTC/格林威治 的偏移量,由ZoneOffset表示。两条时间线之间的
* 转换涉及到偏移量计算,使用的是从ZoneId获取的ZoneRules来确定偏移量对于特定时区的变化方式。例如,大多数时区在将时钟向前移动到夏令时时遇到间隙(通常为1小时),并且在将时钟移回标准时间和
* 重复转换前的最后一个小时时,时间重叠。该ZonedDateTime类适应这种情况,而OffsetDateTime和OffsetTime类,它们不具备访问 ZoneRules。
* <li>OffsetDateTime: 带有偏移量的日期时间,不包含时区ID。结合了 LocalDateTime 和 ZoneOffset。OffsetDateTime、ZonedDateTime和Instant都以纳秒精度存储时间线上的瞬间。Instant
* 是最简单的,简单的代表瞬间。OffsetDateTime添加与 UTC/格林威治的偏移量,从而可以获取本地日期时间。ZonedDateTime添加完整的时区规则。ZonedDateTime或Instant旨在用于在更简单的应用程
* 序中对数据进行建模。当更详细地建模日期时间概念时,或者当与数据库或网络协议进行通信时,可以使用OffsetDateTime。
* <li>OffsetTime: 带有偏移量的时间,不包含 日期 和 时区ID。
* </ul>
* <p>
* Instant: 时间线上的瞬时点。无论是 ZonedDateTime 或 OffsetTimeZone 对象可被转换为 Instant 对象,因为都映射到时间轴上的确切时刻。 但是,相反情况并非如此。要将 Instant 对象
* 转换为 ZonedDateTime 或 OffsetDateTime 对象,需要提供时区或时区偏移信息。
* <p>
* Duration
* <p>
* Clock
*
* <p>
* <p>
* <p>
* Web开发时如何统一时区?
* <ul>
* <li>1、MySQL服务器、Web服务器 固定时区UTC,使用网络对时。
* <li>2、MySQL、JDBC连接、JVM 统一时区UTC。
* <li>3、后端开发人员可使用 带时区的日期时间类、无时区的日期时间类。
* <li>4、前端根据用户所在时区转换显示。
* </ul>
* <p>
*
* @Author zhaolq
* @Date 2024/4/15 16:06:59
*/
public class Test {
public static void main(String[] args) {
LocalDateTime localDateTime = LocalDateTime.now(); // 首先固定一个不带任何时区的时间


// DateTimeFormatter
System.out.println("用于格式化或解析不带偏移量的日期时间: " + DateTimeFormatter.ISO_LOCAL_DATE_TIME); // 例如“2011-12-03T10:15:30”
System.out.println("格式化为不带偏移量的日期时间: " + localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); // 例如“2011-12-03T10:15:30”
System.out.println("格式化为常规的日期时间: " + localDateTime.format(DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")));
System.out.println();


// ZoneId,时区ID,例如Europe/Paris
System.out.println("获取系统默认ZoneId实例: " + ZoneId.systemDefault());
System.out.println("从时区ID获取ZoneId实例: " + ZoneId.of("Asia/Shanghai"));
System.out.println("获取包含偏移量的ZoneId实例: " + ZoneId.ofOffset("UTC", OffsetDateTime.now().getOffset()));
System.out.println("从临时对象获取ZoneId实例: " + ZoneId.from(ZonedDateTime.now())); // 虽然方法入参是TemporalAccessor,但只接受带时区的类型,LocalXXX是不行的。
System.out.println("从临时对象获取ZoneId实例: " + ZoneId.from(OffsetDateTime.now()));
System.out.println("从临时对象获取ZoneId实例: " + ZoneId.from(ZoneOffset.UTC));
System.out.println("获取可用时区ID的集合: " + ZoneId.getAvailableZoneIds());
System.out.println();

// ZoneRules
System.out.println("伦敦是否是夏令时: " + ZoneId.of("Europe/London").getRules().isDaylightSavings(Instant.now()));
System.out.println();


// ZoneOffset,与格林威治/UTC的时区偏移量,例如+02:00 。 继承自 ZoneId(时区,内置夏令时处理规则),所以也可当作一个ZoneId来使用的,当然并不建议这么做,请独立使用。
System.out.println("使用ID获取ZoneOffset实例: " + ZoneOffset.of("+8"));
System.out.println("使用ID获取ZoneOffset实例: " + ZoneOffset.of("+08:00"));
System.out.println("使用以小时为单位的偏移量获取ZoneOffset实例: " + ZoneOffset.ofHours(+8));
System.out.println("使用以小时和分钟为单位的偏移量获取ZoneOffset实例: " + ZoneOffset.ofHoursMinutes(8, 00));
System.out.println("使用以小时、分钟和秒为单位的偏移量获取ZoneOffset实例: " + ZoneOffset.ofHoursMinutesSeconds(8, 00, 00));
System.out.println("获取指定总偏移量(以秒为单位)的ZoneOffset实例: " + ZoneOffset.ofTotalSeconds(28800));
System.out.println("获取UTC时区偏移量的ZoneOffset实例:" + ZoneOffset.UTC);
System.out.println("获取最大偏移量的ZoneOffset实例:" + ZoneOffset.MAX);
System.out.println("获取最小偏移量的ZoneOffset实例:" + ZoneOffset.MIN);
System.out.println("从时间对象获取ZoneOffset的实例: " + ZoneOffset.from(ZonedDateTime.now()));
System.out.println("从时间对象获取ZoneOffset的实例: " + ZoneOffset.from(OffsetDateTime.now()));
System.out.println("从时间对象获取ZoneOffset的实例: " + ZonedDateTime.now().getOffset());
System.out.println("从时间对象获取ZoneOffset的实例: " + OffsetDateTime.now().getOffset());
System.out.println();


// ZonedDateTime,在设计方面,此类应主要被视为LocalDateTime和ZoneId的组合,但实际上相当于三个独立对象的状态 LocalDateTime、ZoneId、ZoneOffset
System.out.println("从默认时区的系统时钟获取当前日期时间: " + ZonedDateTime.now());
System.out.println("获取时区偏移量: " + ZonedDateTime.now().getOffset());
System.out.println("将此日期时间转换为OffsetDateTime: " + ZonedDateTime.now().toOffsetDateTime());
System.out.println("从Instant和时区ID获取ZonedDateTime的实例: " + ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("America/Los_Angeles")));

ZonedDateTime zonedDateTime = ZonedDateTime
.of(localDateTime, ZoneId.of("Asia/Shanghai")) // 给一个不带时区的时间附上"任意"时区
.withYear(2099); // 更改年份后的offsetDateTime副本
System.out.println("上海时间: " + zonedDateTime);
System.out.println("伦敦时间: " + zonedDateTime.withZoneSameInstant(ZoneId.of("Europe/London"))); // 把带时区的时间转换成目标时区的时间。调用了 toEpochSecond 把当前的时间纳秒 结合 指定的偏移量换算成新的纳秒
System.out.println("更改时区: " + zonedDateTime.withZoneSameLocal(ZoneId.of("Europe/London"))); // 不会换算时间,只是把时区更改了

System.out.println("所在月最后一个周四的zonedDateTime副本: " + zonedDateTime.with(TemporalAdjusters.lastInMonth(DayOfWeek.THURSDAY)));
System.out.println("所在月最后一个周日的zonedDateTime副本: " + zonedDateTime.with(TemporalAdjusters.dayOfWeekInMonth(-1, DayOfWeek.SUNDAY)));
System.out.println("所在月的第一个周日的zonedDateTime副本: " + zonedDateTime.with(TemporalAdjusters.dayOfWeekInMonth(1, DayOfWeek.SUNDAY)));
System.out.println();


// OffsetDateTime,结合了 LocalDateTime 和 ZoneOffset
System.out.println("从默认时区的系统时钟获取当前日期时间: " + OffsetDateTime.now());
System.out.println("获取时区偏移量: " + OffsetDateTime.now().getOffset());
System.out.println("将此日期时间转换为ZonedDateTime: " + OffsetDateTime.now().toZonedDateTime());
System.out.println("从Instant和时区ID获取OffsetDateTime的实例: " + OffsetDateTime.ofInstant(Instant.now(), ZoneId.of("America/Los_Angeles")));

OffsetDateTime offsetDateTime = OffsetDateTime
.of(localDateTime, ZoneOffset.of("+08:00")) // 给一个不带时区的时间附上"任意"偏移量
.withYear(2099);
System.out.println("+08:00上海时间: " + offsetDateTime);
System.out.println("+01:00伦敦时间: " + offsetDateTime.withOffsetSameInstant(ZoneOffset.of("+01:00"))); // 把带偏移量的时间转换成目标偏移量的时间
System.out.println("+01:00改偏移量: " + offsetDateTime.withOffsetSameLocal(ZoneOffset.of("+01:00"))); // 不会换算时间,只是把偏移量更改了

System.out.println("所在月最后一个周四的offsetDateTime副本: " + offsetDateTime.with(TemporalAdjusters.lastInMonth(DayOfWeek.THURSDAY)));
System.out.println("所在月最后一个周日的offsetDateTime副本: " + offsetDateTime.with(TemporalAdjusters.dayOfWeekInMonth(-1, DayOfWeek.SUNDAY)));
System.out.println("所在月的第一个周日的offsetDateTime副本: " + offsetDateTime.with(TemporalAdjusters.dayOfWeekInMonth(1, DayOfWeek.SUNDAY)));
System.out.println();


// Instant 表示不带时区的即时时间点
// 自 Java 时代开始以来发生了多少秒
System.out.println("使用1970-01-01T00:00:00Z纪元的秒数获取Instant的实例: " + Instant.ofEpochSecond(0L));
System.out.println("以指定单位计算距另一时刻的时间量: " + Instant.ofEpochSecond(0L).until(Instant.now(), ChronoUnit.SECONDS));
System.out.println("获取从Java纪元1970-01-01T00:00:00Z开始的秒数: " + Instant.now().getEpochSecond());
System.out.println("从系统时钟获取当前时刻: " + Instant.now());
System.out.println("从系统时钟获取当前时刻: " + Instant.now().atZone(ZoneId.of("Asia/Shanghai")));
System.out.println("从系统时钟获取当前时刻: " + Instant.now().atZone(ZoneId.of("America/Los_Angeles")));
System.out.println("从指定时钟获取当前时刻: " + Instant.now(Clock.systemDefaultZone()));
System.out.println("从指定时钟获取当前时刻: " + Instant.now(Clock.systemUTC()));
System.out.println("从指定时钟获取当前时刻: " + Instant.now(Clock.system(ZoneId.of("America/Los_Angeles"))));
System.out.println();

}
}