前端js的Date類型和后端SpringBoot的Date類型在網(wǎng)絡(luò)傳輸中是自動基于UNIX時間戳封裝的,因此只要對應(yīng)使用這兩種數(shù)據(jù)類型,就不會帶來時區(qū)差異
無論js,java,python,在未指定時區(qū)的情況下,將Date類型的對象格式化為string時,均會使用機器本地的時區(qū),可以通過修改電腦的時區(qū),再格式化同一個時間戳的Date對象來測試

在將一個字符串轉(zhuǎn)為Date對象時,也會默認使用當前電腦的時區(qū)。但需要注意的是,轉(zhuǎn)換成的Date對象本身是不帶時區(qū)屬性的,只是將字符串解釋成時間戳的過程中使用了本地時區(qū)
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Date d = formatter.parse("2023-06-06 10:48:24.476");
log.info("d timestamp = {}", d.getTime());
# 東八區(qū) d timestamp = 1686019704476
- mysql的datetime類型保存格式為
yyyy-MM-dd HH:mm:ss(如使用datetime(3)則為yyyy-MM-dd HH:mm:ss.SSS)的日期時間字符串,不帶時區(qū)信息,保存值以mysql server的時區(qū)信息為準。因此如果有多臺不同的mysql server,最好統(tǒng)一所有的時區(qū)為UTC

- 通過ORM將mysql數(shù)據(jù)庫里的datetime列轉(zhuǎn)為java中Date對象時,會使用字符串解析的方法封裝成Date類,當java向mysql數(shù)據(jù)庫傳遞Date類時,會將Date類轉(zhuǎn)為字符串datetime格式傳遞
當java應(yīng)用與mysql數(shù)據(jù)庫之間的連接有serverTimezone參數(shù)時,此處的server指的是mysql數(shù)據(jù)庫服務(wù)器,而連接到數(shù)據(jù)庫的應(yīng)用,雖然有可能是個SpringBoot服務(wù)器,但從ORM的角度依然是客戶端。serverTimezone參數(shù)會讓java應(yīng)用認為mysql數(shù)據(jù)庫的時區(qū)為該指定的時區(qū),故在根據(jù)yyyy-MM-dd HH:mm:ss字符串計算出時間戳后,會額外加減運行java應(yīng)用的當前電腦與此時區(qū)的時間差。
如數(shù)據(jù)庫在一臺東八區(qū)的電腦上,其中有·datetime·數(shù)據(jù)
mysql> SELECT * FROM date_test;
+----+-------------------------+
| id | create_time |
+----+-------------------------+
| 1 | 2023-06-06 10:48:24.476 |
+----+-------------------------+
用同一臺東八區(qū)的電腦上的SpringBoot應(yīng)用去連接它,連接配置為
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/spinq_cloud_computing?characterEncoding=utf-8&serverTimezone=UTC
數(shù)據(jù)庫映射類如下
package cn.spinq.cloud.mini.entity;
import lombok.Data;
import javax.persistence.*;
import java.util.Date;
@Entity
@Data
@Table(name = "date_test")
public class DateTest {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private long id;
@Column(name = "create_time", nullable = false, columnDefinition = "datetime(3)")
private Date createTime;
public DateTest() {
Date d = new Date();
createTime = d;
}
public DateTest(Date d) {
createTime = d;
}
運行如下測試
@Test
void DateDiffTest() throws ParseException {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Optional<DateTest> d1Op = dateTestRepository.findById(new Long(1));
Date d1 = d1Op.get().getCreateTime();
log.info("d1 is = {}, shown by str = {}", d1.getTime(), formatter.format(d1));
Date d2 = formatter.parse("2023-06-06 10:48:24.476");
log.info("unwritten d2 is = {}, shown by str = {}", d2.getTime(), formatter.format(d2));
DateTest d2Test = dateTestRepository.save(new DateTest(d2));
log.info("written d2 is = {}, shown by str = {}", d2Test.getCreateTime().getTime(), formatter.format(d2Test.getCreateTime()));
log.info("d2 is {} hours more than d1.", (d2.getTime() - d1.getTime())/(3600*1000));
}
獲取到結(jié)果
INFO main Method: DateDiffTest - d1 is = 1686048504476, shown by str = 2023-06-06 18:48:24.476
INFO main Method: DateDiffTest - unwritten d2 is = 1686019704476, shown by str = 2023-06-06 10:48:24.476
INFO main Method: DateDiffTest - written d2 is = 1686019704476, shown by str = 2023-06-06 10:48:24.476
INFO main Method: DateDiffTest - d1 is 8 hours more than d2.
可知此處SpringBoot應(yīng)用將mysql數(shù)據(jù)庫中id=1的2023-06-06 10:48:24.476當做了UTC時區(qū)的時間來處理,生成Date對象時為其多加了8個小時
查看數(shù)據(jù)庫,發(fā)現(xiàn)插入的第二條數(shù)據(jù)如下
mysql> SELECT * FROM date_test;
+----+-------------------------+
| id | create_time |
+----+-------------------------+
| 1 | 2023-06-06 10:48:24.476 |
| 2 | 2023-06-06 02:48:24.476 |
+----+-------------------------+
可知在插入d2的時候,因為java判斷mysql需要UTC的時間,因此將程序中當前時區(qū)的時間減去了8個小時,再存入了數(shù)據(jù)庫
- 當mysql數(shù)據(jù)庫時區(qū)和
serverTimezone固定,改變java應(yīng)用的時區(qū),發(fā)現(xiàn)均可向mysql server寫入正確的東八區(qū)時間字符串,讀出后,Date對象的時間戳值保持一致
固定mysql server時區(qū)為GMT+8,serverTimezone=Asia/Shanghai時,mysql中放人時間2023-01-01 00:00:00.000,當前電腦運行如下測試用例
@Test
void DateDiffTest() {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Optional<DateTest> d1Op = dateTestRepository.findById(new Long(1));
Date d1 = d1Op.get().getCreateTime();
log.info("d1 is = {}, shown by str = {}", d1.getTime(), formatter.format(d1));
}
可以發(fā)現(xiàn),字符串格式的時間不同,但時間戳保持一致
// 電腦時區(qū)設(shè)置為東八區(qū)
INFO main Method: DateDiffTest - d1 is = 1672502400000, shown by str = 2023-01-01 00:00:00.000
// 電腦時區(qū)設(shè)置為西五區(qū),包含夏令時
INFO main Method: DateDiffTest - d1 is = 1672502400000, shown by str = 2022-12-31 11:00:00.000
總結(jié)
-
javascript和java中Date類的默認封裝拆箱都可以獲取到正確的時間,無需做特殊處理【因為用json傳遞數(shù)據(jù),因此并不一定傳遞的真的是時間戳,也可能是帶了時區(qū)信息的字符串,此處還未抓包求證,但總之可能正常傳輸】 - 多地區(qū)提供服務(wù)時,應(yīng)該控制所有數(shù)據(jù)庫為同一時區(qū),并將jdbc連接的
serverTimezone參數(shù)設(shè)置為與數(shù)據(jù)庫時區(qū)一致,則不同時區(qū)的服務(wù)器都可以獲取正確的日期數(shù)據(jù)