日期处理第四篇(终)- Java日期时间处理大总结

news2024/11/22 18:07:20

文章目录

    • 日期时间概念
      • 通用标准
      • 日期字段解析
      • 国际化的日期格式
    • 日期的实战
      • 第一个问题:日期常用时间操作
      • 第二个问题:时区的问题
        • 时区概念
        • 时区的处理
          • ZoneID的使用
        • ZoneOffset的使用
        • 让人恼火的夏令时
      • 第三个问题:MySQL存储时间用什么类型?
        • MySQL中的日期类型
          • DATETIME
          • TIMESTAMEP
          • 数值型时间戳(INT)
        • 结论
      • 第四个问题:项目国际化,日期时间处理方案。
    • 总结

前面三篇分享了在Java中处理日期、时间相关的一些工具类。俗话说得好磨刀不误砍柴工,今天我们来系统的去看一下在程序中的日期是什么样的?我们把他的原理搞清楚,把概念弄明白,希望以后再遇到日期相关的问题,处理起来事半功倍。本篇会先阐述一下时间相关的概念,然后再分享日期的一些实战,比如日期格式化、日期计算、数据库存储日期用什么类型最好等。话不多说,开始吧。

在这里插入图片描述

日期时间概念

通用标准

ISO 8601确定四位数年份、两位数日月两位数24小时制时分秒yyyy-MM-dd HH:mm:ss)的表达方式作为国际标准。这一标准方便计算机进行自然排序,且可减少歧义,有利于跨国资讯交换。说个题外话,你知道千禧年问题吗?

日期字段解析

我们经常使用的就是这种格式yyyy-MM-dd HH:mm:ss,还有很多其他格式,我们了解了下面每个字符的含义后,就可以按照自己想要的格式进行输出了。
y: 年,一般使用yyyy表示4位年份如2024,yy表示2位年份如24
M:月,一般使用MM表示月份比如01、12,MMM会根据语言环境显示不用的月份,如中国:一月、十二月;美国:Oct
d: 月份中天数,一般使用dd表示月份,如:20
D:年份中的天数,表示的是一年的第几天,用D表示,如:323
E:星期几。用E表示,比如中国显示星期六,英语环境下会显示 Sat
H:一天中的小时数(0-23),24小时制,一般用HH表示小时数,如:18
h:一天中的小时数(1-12),12小时制,使用hh表示的10点也可能是晚上22点
m:分钟,用mm表示,如:53
s:秒(1-999),用ss表示,如:35
S:毫秒,一般使用SSS表示,如:879
-: 连接符,没有特殊意义,可以是任意字符,汉字也可以
z:时区,通用时区,如:Pacific Standard Time; PST; GMT-08:00
Z:时区,RFC 822时区,如:-0800,+0800
X:时区,ISO 8601时区,如:-08; -0800; -08:00;+08:00
a:表示am/pm,
G:年代,AD(公元)、BC(公元前)

2024-01-21T00:38:55.981+08:00
这个时间的格式用字母来表达式就是:yyyy-MM-ddTHH:mm:ss.SSSX
T是什么含义呢,类似分隔符可以用任何字符代替,但是国际标准用T来表面后半部分是时间,前半部分是日期。X是时区。

我们再来看一些日期格式,下面这些其实并不常用,可以作为参考:
在这里插入图片描述

国际化的日期格式

可以简单看下国际化的日期格式都是什么样的:
https://docs.oracle.com/cd/E19683-01/816-3981/overview-46/index.html
在这里插入图片描述
以上可以看到只要掌握了日期每个字段的概念和代表符号,就可以自定义你想要的任何格式了。最常见的还是yyyy-MM-dd HH:mm:ss,主要字母的大小写。

日期的实战

