《开发实战》13 | 用好Java 8的日期时间类,少踩一些“老三样”的坑

13 | 用好Java 8的日期时间类,少踩一些“老三样”的坑

初始化日期时间

如果要初始化一个 2019 年 12 月 31 日 11 点 12 分 13秒这样的时间,
Date date = new Date(2019, 12, 31, 11, 12, 13);
输出的时间是 3029 年 1 月 31 日 11 点 12 分 13 秒:Sat Jan 31 11:12:13 CST 3920
是新手的低级错误:年应该是和 1900 的差值,月应该是从0 到 11 而不是从 1 到 12。
Date date = new Date(2019 - 1900, 11, 31, 11, 12, 13);
但更重要的问题是,当有国际化需求时,需要使用 Calendar 类来初始化时间

Calendar calendar = Calendar.getInstance();
calendar.set(2019, 11, 31, 11, 12, 13);
System.out.println(calendar.getTime());
Calendar calendar2 = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));
calendar2.set(2019, Calendar.DECEMBER, 31, 11, 12, 13);
System.out.println(calendar2.getTime());
// 输出显示了两个时间,说明时区产生了作用.对现在输出的日期格式还不满意:
Tue Dec 31 11:12:13 CST 2019
Wed Jan 01 00:12:13 CST 2020

“恼人”的时区问题

全球有 24 个时区,同一个时刻不同时区的时间是不一样的。
关于 Date 类,我们要有两点认识:

  1. Date 并无时区问题,世界上任何一台计算机使用 new Date() 初始化得到的时间都一样。因为,Date 中保存的是 UTC 时间,UTC 是以原子钟为基础的统一时间,不以太阳参照计时,并无时区划分。
  2. Date 中保存的是一个时间戳,代表的是从 1970 年 1 月 1 日 0 点(Epoch 时间)到现在的毫秒数。尝试输出 Date(0):
System.out.println(new Date(0));
System.out.println(TimeZone.getDefault().getID() + ":" + TimeZone.getDefault().getRawOffset()/3600000);

国际化的项目,处理好时间和时区问题首先就是要正确保存日期时间。这里有两种保存方式:

  1. 以 UTC 保存,保存的时间没有时区属性,是不涉及时区时间差问题的世界统一时间。我们通常说的时间戳,或 Java 中的 Date 类就是用的这种方式,这也是推荐的方式。
  2. 以字面量保存,比如年 / 月 / 日 时: 分: 秒,一定要同时保存时区信息。只有有了时区信息,我们才能知道这个字面量时间真正的时间点,否则它只是一个给人看的时间表示,只在当前时区有意义。Calendar 是有时区概念的,所以我们通过不同的时区初始化 Calendar,得到了不同的时间。

对于同一个时间表示,比如 2020-01-02 22:00:00,不同时区的人转换成 Date会得到不同的时间(时间戳)

String stringDate = "2020-01-02 22:00:00";
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 默认时区解析时间表示
Date date1 = inputFormat.parse(stringDate);
System.out.println(date1 + ":" + date1.getTime());
// 纽约时区解析时间表示
inputFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
Date date2 = inputFormat.parse(stringDate);
System.out.println(date2 + ":" + date2.getTime());
// 对于当前的上海时区和纽约时区,转化为 UTC 时间戳是不同的时间:
Thu Jan 02 22:00:00 CST 2020:1577973600000
Fri Jan 03 11:00:00 CST 2020:1578020400000

格式化后出现的错乱,即同一个 Date,在不同的时区下格式化得到不同的时间表示。比如,在我的当前时区和纽约时区格式化 2020-01-02 22:00:00

String stringDate = "2020-01-02 22:00:00";
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//同一Date
Date date = inputFormat.parse(stringDate);
//默认时区格式化输出:
System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss Z]").format(date));
//纽约时区格式化输出
TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss Z]").format(date));

我当前时区的 Offset(时差)是 +8 小时,对于 -5 小时的纽约,晚上 10 点对应早上 9 点:
[2020-01-02 22:00:00 +0800]
[2020-01-02 09:00:00 -0500]

