java 新人目前只在一家公司工作过
想问下各位,一般 Java 写后端的话,对于日期字段的处理,一般数据库存储的是什么格式的?
时间戳? yyyy-MM-dd HH:mm:ss 格式字符串? 还是其他的?
会提出这个问题是因为今早看到了这篇帖子 /865425 中说到了时间库的问题,
然后又去查询了一下文中提到时间库自带的一些方法,发现确实挺方便的,但没看到转化时间戳的方法(不知是我没看见,还是没有)
结合现在开发的项目,就有了这个问题
谢谢各位的回复,让我这个疑惑得以解开
在校的时候,自己也写过一些小项目,当时就时间处理十分头大,现在逐渐清晰了
1
Jooooooooo 2022-07-12 16:23:49 +08:00
都行, 看现有的系统用啥, 跟着用一样的.
时间戳或者 string 或者 timestamp 都可以. |
2
bxb100 2022-07-12 16:30:48 +08:00 via Android
UTC 时间戳
|
3
unco020511 2022-07-12 16:34:43 +08:00
有海外业务:时间戳,仅国内业务:存格式化后的字符串(GTM+8 本地时区)
|
4
gam2046 2022-07-12 16:36:28 +08:00
数据库都有日期类型,如果实在特殊原因不想、不能用。时间戳是唯一选择。所有语言都有简单的方法将时间戳转换成日期,并且也方便做本地化。
|
5
dqzcwxb 2022-07-12 16:37:56 +08:00
你猜猜数据库为什么要弄一个 datetime 类型出来
|
6
cheng6563 2022-07-12 16:42:44 +08:00
跨时区的国际系统可以用时间戳,其他的用 datetime 就行了
|
7
neptuno 2022-07-12 17:02:40 +08:00
新项目一律时间戳
|
8
nothingistrue 2022-07-12 17:53:26 +08:00
常规数据库有两种日期时间格式,一种是年、月、日、时、分、秒、毫秒组合存储,另一种是时间戳存储,即自 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 ,甚至还可以将时区国际化直接交给前端处理),在数据库上处理时区会是个灾难。 |
9
dcsuibian 2022-07-12 18:03:26 +08:00
用 long 存毫秒级时间戳,足够用到天荒地老。
MySQL 的 timestamp 不要用,只有 4 字节,除非你想在 2038 年引起下一个千年虫。 对于精准时间点,时间戳特别好用。没有时区、夏令时问题。闰秒操作系统会帮你吃掉。 连接数据库不用担心 serverTimezone=GMT%2B8 问题 时间不对,排查点就基本可以缩小到 Format |
10
MuXia OP @nothingistrue #8 回复的很详细,新知识 get
|
11
dorothyREN 2022-07-12 20:20:29 +08:00
pg 的话 用 timestamptz 是带时区的时间戳,java 用 Timestamp 类型
|
12
realpg 2022-07-13 01:53:10 +08:00
任何时候都存时间戳,不分语言
因为时间戳是绝对单位,且服务器设置好时区转换当地时间方便 |
13
nothingistrue 2022-07-13 10:05:04 +08:00
@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 则完全无法使用。 |
14
MuXia OP @nothingistrue #13 总感觉这对应关系挺混乱的,我现在处理起来都是直接数据库字段用 varchar 或 int 来存时间戳,在代码里面去转化
|
15
nothingistrue 2022-07-13 10:14:09 +08:00
一般来说,不考虑国际化的时候,还是要用 java.time.LocalDateTime ,这个更贴近需求,而且就算狗屎 Mysql 也能正好提供实现。
考虑国际化的时候,应当用 java.time.OffsetDateTime/java.time.ZonedDateTime ,但是在数据库映射上要做特殊处理,不是所有的数据库都支持这种映射,比如 Mysql 。 |
16
MuXia OP @nothingistrue #15 行,有空再去了解一下
|
17
nothingistrue 2022-07-13 10:20:28 +08:00
@MuXia 不要用单一的数值类型存时间戳,那实际上隐式存了一个 JDBC 时区,该时区依赖于 JVM 时区和 JDBC 驱动,当 JVM 时区、JDBC 驱动、或者只是 JDBC 连接配置( Mysql 就是个典型)发生变化的时候,会发生很难处理的时间偏移问题。要存时间戳,必须用数据库的带时区 Timestamp ,或者数字列+时区列两列存储时间。
|
18
MuXia OP @nothingistrue #17 那#9 楼说的这个问题能否规避呢,如果纯国内应用的话,应该不用考虑时区变更的问题
|
19
nothingistrue 2022-07-13 10:32:49 +08:00
@MuXia #17 若不考虑国际化,就用 java.time.LocalDateTime 对应 Mysql DateTime ,只要保证 JVM 、Mysql 、JDBC 连接配置都是东八区,其他时候就都不用考虑时区问题。
|
20
MuXia OP |
21
siweipancc 2022-07-13 11:03:53 +08:00 via iPhone
@dcsuibian 最近搞智能硬件就用 long 存储了,还被同事吐槽,查个时间范围的数据还得手动转一次,我只能说开心就好
|
22
dcsuibian 2022-07-13 11:45:37 +08:00 via Android
|
23
dcsuibian 2022-07-13 11:50:46 +08:00 via Android
|
24
nothingistrue 2022-07-13 12:09:35 +08:00
@dcsuibian #21 并不是,你所谓的无关,其实是隐含了 UTC 0 。数值型的时间戳,都是基于 UNIX 时间戳,而 UNIX 时间戳的定义是:从 UTC 1970 年 1 月 1 日 0 时 0 分 0 秒起至现在的总秒数。单独的一个数值,不是时间戳,带上时区,最起码要隐含 UTC 0 ,才是时间戳。
其实时间戳最大的问题,不是带时区,而是这个时区怎么带,根本没有统一规范。有的总是存储 UTC0 , 有的读写时跟随随环境变量(会发生因不同时区导致的读写不一致问题),有的将时区跟数值一起保存。 |
25
dcsuibian 2022-07-13 13:05:00 +08:00
@nothingistrue 不带时区的。你可以先正常运行一遍:
System.out.println(System.currentTimeMillis()); 然后,换个时区再运行一次,你看看这两个数字差了多少就知道了。(毫秒) 时间戳只是针对某个时间点的偏移量。只不过这个时间点是 UTC 1970 年 1 月 1 日 0 时 0 分 0 秒 完全可以说时间戳是从 UTC+8 1970 年 1 月 1 日 8 时 0 分 0 秒 起至现在的秒数,这俩就是同一个时间点。 只不过挑一个基准时间点,总归要挑个规整点的罢了。 |
26
nothingistrue 2022-07-13 17:54:05 +08:00
@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 时间戳就是年月日时分表时区的组合。 |
27
dcsuibian 2022-07-13 21:18:21 +08:00
@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() 拿到的是同一个数字。 那用这个数字来表示时间点就不会因为时区、显示而产生歧义了啊 |
28
dcsuibian 2022-07-13 23:08:51 +08:00
@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') 这就有了借助时区的转换过程,然后就会有问题。 |
29
nothingistrue 2022-07-14 09:40:07 +08:00
@dcsuibian 你到现在还没发现问题吗,同一个 Unix 时间戳值,在读取 /显示的时候,不同时区是不一样的。
你局限在数值的不变上,但一个显示值随时区变化的数值,压根就不能成为数据,完整的数据,要是数值+时区。这就是数值型时间戳必须额外带时区,或者说数值型时间戳跟时区相关的原因。 你也局限在了 Java 上,java.util.Date 及其相关类的内部值,是存储的自 1970-1-1T00:00:00+0 到现在的毫秒数,但这只是 Java 的规范。Unix 时间戳不是国际标准,其他语言、数据库都可能定义自己的规范,比如有的语言会把时间戳定义为当前时区自 1970-1-1T00:00:00 到现在的毫秒数。结合使用的时候就容易出坑。 |
30
dcsuibian 2022-07-14 12:01:30 +08:00
@nothingistrue
ISO 是国际标准化组织,又不是只面向计算机从业者的,8601 是规定了显示方法。数据的存储和显示相分离的设计原则不是再正常不过了吗? 我一开始不就说了使用“毫秒”级时间戳。确实时间戳没有国际标准。各种语言都可以轻松地处理。 Python 的 time.time(),Java 的 System.currentTimeMills(),JavaScript 的 Date.now(),也就差一个 1000 处理。推荐使用“毫秒”时间戳只是因为整数比浮点更好处理罢了。 最重要是,处理过程中没有涉及到任何“时区”相关的东西? 这个数字只跟时间点有关。如果你没条件找个其它时区的人跟你一起试的话。至少换个系统时区再试试,关键是 你到底有没有试过? |
31
dcsuibian 2022-07-14 12:14:04 +08:00
@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 秒,就是我在第一个运行完、截图、改时区等操作花了一会儿而已。 |
32
dcsuibian 2022-07-14 12:21:46 +08:00
“有的语言会把时间戳定义为当前时区自 1970-1-1T00:00:00 到现在的毫秒数”
根本就没有语言这么做,再者说就算这么做了,也跟我时间戳(无论是毫秒还是秒)这个普遍概念没关系了。 |