知道了日期的概念之后,我们进入实战部分,这里会介绍最常见的一些场景和问题。首先之前介绍了三篇的Java日期处理的工具类:

  1. 日期处理第一篇:优雅好用的Java日期工具类Joda-Time: Java8之前的业务场景推荐使用Joda-Time
  2. 日期处理第二篇:Java8新时间和日期API,看完你就全明白了:Java8以后的版本包括Java8,优先使用内置的sdk,弃用java.date下的工具类,优先使用java.time下的工具类。
  3. 日期处理第三篇:Hutool的日期时间工具-DateUtil使用:使用起来很方便,也兼容了Java8新的日期的API,但是非必要不建议引入额外的jar包了,推荐使用内置的就可以。

应该没有太多项目了还在java8之前的版本,所以本篇会全部采用Java8新的日期API来进行编码做示例。

第一个问题:日期常用时间操作

这个问题应该是我们业务中最常遇见到的了,将日期输出、展示、存储、时间戳转成日期格式等场景,我们下面一一说明。

  1. 获取当前系统时间
// 2024-01-21T12:20:05.394
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
// 2024-01-21T12:20:05.395  推荐使用带有时区的方式获取时间
LocalDateTime localDateTime2 = LocalDateTime.now(ZoneId.systemDefault());
System.out.println(localDateTime2);
// 2024-01-21T04:20:05.395
LocalDateTime localDateTime3 = LocalDateTime.now(ZoneId.of("UTC+0"));
System.out.println(localDateTime3);

虽然我们日常使用第一种LocalDateTime.now()的场景更多,但是这里还是要强调推荐使用LocalDateTime.now(ZoneId.systemDefault());这种带有时区的方式获取时间,这样目的更明确。

  1. 获取当前系统时间戳
		//1705811713124
        long now = System.currentTimeMillis();
        System.out.println(now);
        
        // 1705811713124
        Instant instant = Instant.now();
        System.out.println(instant.toEpochMilli());
  1. 日期时间和字符串之间的转换
		// 时间戳转成字符串
        Instant instant = Instant.now();
        // 2024-01-21T12:35:13.124
        LocalDateTime fromMillsDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        System.out.println(fromMillsDateTime);
		
		// 字符串转成时间戳
		Instant instantFromDateTime = fromMillsDateTime.toInstant(ZoneOffset.UTC);
		// 1705840746886
        System.out.println(instantFromDateTime.toEpochMilli());
  1. 日期格式化-DateTimeFormatter
    记住不要再使用SimpleDateFormat了,SimpleDateFormat是线程不安全的,也说也被淘汰的类了,推荐使用DateTimeFormatter,DateTimeFormatter也是一个不可变的类,所以是线程安全的,比SimpleDateFormat靠谱多了吧。另外它还内置了非常多的格式化模版实例供以使用,形如:
    在这里插入图片描述
String strDate6 = "2017-01-05";
String strDate7 = "2017-01-05 12:30:05";

LocalDate date = LocalDate.parse(strDate6, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDateTime dateTime1 = LocalDateTime.parse(strDate7, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));


LocalDateTime dateTime = LocalDateTime.now();
String strDate1 = dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);    // 20170105
String strDate2 = dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE);    // 2017-01-05
String strDate3 = dateTime.format(DateTimeFormatter.ISO_LOCAL_TIME);    // 14:20:16.998
String strDate4 = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));   // 2017-01-05
String strDate5 = dateTime.format(DateTimeFormatter.ofPattern("今天是:YYYY年 MMMM DD日 E", Locale.CHINESE)); // 今天是:2017年 一月 05日 星期四

若想自定义模式pattern,和Date一样它也可以自己指定任意的pattern 日期/时间模式。由于本文在Date部分详细介绍了日期/时间模式,各个字母代表什么意思以及如何使用,这里就不再赘述了哈。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("第Q季度 yyyy-MM-dd HH:mm:ss", Locale.US);

    // 格式化输出
    System.out.println(formatter.format(LocalDateTime.now()));

    // 解析
    String dateTimeStrParam = "第1季度 2021-01-17 22:51:32";
    LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStrParam, formatter);
    // Q/q:季度,如3; 03; Q3; 3rd quarter。
    System.out.println("解析后的结果:" + localDateTime);

还有很多其他操作参考: 日期处理第二篇:Java8新时间和日期API,看完你就全明白了

第二个问题:时区的问题