所以,要正确处理时区,在于存进去和读出来两方面:
存的时候,需要使用正确的当前时区来保存,这样 UTC 时间才会正确;
读的时候,也只有正确设置本地时区,才能把 UTC 时间转换为正确的当地时间。
Java 8 推出了新的时间日期类 ZoneId、ZoneOffset、LocalDateTime、ZonedDateTime和 DateTimeFormatter
首先初始化上海、纽约和东京三个时区。我们可以使用 ZoneId.of 来初始化一个标准的时区,也可以使用 ZoneOffset.ofHours 通过一个 offset,来初始化一个具有指定时间差的自定义时区。
对于日期时间表示,LocalDateTime 不带有时区属性,所以命名为本地时区的日期时间;而 ZonedDateTime=LocalDateTime+ZoneId,具有时区属性。因此,LocalDateTime 只能认为是一个时间表示,ZonedDateTime 才是一个有效的时间。在这里我们把 2020-01-02 22:00:00 这个时间表示,使用东京时区来解析得到一个ZonedDateTime。
使用 DateTimeFormatter 格式化时间的时候,可以直接通过 withZone 方法直接设置格式化使用的时区。最后,分别以上海、纽约和东京三个时区来格式化这个时间输出:

//一个时间表示
String stringDate = "2020-01-02 22:00:00";
//初始化三个时区
ZoneId timeZoneSH = ZoneId.of("Asia/Shanghai");
ZoneId timeZoneNY = ZoneId.of("America/New_York");
ZoneId timeZoneJST = ZoneOffset.ofHours(9);
//格式化器
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ZonedDateTime date = ZonedDateTime.of(LocalDateTime.parse(stringDate, dateTimeFormatter), timeZoneJST);
//使用DateTimeFormatter格式化时间,可以通过withZone方法直接设置格式化使用的时区
DateTimeFormatter outputFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");
System.out.println(timeZoneSH.getId() + outputFormat.withZone(timeZoneSH).format(date));
System.out.println(timeZoneNY.getId() + outputFormat.withZone(timeZoneNY).format(date));
System.out.println(timeZoneJST.getId() + outputFormat.withZone(timeZoneJST).format(date));

我推荐使用 Java 8 的日期时间类,即使用 ZonedDateTime 保存时间,然后使用设置了 ZoneId 的 DateTimeFormatter 配合ZonedDateTime 进行时间格式化得到本地时间表示

日期时间格式化和解析

这明明是一个 2019 年的日期,怎么使用 SimpleDateFormat 格式化后就提前跨年了?

Locale.setDefault(Locale.SIMPLIFIED_CHINESE);
System.out.println("defaultLocale:" + Locale.getDefault());
Calendar calendar = Calendar.getInstance();
calendar.set(2019, Calendar.DECEMBER, 29,0,0,0);
SimpleDateFormat YYYY = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("格式化: " + YYYY.format(calendar.getTime()));
System.out.println("weekYear:" + calendar.getWeekYear());
System.out.println("firstDayOfWeek:" + calendar.getFirstDayOfWeek());
System.out.println("minimalDaysInFirstWeek:" + calendar.getMinimalDaysInFirstWeek());

输出:
defaultLocale:zh_CN
格式化: 2020-12-29
weekYear:2020
firstDayOfWeek:1
minimalDaysInFirstWeek:1

这是因为:小写 y 是年,而大写 Y 是 week year,也就是所在的周属于哪一年
一年第一周的判断方式是,从 getFirstDayOfWeek() 开始,完整的 7 天,并且包含那一年至少 getMinimalDaysInFirstWeek() 天。这个计算方式和区域相关,对于当前 zh_CN 区域来说,2020 年第一周的条件是,从周日开始的完整 7 天,2020 年包含 1 天即可。显然,2019 年 12 月 29 日周日到 2020 年 1 月 4 日周六是 2020 年第一周,得出的 weekyear 就是 2020 年。
没有特殊需求,针对年份的日期格式化,应该一律使用 “y” 而非“Y”

定义的 static 的 SimpleDateFormat 可能会出现线程安全问题
因此只能在同一个线程复用 SimpleDateFormat,比较好的解决方式是,通过 ThreadLocal 来存放 SimpleDateFormat:
private static ThreadLocal threadSafeSimpleDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”));
当需要解析的字符串和格式不匹配的时候,SimpleDateFormat 表现得很宽容,还是能得到结果

String dateString = "20160901";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
System.out.println("result:" + dateFormat.parse(dateString));

result:Mon Jan 01 00:00:00 CST 2091

输出了 2091 年 1 月 1 日,原因是把 0901 当成了月份,相当于 75 年。

对于 SimpleDateFormat 的这三个坑,我们使用 Java 8 中的 DateTimeFormatter 就可以避过去

