網(wǎng)頁應(yīng)用的多時區(qū)日期格式的處理

  1. 前端js的Date類型和后端SpringBoot的Date類型在網(wǎng)絡(luò)傳輸中是自動基于UNIX時間戳封裝的,因此只要對應(yīng)使用這兩種數(shù)據(jù)類型,就不會帶來時區(qū)差異

  2. 無論js,java,python,在未指定時區(qū)的情況下,將Date類型的對象格式化為string時,均會使用機器本地的時區(qū),可以通過修改電腦的時區(qū),再格式化同一個時間戳的Date對象來測試

修改電腦時區(qū)前后對比.png

在將一個字符串轉(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
  1. 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
mysql datetime.png
  1. 通過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ù)庫

  1. 當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é)

  1. javascriptjava中Date類的默認封裝拆箱都可以獲取到正確的時間,無需做特殊處理【因為用json傳遞數(shù)據(jù),因此并不一定傳遞的真的是時間戳,也可能是帶了時區(qū)信息的字符串,此處還未抓包求證,但總之可能正常傳輸】
  2. 多地區(qū)提供服務(wù)時,應(yīng)該控制所有數(shù)據(jù)庫為同一時區(qū),并將jdbc連接的serverTimezone參數(shù)設(shè)置為與數(shù)據(jù)庫時區(qū)一致,則不同時區(qū)的服務(wù)器都可以獲取正確的日期數(shù)據(jù)

參考資料1

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容