令人最头痛的应该就是这个时区的问题了,平时虽然我们用不到,但还是会经常遇到时区的问题的,最近也在做国际化相关的项目,所以开始深入的了解一下时区的问题。
查资料的时候发现这篇文章研究的很清楚,可以参考:https://www.cnblogs.com/yourbatman/p/14307194.html
如果展开来讲,又能写一篇博客深入浅出时区了,这里我们还是尽量简单讲基础概念和实战运用,想深入了解的可以参考上面的文章。

时区概念

我们经常遇到一些名词:时间戳、UTC、GMT、夏令时等,我们来看一看这几个名词分别是什么意思。

  • 时间戳(Timestamp):是指从一个特定的时间起点开始,到另一个时间点的间隔数值。通常来说,这个起点是UNIX时间的开始,即1970年1月1日00:00:00 UTC(协调世界时),到目标日期的秒数(或者毫秒数)。时间戳是一个非常精确的时间表示方法,广泛应用于程序开发和数据库中。

  • UTC(Coordinated Universal Time,协调世界时):是世界标准时间,替代了过去的格林尼治标准时间(GMT)。UTC比GMT更加精确,在偏差控制上采用了原子时钟,因此UTC现在被作为全球统一的时间标准。

  • GMT(Greenwich Mean Time,格林尼治平时):指位于英国伦敦郊区的格林尼治天文台的标准时间,过去常用作国际时间标准。由于GMT不是非常精确(它不涉及原子时钟),现在一般使用更精确的UTC来替代它。

  • 夏令时(Daylight Saving Time,DST):是一种在夏季把标准时间调快1小时的制度,目的是让人们更好地利用日照,晚上减少电灯的使用,以节约能源。不是所有的国家和地区都采用夏令时。在进入冬季时,会把时间调回正常的标准时间。

其实夏令时的英语很有意思,节省日光时间,所以这就很容易记住和明白他的含义了。这里记住UTC就可以了,因为我们使用最多的还是UTC。还有UTC、GMT这些不是时区,是一个标准的统称,UTC+8 这才是一个时区,表示标准时间增加8个小时,就是北京时间了。

时区的处理

Java中引入了ZoneId和ZoneOffset,这两个概念如下:
ZoneId:代表了一个时区的标识符,例如“Europe/Paris”。它是用来识别特定的时区规则的,并且可以用于转换时间点到本地时间。ZoneId可以通过静态方法of来获取,也可以通过时区规则的转换来获得。ZoneId与时区规则相关,这意味着它也包含了关于夏令时(如果该时区有)的信息。如:将一个Instant时间戳转换为本地日期/时间LocalDateTime。

ZoneOffset:是ZoneId的一个具体实现,它表示与UTC/格林尼治时间偏移了多少小时、分钟。ZoneOffset不包含夏令时的信息,它纯粹代表了一个固定的时差,例如“+08:00”代表东八区。ZoneOffset通常用在不需要关心夏令时变化的情况下,如记录日志、事件时间戳等。

ZoneID的使用

时区ZoneId是包含有规则的,实际上描述偏移量何时以及如何变化的实际规则由java.time.zone.ZoneRules定义。ZoneId则只是一个用于获取底层规则的ID。之所以采用这种方法,是因为规则是由政府定义的,并且经常变化,而ID是稳定的。对于API调用者来说只需要使用这个ID(也就是ZoneId)即可,而需无关心更为底层的时区规则ZoneRules,和“政府”同步规则的事是它领域内的事就交给它喽。如:夏令时这条规则是由各国政府制定的,而且不同国家不同年一般都不一样,这个事就交由JDK底层的ZoneRules机制自行sync,使用者无需关心。
ZoneId在系统内是唯一的,它共包含三种类型的ID:

最简单的ID类型:ZoneOffset,它由’Z’和以’+‘或’-‘开头的id组成。如:Z、+18:00、-18:00
另一种类型的ID是带有某种前缀形式的偏移样式ID,例如’GMT+2’或’UTC+01:00’。可识别的(合法的)前缀是’UTC’, ‘GMT’和’UT’
第三种类型是基于区域的ID(推荐使用)。基于区域的ID必须包含两个或多个字符,且不能以’UTC’、‘GMT’、‘UT’ '+‘或’-'开头。基于区域的id由配置定义好的,如Europe/Paris

