写了这么多年DateUtils,殊不知你还有这么多弯弯绕!

在这里插入图片描述

目录

      • 在日常开发中,Date工具类使用频率相对较高,大家通常都会这样写:
      • 这很简单啊,有什么争议吗?
      • 格式化后出现的时间错乱。
      • 看看Java 8是如何解决时区问题的:
      • 在处理带时区的国际化时间问题,推荐使用jdk8的日期时间类:
      • 在与前端联调时,报了个错,```java.lang.NumberFormatException: multiple points```,起初我以为是时间格式传的不对,仔细一看,不对啊。
      • 看一下```SimpleDateFormat.parse```的源码:

大家好,我是哪吒。

在日常开发中,Date工具类使用频率相对较高,大家通常都会这样写:

public static Date getData(String date) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.parse(date);
}

public static Date getDataByFormat(String date, String format) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    return sdf.parse(date);
}

这很简单啊,有什么争议吗?

你应该听过“时区”这个名词,大家也都知道,相同时刻不同时区的时间是不一样的。

因此在使用时间时,一定要给出时区信息。

public static void getDataByZone(String param, String format) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat(format);

    // 默认时区解析时间表示
    Date date = sdf.parse(param);
    System.out.println(date + ":" + date.getTime());

    // 东京时区解析时间表示
    sdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
    Date newYorkDate = sdf.parse(param);
    System.out.println(newYorkDate + ":" + newYorkDate.getTime());
}

public static void main(String[] args) throws ParseException {
   getDataByZone("2023-11-10 10:00:00","yyyy-MM-dd HH:mm:ss");
}

对于当前的上海时区和纽约时区,转化为 UTC 时间戳是不同的时间。

对于同一个本地时间的表示,不同时区的人解析得到的 UTC 时间一定是不同的,反过来不同的本地时间可能对应同一个 UTC。

在这里插入图片描述

格式化后出现的时间错乱。

public static void getDataByZoneFormat(String param, String format) throws ParseException {
   SimpleDateFormat sdf = new SimpleDateFormat(format);
    Date date = sdf.parse(param);
    // 默认时区格式化输出
    System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss Z]").format(date));
    // 东京时区格式化输出
    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
    System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss Z]").format(date));
}

public static void main(String[] args) throws ParseException {
   getDataByZoneFormat("2023-11-10 10:00:00","yyyy-MM-dd HH:mm:ss");
}

我当前时区的 Offset(时差)是 +8 小时,对于 +9 小时的纽约,整整差了1个小时,北京早上 10 点对应早上东京 11 点。

在这里插入图片描述

看看Java 8是如何解决时区问题的:

Java 8 推出了新的时间日期类 ZoneId、ZoneOffset、LocalDateTime、ZonedDateTime 和 DateTimeFormatter,处理时区问题更简单清晰。

public static void getDataByZoneFormat8(String param, String format) throws ParseException {
    ZoneId zone = ZoneId.of("Asia/Shanghai");
    ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
    ZoneId timeZone = ZoneOffset.ofHours(2);

    // 格式化器
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern(format);
    ZonedDateTime date = ZonedDateTime.of(LocalDateTime.parse(param, dtf), zone);

    // withZone设置时区
    DateTimeFormatter dtfz = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");
    System.out.println(dtfz.withZone(zone).format(date));
    System.out.println(dtfz.withZone(tokyoZone).format(date));
    System.out.println(dtfz.withZone(timeZone).format(date));
}

public static void main(String[] args) throws ParseException {
    getDataByZoneFormat8("2023-11-10 10:00:00","yyyy-MM-dd HH:mm:ss");
}
  • Asia/Shanghai对应+8,对应2023-11-10 10:00:00;
  • Asia/Tokyo对应+9,对应2023-11-10 11:00:00;
  • timeZone 是+2,所以对应2023-11-10 04:00:00;

在这里插入图片描述

在处理带时区的国际化时间问题,推荐使用jdk8的日期时间类:

  1. 通过ZoneId,定义时区;
  2. 使用ZonedDateTime保存时间;
  3. 通过withZone对DateTimeFormatter设置时区;
  4. 进行时间格式化得到本地时间;

思路比较清晰,不容易出错。

在与前端联调时,报了个错,java.lang.NumberFormatException: multiple points,起初我以为是时间格式传的不对,仔细一看,不对啊。

百度一下,才知道是高并发情况下SimpleDateFormat有线程安全的问题。

下面通过模拟高并发,把这个问题复现一下:

public static void getDataByThread(String param, String format) throws InterruptedException {
    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    // 模拟并发环境,开启5个并发线程
    for (int i = 0; i < 5; i++) {
        threadPool.execute(() -> {
            for (int j = 0; j < 2; j++) {
                try {
                    System.out.println(sdf.parse(param));
                } catch (ParseException e) {
                    System.out.println(e);
                }
            }
        });
    }
    threadPool.shutdown();

    threadPool.awaitTermination(1, TimeUnit.HOURS);
}

果不其然,报错。还将2023年转换成2220年,我勒个乖乖。