private static DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
        .appendValue(ChronoField.YEAR) //年
        .appendLiteral("/")
        .appendValue(ChronoField.MONTH_OF_YEAR) //月
        .appendLiteral("/")
        .appendValue(ChronoField.DAY_OF_MONTH) //日
        .appendLiteral(" ")
        .appendValue(ChronoField.HOUR_OF_DAY) //时
        .appendLiteral(":")
        .appendValue(ChronoField.MINUTE_OF_HOUR) //分
        .appendLiteral(":")
        .appendValue(ChronoField.SECOND_OF_MINUTE) //秒
        .appendLiteral(".")
        .appendValue(ChronoField.MILLI_OF_SECOND) //毫秒
        .toFormatter();

日期时间的计算

有些开发计算30天后的时间,

Date today = new Date();
Date nextMonth = new Date(today.getTime() + 30L * 1000 * 60 * 60 * 24);
System.out.println(today);
System.out.println(nextMonth);

一定要使用30L,不然会int会溢出。

还有更简单的方法。
对于 Java 8 之前的代码,我更建议使用 Calendar:

Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.DAY_OF_MONTH, 30);
System.out.println(c.getTime());

使用 Java 8 的日期时间类型,可以直接进行各种计算

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime.plusDays(30));

对日期时间做计算操作,Java 8 日期时间 API 会比 Calendar 功能强大很多

减一天和加一天,以及减一个月和加一个月:
System.out.println(LocalDate.now()
.minus(Period.ofDays(1))
.plus(1, ChronoUnit.DAYS)
.minusMonths(1)
.plus(Period.ofMonths(1)));

System.out.println("//本月的第一天");
System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()));
System.out.println("//今年的程序员日");
System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfYear()).plusDays(255));
System.out.println("//今天之前的一个周六");
System.out.println(LocalDate.now().with(TemporalAdjusters.previous(DayOfWeek.SATURDAY)));
System.out.println("//本月最后一个工作日");
System.out.println(LocalDate.now().with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)));

可以直接使用 lambda 表达式进行自定义的时间调整。比如,为当前时间增加 100天以内的随机天数:
System.out.println(LocalDate.now().with(temporal -> temporal.plus(ThreadLocalRandom.current().nextInt(100), ChronoUnit.DAYS)));

自定义函数,判断指定日期是否是家庭成员的生日:

public static Boolean isFamilyBirthday(TemporalAccessor date) {
  int month = date.get(MONTH_OF_YEAR);
  int day = date.get(DAY_OF_MONTH);
  if (month == Month.FEBRUARY.getValue() && day == 17)
    return Boolean.TRUE;
  if (month == Month.SEPTEMBER.getValue() && day == 21)
    return Boolean.TRUE;
  if (month == Month.MAY.getValue() && day == 22)
    return Boolean.TRUE;
  return Boolean.FALSE;
}

使用 query 方法查询是否匹配条件:

System.out.println("//查询是否是今天要举办生日");
System.out.println(LocalDate.now().query(CommonMistakesApplication::isFamilyBirthday));

但计算两个日期差时可能会踩坑:Java 8 中有一个专门的类 Period 定义了日期间隔,通过 Period.between 得到了两个 LocalDate 的差,返回的是两个日期差几年零几月零几天。如果希望得知两个日期之间差几天,直接调用Period 的 getDays() 方法得到的只是最后的“零几天”,而不是算总的间隔天数。
比如,计算 2019 年 12 月 12 日和 2019 年 10 月 1 日的日期间隔,很明显日期差是 2 个月零 11 天,但获取 getDays 方法得到的结果只是 11 天,而不是 72 天:

System.out.println("//计算日期差");
LocalDate today = LocalDate.of(2019, 12, 12);
LocalDate specifyDate = LocalDate.of(2019, 10, 1);
System.out.println(Period.between(specifyDate, today).getDays());
System.out.println(Period.between(specifyDate, today));
System.out.println(ChronoUnit.DAYS.between(specifyDate, today));

可以使用 ChronoUnit.DAYS.between 解决这个问题.