在Java中使用ZoneId来处理时区。

        // 中国时间:2024-01-21T13:12:57.577
        LocalDateTime chinaTime = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        LocalDateTime chinaTime2 = LocalDateTime.now(ZoneId.of("UTC+8"));
        System.out.println(chinaTime);
        System.out.println(chinaTime2);

        // 纽约时间:2024-01-21T00:12:57.580
        LocalDateTime usaTime2 = LocalDateTime.now(ZoneId.of("America/New_York"));
        System.out.println(usaTime2);

这些代号不太好记,可以实时查看sdk,也可以根据时区偏移量来计算。
在这里插入图片描述

ZoneOffset的使用

距离格林威治/UTC的时区偏移量,例如+02:00。值得注意的是它继承自ZoneId,所以也可当作一个ZoneId来使用的,当然并不建议你这么去做,请独立使用。

时区偏移量是时区与格林威治/UTC之间的时间差。这通常是固定的小时数和分钟数。世界不同的地区有不同的时区偏移量。在ZoneId类中捕获关于偏移量如何随一年的地点和时间而变化的规则(主要是夏令时规则),所以继承自ZoneId。

偏移量是能精确到秒的,只不过一般来说精确到分钟已经到顶了。

		// 2024-01-21T13:20:14.987
        LocalDateTime zoneOffsetTime = LocalDateTime.now(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(8)));
        System.out.println(zoneOffsetTime);
让人恼火的夏令时

因为有夏令时规则的存在,让操作日期/时间的复杂度大大增加。但还好JDK尽量的屏蔽了这些规则对使用者的影响。因此:**推荐使用时区(ZoneId)**转换日期/时间,一般情况下不建议使用偏移量ZoneOffset去搞,这样就不会有夏令时的烦恼啦。

第三个问题:MySQL存储时间用什么类型?

在数据库中也有很多存储时间格式,有的使用INT,有的使用DATETIME或者使用TIMESTAMP,甚至有人会使用字符串的日期时间如”2024-01-21 13:00:00“。那么下面我们来看下使用哪种方式存储比较好。

首先,不推荐使用字符串。这是初学者很容易犯的错误,容易直接将字段设置为 VARCHAR 类型,存储"2021-01-01 00:00:00"这样的字符串。当然这样做的优点是比较简单,上手快。但是极力不推荐这样做,因为这样做有两个比较大的问题:

  • 字符串占用的空间大
  • 这样存储的字段比较效率太低,只能逐个字符比较,无法使用 MySQL 提供的日期API
MySQL中的日期类型

MySQL 数据库中常见的日期类型有 YEAR、DATE、TIME、DATETIME、TIMESTAMEP。因为一般都需要将日期精确到秒,其中比较合适的有DATETIMETIMESTAMEP

DATETIME

DATETIME 在数据库中存储的形式为:YYYY-MM-DD HH:MM:SS,固定占用 8 个字节。
从 MySQL 5.6 版本开始,DATETIME 类型支持毫秒,DATETIME(N) 中的 N 表示毫秒的精度。例如,DATETIME(6) 表示可以存储 6 位的毫秒值。

TIMESTAMEP

TIMESTAMP 实际存储的内容为‘1970-01-01 00:00:00’到现在的毫秒数。在 MySQL 中,由于类型 TIMESTAMP 占用 4 个字节,因此其存储的时间上限只能到‘2038-01-19 03:14:07’。

从 MySQL 5.6 版本开始,类型 TIMESTAMP 也能支持毫秒。与 DATETIME 不同的是,若带有毫秒时,类型 TIMESTAMP 占用 7 个字节,而 DATETIME 无论是否存储毫秒信息,都占用 8 个字节。