在时间工具类里,时间格式化,我都是这样弄的啊,没问题啊,为啥这个不行?原来是因为共用了同一个SimpleDateFormat,在工具类里,一个线程一个SimpleDateFormat,当然没问题啦!
在这里插入图片描述

看一下SimpleDateFormat.parse的源码:

public class SimpleDateFormat extends DateFormat {
	@Override
	public Date parse(String text, ParsePosition pos){
		CalendarBuilder calb = new CalendarBuilder();
		
		Date parsedDate;
		try {
		    parsedDate = calb.establish(calendar).getTime();
		    // If the year value is ambiguous,
		    // then the two-digit year == the default start year
		    if (ambiguousYear[0]) {
		        if (parsedDate.before(defaultCenturyStart)) {
		            parsedDate = calb.addYear(100).establish(calendar).getTime();
		        }
		    }
		}
	}
}

class CalendarBuilder {
	Calendar establish(Calendar cal) {
	    boolean weekDate = isSet(WEEK_YEAR)
	                        && field[WEEK_YEAR] > field[YEAR];
	    if (weekDate && !cal.isWeekDateSupported()) {
	        // Use YEAR instead
	        if (!isSet(YEAR)) {
	            set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
	        }
	        weekDate = false;
	    }
	
	    cal.clear();
	    // Set the fields from the min stamp to the max stamp so that
	    // the field resolution works in the Calendar.
	    for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
	        for (int index = 0; index <= maxFieldIndex; index++) {
	            if (field[index] == stamp) {
	                cal.set(index, field[MAX_FIELD + index]);
	                break;
	            }
	        }
	    }
		...
	
	}
}
  1. 先new CalendarBuilder();
  2. 通过parsedDate = calb.establish(calendar).getTime();解析时间;
  3. establish方法内先cal.clear(),再重新构建cal,整个操作没有加锁;

上面几步就会导致在高并发场景下,线程1正在操作一个Calendar,此时线程2又来了。线程1还没来得及处理 Calendar 就被线程2清空了。

因此,只能是一个线程一个SimpleDateFormat,也就是最常见的工具类。


🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师

华为OD机试 2023B卷题库疯狂收录中,刷题点这里

刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试,发现新题目,随时更新,全天CSDN在线答疑。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LSTM和GRU vs 循环神经网络RNN

1、考虑下列三种情况下&#xff0c;对比一下普通RNN的表现和LSTM和GRU表现&#xff1a; &#xff08;1&#xff09;早期观测值对预测未来观测者具有非常重要的意义。 考虑一个极端情况&#xff0c;其中第一个观测值包含一个校验和&#xff0c; 目标是在序列的末尾辨别校验和是…

第十四章 集合(List)

一、集合框架体系 集合&#xff1a; &#xff08;1&#xff09;可以动态保存任意多个对象。 &#xff08;2&#xff09;提供了一系列方便的操作对象的方法&#xff1a;add、remove、set、get等。 二、Collection 1. Collection 接口常用方法 &#xff08;1&#xff09;add&a…

BP网络识别26个英文字母matlab

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;字母识别 获取完整源码源工程文件 一、 设计思想 字符识别在现代日常生活的应用越来越广泛&#xff0c;比如车辆牌照自动识别系统&#xff0c;手写识别系统&#xff0c;办公自动化等等。本文采用BP网络对26个英文字母进行…

第 377 场周赛 解题报告 | 珂学家 | Floyd + 划分型DP

前言 整体评价 天崩局&#xff0c;压哨绝杀&#xff0c;感谢天&#xff0c;感谢地&#xff0c;T_T. 感觉被T2玩惨了&#xff0c;T3和T4很像&#xff0c;无非一个贪心&#xff0c;一个是划分型DP&#xff0c;但是都需要基于floyd预处理。 T1. 最小数字游戏 思路&#xff1a; …

接口测试 — 11.logging日志模块处理流程

1、概括理解 了解了四大组件的基本定义之后&#xff0c;我们通过图示的方式来理解下信息的传递过程&#xff1a; 也就是获取的日志信息&#xff0c;进入到Logger日志器中&#xff0c;传递给处理器确定要输出到哪里&#xff0c;然后进行过滤器筛选&#xff0c;通过后再按照定义…

【LeetCode】链表精选11题

目录 快慢指针&#xff1a; 1. 相交链表&#xff08;简单&#xff09; 2. 环形链表&#xff08;简单&#xff09; 3. 快乐数&#xff08;简单&#xff09; 4. 环形链表 II&#xff08;中等&#xff09; 5. 删除链表的倒数第 N 个节点&#xff08;中等&#xff09; 递归迭…

量化投资策略的评估标准及其计算公式

收益率指标&#xff1a;分为策略的总收益率和策略的年化收益率 策略的总收益率&#xff1a; 策略的总收益率是评价一个策略盈利能力的最基本的指标&#xff0c;其计算方法为&#xff1a; 公式中Vt表示策略最终的股票和现金的总价值&#xff0c;V0表示策略最初的股票和现金的总…