image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
网络安全设计方案 目录 网络安全问题………………………………………………3 设计的安全性………………………………………………3 可用性…………………………………………………………..3 机密性…………………………………………………………..3 完整性…………………………………………………………..3 可控性…………………………………………………………..3 可审查性………………………………………………………..3 访问控制………………………………………………………..3 数据加密………………………………………………………..3 安全审计………………………………………………………..3 3、安全设计方案………………………………………………5 设备选型………………………………………………………..5 网络安全………………………………………………………..7 访问控制………………………………………………………...9 入侵检测………………………………………………………..10 4、总结…………………………………………………………11 1、网络安全问题 随着互联网的飞速发展,网络安全逐渐成为一个潜在的巨大问题。网络安全性是一个 涉及面很广泛的问题,其中也会涉及到是否构成犯罪行为的问题。在其最简单的形式中 ,它主要关心的是确保无关人员不能读取,更不能修改传送给其他接收者的信息。此时 ,它关心的对象是那些无权使用,但却试图获得远程服务的人。安全性也处理合法消息 被截获和重播的问题,以及发送者是否曾发送过该条消息的问题。 大多数安全性问题的出现都是由于有恶意的人试图获得某种好处或损害某些人而故意 引起的。可以看出保证网络安全不仅仅是使它没有编程错误。它包括要防范那些聪明的 ,通常也是狡猾的、专业的,并且在时间和金钱上是很充足、富有的人。同时,必须清 楚地认识到,能够制止偶然实施破坏行为的敌人的方法对那些惯于作案的老手来说,收 效甚微。 网络安全性可以被粗略地分为4个相互交织的部分:保密、鉴别、反拒认以及完整性 控制。保密是保护信息不被未授权者访问,这是人们提到的网络安全性时最常想到的内 容。鉴别主要指在揭示敏感信息或进行事务处理之前先确认对方的身份。反拒认主要与 签名有关。保密和完整性通过使用注册过的邮件和文件锁来 设备选型 传统的组网已经不能满足现在网络应用的变化了,在组网的初期必须考虑到安全和网络 的问题,考虑到这个问题我们就不能不考虑免疫网络的作用以及前景如何。 免疫网络—— 免疫网络是企业信息网络的一种安全形式。 "免疫"是生物医学的名词,它指的是人体所具有的"生理防御、自身稳定与免疫监视"的 特定功能。 就像我们耳熟能详的电脑病毒一样,在电脑行业,"病毒"就是对医学名词形象的借用。 同样,"免疫"也被借用于说明计算机网络的一种能力和作用。免疫就是让企业的内部网 络也像人体一样具备"防御、稳定、监视"的功能。这样的网络就称之为免疫网络。 免疫网络的主要理念是自主防御和管理,它通过源头抑制、群防群控、全网联动使网络 内每一个节点都具有安全功能,在面临攻击时调动各种安全资源进行应对。 它具有安全和网络功能融合、全网设备联动、可信接入、深度防御和控制、精细带宽管 理、业务感知、全网监测评估等主要特征。 下面让我们看看这几个特征的距离内容 安全和网络功能的融合 网络架构的融合,主要包括网关和终端的融合 网关方面:ARP先天免疫原理—NAT表中添加源MAC地址滤窗防火墙—封包检测,IP分片检查 UDP洪水终端方面:驱动部分—免疫标记 网络协议的融合—行为特征和网络行为的融合 全网设备的联动 驱动与运营中心的联动分收策略 驱动与驱动的联动IP地址冲突 网关和驱动的联动群防群控 运营中心和网关的联动(外网攻击,上下线 可信接入 MAC地址的可信(类似于DNA),生物身份 传输的可信(免疫标记) 深度防御和控制 深入到每个终端的网卡 深入到协议的最低层 深入到二级路由,多级路由器下 精细带宽管理 身份精细—IP/MAC的精确 位置精确—终端驱动 路径细分(特殊的IP) 流量去向(内,公网) 应用流控(QQ,MSN) 业务感知 协议区分和应用感知 它与防火墙(FW)、入侵检测系统(IDS)、防病毒等"老三样"组成的安全网络相比,突 破了被动防御、边界防护的局限,着重从内网的角度解决攻击问题,应对目前网络攻击 复杂性、多样性、更多从内网发起的趋势,更有效地解决网络威胁。 同时,安全和管理密不可分。免疫网络对基于可信身份的带宽管理、业务感知和控制, 以及对全网安全问题和工作效能的监测、分析、统计、评估,保证了企业网络的可管可 控,大大提高了通信效率和可靠性。 安全架构分析 根据企业网络现状及发展趋势,主要安全措施从以下几个方面进行考虑:   网络传输保护

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值