类型 TIMESTAMP 最大的优点是可以带有时区属性,因为它本质上是从毫秒转化而来。如果你的业务需要对应不同的国家时区,那么类型 TIMESTAMP 是一种不错的选择。比如新闻类的业务,通常用户想知道这篇新闻发布时对应的自己国家时间,那么 TIMESTAMP 是一种选择。Timestamp 类型字段的值会随着服务器时区的变化而变化,自动换算成相应的时间,说简单点就是在不同时区,查询到同一个条记录此字段的值会不一样。

数值型时间戳(INT)

很多时候,我们也会使用 int 或者 bigint 类型的数值也就是时间戳来表示时间。这种存储方式的具有 Timestamp 类型的所具有一些优点,并且使用它的进行日期排序以及对比等操作的效率会更高,跨系统也很方便,毕竟只是存放的数值。缺点也很明显,就是数据的可读性太差了,你无法直观的看到具体时间。

综上,通过对这三个类型的比较,推荐使用TIMESTAMP。我们看看《高性能 MySQL 》中的作者是如何说的:
在这里插入图片描述

结论

当你不知道在数据库中使用哪个时间类型时,就选择TIMESTAMP。

第四个问题:项目国际化,日期时间处理方案。

内容过多,准备专门写一个博客来阐述国际化方案中的时间该怎么处理,从数据库到前端展示等,保持关注。

总结

本文在前三篇的基础上,着重介绍日期时间的原理和概念以及经常用到的一些名词解释。最后把项目中经常遇到的一些问题记录下来,后续如果再遇到其他的问题,也会补充到这里,记得收藏关注。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1405225.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

12. openCV在QT环境中利用zBar开发库实现二维码内容识别

1. 说明 本篇博客仅记录如何使用zBar库进行二维码内容的识别,其中牵扯到的一些其它知识点,比如二维码区域检测、zBar库开发环境配置等可以参考本专栏的其它相关博客,此篇博客不再赘述。 2. 具体步骤 博客中代码功能:手动选择一张包含二维码的图片,会将检测到的二维码区…

Linux 的提示符太长了,帮你精简一下

普通用户修改文件 ~/.bashrc 修改 50 行左右的代码,将两个w改为大写的W 如果是root用户则修改文件/root/.bashrc,同样的方法。

Lingo数学建模基础

1.基本运算符 1.1算数运算符 1.2逻辑运算 #not# 否定操作数的逻辑值,一元运算符 #eq# 若两运算数相等,则为true,否则为false #ne# 若两运算数不相等,则为true,否则为false #gt# 若左边运算数严格大于右边,则为true,否则为…

智能小程序环境配置流程

App 与智能小程序 在用户使用 App 扫描小程序的二维码或者点击设备,尝试进入小程序时,系统会对 App 当前环境与小程序所需运行环境进行比对,确定环境配置兼容后,App 才能启动并运行小程序。 比对规则中,主要涉及&…

NVMe TCG安全数据存储简介