探秘JDK 13的黑科技:新特性一览

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 探秘JDK 13的黑科技&#xff1a;新特性一览 前言switch表达式扩展Switch表达式的基本概念&#xff1a;使用Switch表达式的优势&#xff1a;示例代码&#xff1a;注意事项和最佳实践&#xff1a; Text …

Spring Cloud + Vue前后端分离-第7章 核心业务功能开发

Spring Cloud Vue前后端分离-第7章 核心业务功能开发 7-1 课程管理功能开发 课程管理页面美化 1.课程管理页面美化 demo-course.jpg 复制search.html中的部分代码 course.vue 看效果 测试一下新增修改删除效果 1.课程管理页面美化2 scoped:style下的样式只应用于当前组件…

LCT(link cut tree) 详细图解与应用

樱雪喵用时 3days 做了 ybtoj 的 3 道例题&#xff0c;真是太有效率了&#xff01;&#xff01;1 为了避免自己没学明白就瞎写东西误人子弟&#xff0c;这篇 Blog 拖到了现在。 图片基本沿用 OIwiki&#xff0c;原文跳步骤&#xff08;主要是 access 部分&#xff09;的就自己…

Spark编程实验三:Spark SQL编程

目录 一、目的与要求 二、实验内容 三、实验步骤 1、Spark SQL基本操作 2、编程实现将RDD转换为DataFrame 3、编程实现利用DataFrame读写MySQL的数据 四、结果分析与实验体会 一、目的与要求 1、通过实验掌握Spark SQL的基本编程方法&#xff1b; 2、熟悉RDD到DataFram…

如何利用flume进行日志采集

介绍 Apache Flume 是一个分布式、可靠、高可用的日志收集、聚合和传输系统。它常用于将大量日志数据从不同的源&#xff08;如Web服务器、应用程序、传感器等&#xff09;收集到中心化的存储或数据处理系统中。 基本概念 Agent&#xff08;代理&#xff09;&#xff1a; …

039、转置卷积

之——增大高宽 杂谈 通常来说&#xff0c;卷积不会增大输入的高宽&#xff0c;通常要么不变&#xff0c;要么减半&#xff1b;如果想要直接padding来增加高宽&#xff0c;在不断的卷积过程中&#xff0c;padding的0越来越多&#xff0c;最后要做像素级的判断时候&#xff0c;由…

分布式核心技术之分布式共识

文章目录 什么是分布式共识&#xff1f;分布式共识方法PoWPoSDPoS 三种分布式共识算法对比分析 选主过程就是一个分布式共识问题&#xff0c;因为每个节点在选出主节点之前都可以认为自己会成为主节点&#xff0c;也就是说集群节点“存异”&#xff1b;而通过选举的过程选出主节…

基于Java SSM框架实现医院挂号上班打卡系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现医院挂号上班打卡系统演示 摘要 在网络发展的时代&#xff0c;国家对人们的健康越来越重视&#xff0c;医院的医疗设备更加先进&#xff0c;医生的医术、服务水平也不断在提高&#xff0c;给用户带来了很大的选择余地&#xff0c;而且人们越来越追求更个…

【leetcode100-019】【矩阵】螺旋矩阵

【题干】 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 【思路】 不难注意到&#xff0c;每进行一次转向&#xff0c;都有一行/列被输出&#xff08;并失效&#xff09;&#xff1b;既然已经失效&#xff0c;那我…

MyBatis-Plus中默认方法对应的SQL到底长啥样?

我希望成为&#xff1a;自媒体圈中技术最好、实战经验最丰富的达人&#xff0c;技术圈中最会分享的架构师。加油&#xff01; 我的公众号&#xff1a;Hoeller 过段时间要给公司同事做Mybatis-Plus相关的培训&#xff0c;所以抓紧时间看看Mybatis-Plus的源码&#xff0c;顺便也分…

RT-Thread 内核对象管理框架

内核对象管理框架 RT-Thread采用内核对象管理系统来访问/管理所有内核对象&#xff0c;内核对象包含了内核中绝大部分设施&#xff0c;这些内核对象可以是静态分配的静态对象&#xff0c;也可以是从系统内存堆中分配的动态对象。 RT-Thread内核对象包括&#xff1a;线程&…

使用VisualStutio2022开发第一个C++程序

使用VisualStudio2022创建C项目 第一步&#xff1a;新建C的控制台应用 第二步&#xff1a;填写项目名称和代码存放位置&#xff0c;代码的存放目录不要有中文名 第三步:点击创建&#xff0c;VisualStudio会自动开始帮我们创建项目 第四步&#xff1a;项目创建好以后&…

2023,测试开发人的年终总结

根据TesterHome社区发帖整理。从该年终总结可以看出当前测试行业&#xff0c;哪怕是测试开发也是竞争很厉害。作为测试同仁&#xff0c;不但要掌握功能测试、接口测试、性能测试&#xff0c;掌握各种工具使用&#xff0c;还得懂开发&#xff0c;懂Java/Python&#xff0c;懂VUE…