询问一个关于 Java 日期在数据库存储的格式问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MuXia
V2EX    Java

询问一个关于 Java 日期在数据库存储的格式问题

  •  
  •   MuXia 2022 年 7 月 12 日 4142 次点击
    这是一个创建于 1384 天前的主题,其中的信息可能已经有所发展或是发生改变。

    java 新人目前只在一家公司工作过

    想问下各位,一般 Java 写后端的话,对于日期字段的处理,一般数据库存储的是什么格式的?

    时间戳? yyyy-MM-dd HH:mm:ss 格式字符串? 还是其他的?

    第 1 条附言    2022 年 7 月 12 日

    会提出这个问题是因为今早看到了这篇帖子 /865425 中说到了时间库的问题,

    然后又去查询了一下文中提到时间库自带的一些方法,发现确实挺方便的,但没看到转化时间戳的方法(不知是我没看见,还是没有)

    结合现在开发的项目,就有了这个问题

    谢谢各位的回复,让我这个疑惑得以解开

    在校的时候,自己也写过一些小项目,当时就时间处理十分头大,现在逐渐清晰了

    32 条回复    2022-07-14 12:21:46 +08:00
    Jooooooooo
        1
    Jooooooooo  
       2022 年 7 月 12 日
    都行, 看现有的系统用啥, 跟着用一样的.

    时间戳或者 string 或者 timestamp 都可以.
    bxb100
        2
    bxb100  
       2022 年 7 月 12 日 via Android
    UTC 时间戳
    unco020511
        3
    unco020511  
       2022 年 7 月 12 日
    有海外业务:时间戳,仅国内业务:存格式化后的字符串(GTM+8 本地时区)
    ntdll
        4
    ntdll  
       2022 年 7 月 12 日
    数据库都有日期类型,如果实在特殊原因不想、不能用。时间戳是唯一选择。所有语言都有简单的方法将时间戳转换成日期,并且也方便做本地化。
    dqzcwxb
        5
    dqzcwxb  
       2022 年 7 月 12 日
    你猜猜数据库为什么要弄一个 datetime 类型出来
    cheng6563
        6
    cheng6563  
       2022 年 7 月 12 日
    跨时区的国际系统可以用时间戳,其他的用 datetime 就行了
    neptuno
        7
    neptuno  
       2022 年 7 月 12 日
    新项目一律时间戳
    nothingistrue
        8
    nothingistrue  
       2022 年 7 月 12 日
    常规数据库有两种日期时间格式,一种是年、月、日、时、分、秒、毫秒组合存储,另一种是时间戳存储,即自 1970 年 0 点开始的毫秒数,内部是数值类型。第一种类型没有时区(查询出来的显示值,不随环境变量当中的时区而改变),Mysql 还细分为 Date 和 DateTime ,Oracle 则统一为 Date ,对应的 Java 类型是 java.time.LoacalDate 和 LocalDateTime 。第二种类型有时区(查询出来的显示值,随环境变量当中的时区的不同而不同),有的数据库单位直到毫秒,有得能到微秒,对应的 Java 类型是 java.sql.Date 或 java.sql.Timestamp (取决于要哪个单位)。

    一般来说,如果是新项目,一律考虑使用 Date / DateTime - java.time.LocalDate / LocalDateTime ,即使要国际化(时区上你可以在程序层面再控制转换成 ZonedDateTime ,甚至还可以将时区国际化直接交给前端处理),在数据库上处理时区会是个灾难。
    dcsuibian
        9
    dcsuibian  
       2022 年 7 月 12 日
    用 long 存毫秒级时间戳,足够用到天荒地老。
    MySQL 的 timestamp 不要用,只有 4 字节,除非你想在 2038 年引起下一个千年虫。

    对于精准时间点,时间戳特别好用。没有时区、夏令时问题。闰秒操作系统会帮你吃掉。
    连接数据库不用担心 serverTimezOne=GMT%2B8 问题
    时间不对,排查点就基本可以缩小到 Format
    MuXia
        10
    MuXia  
    OP
       2022 年 7 月 12 日
    @nothingistrue #8 回复的很详细,新知识 get
    dorothyREN
        11
    dorothyREN  
       2022 年 7 月 12 日
    pg 的话 用 timestamptz 是带时区的时间戳,java 用 Timestamp 类型
    realpg
        12
    realpg  
    PRO
       2022 年 7 月 13 日
    任何时候都存时间戳,不分语言
    因为时间戳是绝对单位,且服务器设置好时区转换当地时间方便
    nothingistrue
        13
    nothingistrue  
       2022 年 7 月 13 日
    @MuXia 忽略我之前的回复,有错误。各数据库的日期时间保存格式,都不相同,我说得只在 Mysql 上是正确的。映射那里也写错了,与 JDBC 规范不符合。

    正确的应该是:
    java.time.LocalDateTime ,无时区日期时间(显示值即值,没有内部值,对应的现实时间随时区浮动),JDBC 类型是 TIMESTAMP ;
    java.time.LocalDate ,无时区日期(显示值即值,没有内部值,与现实时间没有直接对应关系),JDBC 类型是 Date ;
    java.time.LocalTime ,无时区当天时间(显示值即值,没有内部值,对应的现实时间随时区+天浮动),JDBC 类型是 Time ;
    java.time.OffsetDateTime ,偏移量日期时间(对应现实完整时间,内部值固定,显示值随时区偏移),JDBC 类型是 TIMESTAMP ;
    java.time.OffsetTime ,偏移量当天时间(对应现实当天时间,内部值固定,显示值随时区偏移),JDBC 类型是 TIMESTAMP ;
    java.time.ZonedDateTime ,基本等同于 OffsetDateTime ,区别只是一个是 CST 时区,一个是+/-数字时区。

    详细可见 : https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html#basic-provided 表 2 。注意上面的 Date 、Time 、TIMESTAMP 都是 JDBC 类型,具体是什么类型取决于各数据库厂商提供的 JDBC 驱动。另外 TIMESTAMP 是时间戳 + 时区,不是只有时间戳。

    这里面 Mysql 提供了个狗屎。它的 DateTime 只是歪打正着的跟 java.time.LocalDateTime 对应,但也只在 JVM 时区跟 数据库时区一致的情况下才这样,不一致的时候要出问题。而它的 Timestamp 则完全无法使用。
    MuXia
        14
    MuXia  
    OP
       2022 年 7 月 13 日
    @nothingistrue #13 总感觉这对应关系挺混乱的,我现在处理起来都是直接数据库字段用 varchar 或 int 来存时间戳,在代码里面去转化
    nothingistrue
        15
    nothingistrue  
       2022 年 7 月 13 日
    一般来说,不考虑国际化的时候,还是要用 java.time.LocalDateTime ,这个更贴近需求,而且就算狗屎 Mysql 也能正好提供实现。

    考虑国际化的时候,应当用 java.time.OffsetDateTime/java.time.ZonedDateTime ,但是在数据库映射上要做特殊处理,不是所有的数据库都支持这种映射,比如 Mysql 。
    MuXia
        16
    MuXia  
    OP
       2022 年 7 月 13 日
    @nothingistrue #15 行,有空再去了解一下
    nothingistrue
        17
    nothingistrue  
       2022 年 7 月 13 日
    @MuXia 不要用单一的数值类型存时间戳,那实际上隐式存了一个 JDBC 时区,该时区依赖于 JVM 时区和 JDBC 驱动,当 JVM 时区、JDBC 驱动、或者只是 JDBC 连接配置( Mysql 就是个典型)发生变化的时候,会发生很难处理的时间偏移问题。要存时间戳,必须用数据库的带时区 Timestamp ,或者数字列+时区列两列存储时间。
    MuXia
        18
    MuXia  
    OP
       2022 年 7 月 13 日
    @nothingistrue #17 那#9 楼说的这个问题能否规避呢,如果纯国内应用的话,应该不用考虑时区变更的问题
    nothingistrue
        19
    nothingistrue  
       2022 年 7 月 13 日
    @MuXia #17 若不考虑国际化,就用 java.time.LocalDateTime 对应 Mysql DateTime ,只要保证 JVM 、Mysql 、JDBC 连接配置都是东八区,其他时候就都不用考虑时区问题。
    MuXia
        20
    MuXia  
    OP
       2022 年 7 月 13 日
    @nothingistrue #19
    ```
    MySQL 的 timestamp 不要用,只有 4 字节,除非你想在 2038 年引起下一个千年虫。
    ```
    九楼说的这个问题是不是无解?
    siweipancc
        21
    siweipancc  
       2022 年 7 月 13 日 via iPhone
    @dcsuibian 最近搞智能硬件就用 long 存储了,还被同事吐槽,查个时间范围的数据还得手动转一次,我只能说开心就好
    dcsuibian
        22
    dcsuibian  
       2022 年 7 月 13 日 via Android
    @nothingistrue
    @MuXia
    时间戳是跟当前在什么时区无关的。
    https://www.liaoxuefeng.com/article/978494994163392
    dcsuibian
        23
    dcsuibian  
       2022 年 7 月 13 日 via Android
    nothingistrue
        24
    nothingistrue  
       2022 年 7 月 13 日
    @dcsuibian #21 并不是,你所谓的无关,其实是隐含了 UTC 0 。数值型的时间戳,都是基于 UNIX 时间戳,而 UNIX 时间戳的定义是:从 UTC 1970 年 1 月 1 日 0 时 0 分 0 秒起至现在的总秒数。单独的一个数值,不是时间戳,带上时区,最起码要隐含 UTC 0 ,才是时间戳。

    其实时间戳最大的问题,不是带时区,而是这个时区怎么带,根本没有统一规范。有的总是存储 UTC0 , 有的读写时跟随随环境变量(会发生因不同时区导致的读写不一致问题),有的将时区跟数值一起保存。
    dcsuibian
        25
    dcsuibian  
       2022 年 7 月 13 日
    @nothingistrue 不带时区的。你可以先正常运行一遍:
    System.out.println(System.currentTimeMillis());
    然后,换个时区再运行一次,你看看这两个数字差了多少就知道了。(毫秒)

    时间戳只是针对某个时间点的偏移量。只不过这个时间点是 UTC 1970 年 1 月 1 日 0 时 0 分 0 秒
    完全可以说时间戳是从 UTC+8 1970 年 1 月 1 日 8 时 0 分 0 秒 起至现在的秒数,这俩就是同一个时间点。
    只不过挑一个基准时间点,总归要挑个规整点的罢了。
    nothingistrue
        26
    nothingistrue  
       2022 年 7 月 13 日
    @dcsuibian System.currentTimeMillis() 生成的是 UTC0 时间戳,隐式 UTC0 ,不代表没有 UTC 0 。这个区别很重要,因为有些工具生成的当前时间不是 UTC0 的。

    试试 Mysql 下执行这个 SELECT CURRENT_TIMESTAMP(),LOCALTIMESTAMP(),UTC_TIMESTAMP(),NOW() FROM DUAL;

    另外实际上只有 UNIX 时间戳才是从 UTC 1970 年 1 月 1 日 0 时 0 分 0 秒起至现在的总秒数,ISO 8601 时间戳就是年月日时分表时区的组合。
    dcsuibian
        27
    dcsuibian  
       2022 年 7 月 13 日
    @nothingistrue MySQL 的 Timestamp 会在你 select 帮你格式化成文本帮助你阅读,格式化时就会用到时区信息,但底层的数字是没有的时区信息的。

    对应的,你套上 UNIX_TIMESTAMP()函数,然后再看看。
    SELECT
    UNIX_TIMESTAMP(CURRENT_TIMESTAMP()),UNIX_TIMESTAMP(LOCALTIMESTAMP()),UNIX_TIMESTAMP(UTC_TIMESTAMP()),UNIX_TIMESTAMP(NOW())
    FROM DUAL;

    世界上各个时区的人在同一时间点调用 System.currentTimeMillis() 拿到的是同一个数字。
    那用这个数字来表示时间点就不会因为时区、显示而产生歧义了啊
    dcsuibian
        28
    dcsuibian  
       2022 年 7 月 13 日
    @nothingistrue 扯远了。
    回到用 long 存时间戳的问题上,假如现在有一台 MySQL (无论在哪儿),Java 程序 1 在北京,Java 程序 2 在纽约,它们都连接着这个数据库。

    实验 1:
    先是北京产生了一条记录,然后 10 分钟后纽约产生了一条记录。Java 程序都使用 System.currentTimeMillis()将得到的 long 数字存入数据库。那么这两条记录差的大概就是 10*60*1000 毫秒。无论你是否设置了 serverTimezone 参数,可测试。

    实验 2:
    使用如下 Java 程序插入一条新纪录。
    String url = "jdbc:mysql://localhost:3306/test?serverTimezOne=UTC%2B8";
    Connection cOnn= DriverManager.getConnection(url, "root", "password");
    PreparedStatement stmt = conn.prepareStatement("INSERT INTO record(`time`) VALUES(?)");
    Date date = new Date();
    stmt.setObject(1, date);
    stmt.execute();
    stmt.close();
    conn.close();
    在保留和去除 serverTimezOne=UTC%2B8 这个参数的情况下分别插入一条数据。那么你在数据库里看到的时间差别就很大。这是因为虽然 MySQL 底层虽然用了存整形的方法存时间戳。但你 insert 的时候仍然得用字符串:
    INSERT INTO record(`time`) VALUES('2022-07-13 00:00:00')
    这就有了借助时区的转换过程,然后就会有问题。
    nothingistrue
        29
    nothingistrue  
       2022 年 7 月 14 日
    @dcsuibian 你到现在还没发现问题吗,同一个 Unix 时间戳值,在读取 /显示的时候,不同时区是不一样的。

    你局限在数值的不变上,但一个显示值随时区变化的数值,压根就不能成为数据,完整的数据,要是数值+时区。这就是数值型时间戳必须额外带时区,或者说数值型时间戳跟时区相关的原因。

    你也局限在了 Java 上,java.util.Date 及其相关类的内部值,是存储的自 1970-1-1T00:00:00+0 到现在的毫秒数,但这只是 Java 的规范。Unix 时间戳不是国际标准,其他语言、数据库都可能定义自己的规范,比如有的语言会把时间戳定义为当前时区自 1970-1-1T00:00:00 到现在的毫秒数。结合使用的时候就容易出坑。
    dcsuibian
        30
    dcsuibian  
       2022 年 7 月 14 日
    @nothingistrue
    ISO 是国际标准化组织,又不是只面向计算机从业者的,8601 是规定了显示方法。数据的存储和显示相分离的设计原则不是再正常不过了吗?
    我一开始不就说了使用“毫秒”级时间戳。确实时间戳没有国际标准。各种语言都可以轻松地处理。
    Python 的 time.time(),Java 的 System.currentTimeMills(),Javascript 的 Date.now(),也就差一个 1000 处理。推荐使用“毫秒”时间戳只是因为整数比浮点更好处理罢了。
    最重要是,处理过程中没有涉及到任何“时区”相关的东西?

    这个数字只跟时间点有关。如果你没条件找个其它时区的人跟你一起试的话。至少换个系统时区再试试,关键是



    你到底有没有试过?
    dcsuibian
        31
    dcsuibian  
       2022 年 7 月 14 日
    @nothingistrue
    [1-中国标准时间.jpg]( https://dcsuibian-public-resources.oss-cn-hangzhou.aliyuncs.com/img/1-%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4.jpg)

    [2-亚马逊标准时间.jpg]( https://dcsuibian-public-resources.oss-cn-hangzhou.aliyuncs.com/img/2-%E4%BA%9A%E9%A9%AC%E9%80%8A%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4.jpg)

    你看看在切换时间后,时间戳这两个数字差了多少? 36438 毫秒,也就是 36.4 秒,就是我在第一个运行完、截图、改时区等操作花了一会儿而已。
    dcsuibian
        32
    dcsuibian  
       2022 年 7 月 14 日
    “有的语言会把时间戳定义为当前时区自 1970-1-1T00:00:00 到现在的毫秒数”
    根本就没有语言这么做,再者说就算这么做了,也跟我时间戳(无论是毫秒还是秒)这个普遍概念没关系了。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2953 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 53ms UTC 12:55 PVG 20:55 LAX 05:55 JFK 08:55
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86