NVMe(非易失性内存主机控制器接口规范)与TCG(可信计算组)的集成主要体现在数据安全、固件验证和硬件信任根等方面,以确保存储设备的数据保护能力和安全性。 TCG Opal定义了一套针对自加密硬盘(SED, Self-En…

Linux部署MinIO实现图片存储,读取,删除

1、MinIo简介 MinIO 是一个高性能的分布式对象存储服务,它与亚马逊的S3(简单存储服务)兼容,在开源许可下是免费和开放的。您可以用它存储任何种类的对象,也就是说可以存储无结构的数据如照片、视频、日志文件、备份和…

解决github无法访问的问题(修改hosts)

1.先ping github.com看是否能ping通 不能ping通的话,找到github最新的ip地址,修改hosts文件(C:\Windows\System32\drivers\etc) 找最新的ip地址的办法: a.cmd中ping时返回的 b.点击ipaddress.com查询网站链接 修改host…

c盘红色满了怎么清理c盘空间?整理了5个方法~

用户文档、下载文件、图片和视频等个人文件的不断增长导致了磁盘空间紧张。那么如何管理文件,清理我们的电脑呢?下面整理了5个不同类型的方法。 方法一:清理临时文件 1、打开“运行”(Win R),输入 %temp…

最新阿里云免费SSL证书申请使用介绍

为网站部署SSL证书已经是现在站长的必须要做的工作,然而SSL证书并不全是免费的,免费且好用的资源也越来越有限,毕竟嘛,这些都需要成本。 PS:最下方有最终建议方案。 本文介绍当前(2024年)阿里云免费SSL证书信息。 阿…

企业为什么需要WMS仓储管理系统,终于有人说明白了

随着科技的飞速发展和市场竞争的加剧,仓储企业面临着越来越多的挑战和机遇。为了提高产品的性能和质量,同时节约成本、提高运营效率,数字化建设成为了仓储企业的核心需求。而WMS仓储管理系统的应用,更是企业数字化建设的重中之重。…

jdk的安装和Tomcat的安装

jdk的安装 双击jdk,然后一路下一步 公共JRE可以关闭,没多大用,反而会占用内存 计算机–>属性–>高级系统设置–>环境变量 系统变量–新建 JAVA_HOMEjdk的存放路径 修改path 在path的最后面添加(;%JAVA_H…

springboot 整合 ElasticSearch 方法 (一)

下载 ES 相当于安装 MySQL, 可以在官网上下载 (链接在后面). 要注意安装的 ES 的版本要和项目中用的 Springboot 的版本对应. 比如我用的 Springboot 版本是 2.6, 所以ES要下载7.15 版本的. 官网链接: https://www.elastic.co/cn/downloads/elasticsearch 点右边这个查看更多…

视频监控平台EasyCVR增加fMP4流媒体视频格式及其应用场景介绍

近期我们在视频监控管理平台EasyCVR系统中新增了HTTP-FMP4播放协议,今天我们就来聊聊该协议的特点和应用。 fMP4(Fragmented MPEG-4)是基于MPEG-4 Part 12的流媒体格式,是流媒体的一项重要技术,因为它能通过互联网传送…

Nuxt2.x Error页面返回自定义请求状态码

一、问题描述 最近接到一个需求,针对Nuxt2.x的一个项目进行SEO优化,需要对404页面的状态进行修改,将404页面的请求状态码改为301,而不是404: 二、解决方案 1.几种无效尝试 (1)layouts下的err…

InternLM第6次课笔记

OpenCompass 大模型评测 测评什么 如何评测 基座模型:加入instrcut 对话模型:直接对话 评测方式 客观 主观 提示词工程 不同prompt鲁棒性 OpenCompass能力框架

Portainer Docker容器可视化管理平台实践

Portainer Docker容器可视化管理平台实践 引安装登录Remote ENV 实践 引 平常用docker命令操作比较多,找了一款docker可视化工具,方便快速预览和批量操作,不想一行一行敲的时候,可以偷偷懒。Portainer试用了一下,安装…

实施企业增长战略:明确需求和战略咨询公司选择尤为重要

在当今快速变化的商业环境中,企业的增长并非偶然产生的成果,而是通过精心设计和策略性规划实现的。企业要实现增长,明确企业自身需求和选择专业的战略咨询公司是非常重要的。接下来,本文就这两大关键问题进行分析。首先&#xff0…

Linux问题 apt-get install时 无法解析域名“cn.archive.ubuntu.com”

问题描述: 在安装程序时会出现无法解析域名的错误 解决办法: 1、编辑文件 sudo vim /etc/resolv.conf 2、在最后加上(按键 i 进入编辑模式) nameserver 8.8.8.8 3、保存退出(:wq)

基于神经网络的电力系统的负荷预测

一、背景介绍: 电力系统负荷预测是生产部门的重要工作之一,通过准确的负荷预测,可以经济合理地安排机组的启停、减少旋转备用容量、合理安排检修计划、降低发电成本和提高经济效益。负荷预测按预测的时间可以分为长期、中期和短期负荷预测。…

用Axure RP 9制作滑块

制作流程 1.打开界面 放置一个水平线 修改长为400 线段为5 2.放入圆 如图 3.修改圆的长和宽 如图 4.将圆变成动态面板 5.设置交互事件 如图 6.增加交互事件 7.增加 8.修改成跟随水平