一、前言
二、常用时间类 1.为什么JDK8新增了时间处理类
- 学习概述:在Java开发中必不可少的要处理时间,那前后端如何传递时间参数?数据库的时间使用什么类型?Java中时间转换的各种方式有哪些?经过本篇文章带大家一一了解。如果有不正确的地方,欢迎大家评论讨论,谢谢。
- 学习目标:读完这篇文章之后,希望你能学会熟练使用JDK8z中各种时间转换方法。
老版本的java.util.Date与java.util.Calendar类是线程不安全的,并且使用起来复杂。
线程不安全示例:
final static SimpleDateFormat YYY_MM_DD_HH_MM_SS = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Date date = null;
try {
date = YYY_MM_DD_HH_MM_SS.parse("2022-05-11 23:12:12");
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date);
}).start();
}
}
2.JDK8新增的时间类
JDK8在java.util.time目录下增加了大量的时间处理类
类名 | 用途 |
Instant | 用于老版本时间转换为新版本 |
Duration | 表示秒或纳秒时间间隔,适合处理较短的时间,需要更高的精确性。可用于秒杀 |
Period | 表示一段时间的年、月、日 |
LocalDate | 是一个不可变的日期时间对象,表示日期,通常被视为年月日 |
LocalTime | 是—个不可变的日期时间对象,代表一个时间,通常被看作是时分秒 |
LocalDateTime | 不可变的日期时间对象,代表日期时间,通常被视为年-月-日-时分秒 |
ZonedDateTime | 有时区的日期时间的不可变表示,此类存储所有日期和时间字段,精度为纳秒,时区为区域偏移量,用于处理模糊的本地日期时间 |
//打印所有时区
ZoneId.getAvailableZoneIds().iterator().forEachRemaining(System.out::println);
//获取默认时区
System.out.println(ZoneId.systemDefault());
4. 修改时间
public static void dateOperator(){
LocalDateTime now = LocalDateTime.now();
//当前时间添加一小时
System.out.println(now.plusHours(1));
//当前时间减去一小时
System.out.println(now.minusHours(2));
//当前时间修改为4点
System.out.println(now.withHour(4));
//修改为10点
System.out.println(now.with(ChronoField.HOUR_OF_DAY, 10));
//计算1年10天5小时后的时间
Period of = Period.of(1, 10, 5);
System.out.println(now.plus(of));
}
5. Date/Calendar转为LocalDate
public static void date2Local(){
//1.java.util.Date类转为LocalDate
Date date = new Date();
//Date包含日期和时间,但是并不提供时区信息
Instant instant = date.toInstant();
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
System.out.println("java.util.Date=" + date + "; toLocalDate=" + zonedDateTime.toLocalDate());
//2.java.sql.Date类转为LocalDate
java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
LocalDate r = sqlDate.toLocalDate();
System.out.println("java.sql.Date=" + sqlDate + "; toLocalDate = " + r);
//3.java.sql.Timestamp类转为LocalDate
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
LocalDateTime localDateTime = timestamp.toLocalDateTime();
System.out.println("java.sql.Timestamp=" + timestamp + "; toLocalDate = " + localDateTime);
//4.java.util.Calendar类转为LocalDate
Calendar calendar = Calendar.getInstance();
//4.1 先获取Calendar的时区
TimeZone timeZone = calendar.getTimeZone();
//4.2 TimeZone转为ZoneId
ZoneId zoneId = timeZone.toZoneId();
ZonedDateTime calZone = ZonedDateTime.ofInstant(calendar.toInstant(), zoneId);
System.out.println("java.util.Calendar=" + calendar + "; toLocalDate=" + calZone);
//方式二:Calendar封装的月份是从0开始的,所以在这里获取到的month要+1
LocalDateTime.of(calendar.get(Calendar.YEAR),calendar.get(Calendar.MONTH) + 1,calendar.get(Calendar.DAY_OF_MONTH)
,calendar.get(Calendar.HOUR_OF_DAY),calendar.get(Calendar.MINUTE),calendar.get(Calendar.SECOND));
}
6. DateTimeFormatter格式化器
public static void dateTimeFormatter(){
LocalDateTime now = LocalDateTime.now();
System.out.println(now.format(DateTimeFormatter.BASIC_ISO_DATE));
System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
System.out.println(now.format(DateTimeFormatter.ISO_DATE_TIME));
/**
* 注意此种方式在不同时区的显示方式不一样,在其他时区不会显示中文,会根据当前
* 系统的默认时区来进行区别显示.
*/
System.out.println(now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)));
System.out.println(now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)));
System.out.println(now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)));
System.out.println(now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)));
}
7. TemporalAdjusters调节器打印结果:
BASIC_ISO_DATE:20220512
ISO_LOCAL_DATE_TIME:2022-05-12T22:14:10.602321
ISO_DATE_TIME:2022-05-12T22:14:10.602321
FormatStyle.FULL:2022年5月12日星期四
FormatStyle.MEDIUM:2022年5月12日
FormatStyle.SHORT:2022/5/12
FormatStyle.LONG:2022年5月12日
public static void temporalAdjusters(){
LocalDateTime now = LocalDateTime.now();
//将时间修改为当月的第一天
System.out.println(now.with(TemporalAdjusters.firstDayOfMonth()));
//将时间修改为下一个月的第一天
System.out.println(now.with(TemporalAdjusters.firstDayOfNextMonth()));
//将时间修改为当月的最后一天
System.out.println(now.with(TemporalAdjusters.lastDayOfMonth()));
//将时间修改为当月的最后一个星期五
System.out.println(now.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)));
//将时间修改为下一个星期五
System.out.println(now.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)));
//将时间修改为上一个星期五
System.out.println(now.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)));
}
8. TemporalQuery查询器
学习的时态类对象(LocalDate,LocalTime)都有一个方法叫做query,可以针对日期进行查询。
R query(TemporalQuery query)这个方法是一个泛型方法,返回的数据就是传入的泛型类的类型,TemporalQuery是一个泛型接口,里面有一个抽象方法是R queryFrom (TemporalAccessor temporal),TemporalAccessor是Temporal的父接口,实际上也就是LocalDate,LocalDateTime相关类的顶级父接口,这个queryFrom的方法的实现逻辑就是,传入一个日期/时间对象通过自定义逻辑返回数据.
如果要计划曰期距离某一个特定天数差距多少天,可以自定义类实现TemporalQuery接口并且作为
参数传入到query方法中。
2.问题解析
- 需求:计算当前时间距离下一个劳动节还有多少天?
思路:
3.编码实现1. 使用TemporalQuery查询器 传入当前时间,由查询器处理完后返回
static class Next51Query implements TemporalQuery{
@Override
public Long queryFrom(TemporalAccessor temporal) {
LocalDate from = LocalDate.from(temporal);
LocalDate next = LocalDate.of(from.getYear(), Month.MAY, 1);
//当前时间已经过了五一
if(from.isAfter(next)) {
next = next.plusYears(1);
}
//通过ChronoUnit计算两个时间的间隔
return ChronoUnit.DAYS.between(from, next);
}
}
public static void main(String[] args) {
LocalDate of = LocalDate.of(2022, 5, 4);
System.out.println(of.query(new Next51Query()));
}
4.输出结果
362
四、TemporalAdjusters例题
1.问题描述
2.编码实现
- 需求:发工资是每个月15号 如果15号是周末 则调整为上一个周五
public static void testPayAdjuster(){
LocalDate of = LocalDate.of(2022, 5, 15);
LocalDate from = LocalDate.from(new PayAdjuster().adjustInto(of));
System.out.println("预计发薪日=" + of + ";实际发薪日=" + from);
}
/**
* 获取下一个发薪日的日期
* 发工资是每个月15号 如果15号是周末 则调整为上一个周五
*/
static class PayAdjuster implements TemporalAdjuster{
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate from = LocalDate.from(temporal);
from = from.withDayOfMonth(15);
if(from.getDayOfWeek().equals(DayOfWeek.SUNDAY) || from.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
from = from.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
}
return from;
}
}
3.输出结果
预计发薪日=2022-05-15;实际发薪日=2022-05-13
五、时间在前后端中如何处理
1. 后端接口中统一使用毫秒时间戳
时间戳是从格林威治时间 1970 年 1 月 1 日至当前时间的总秒数,例如 1608523771000。注意这个基准是带时区的,格林威治时间也就是零时区,我们是东八区。
2. 前端按时区格式化时间不同地区展示时间格式不同,具体用哪个格式,该有由前端根据页面场景来灵活使用。所以用什么形式属于展现层面的问题,展现是前端的范畴。 后端接口负责的是数据
六、时间在数据库中如何存储 1. 切记不能用字符串存储2. DateTime/TimeStamp/Long比较
- 字符串占用的空间更大!
- 字符串存储的日期比较效率比较低(逐个字符进行比对),无法用日期相关的 API 进行计算和比较。
DateTime 类型是没有时区信息的(时区无关) ,DateTime 类型保存的时间都是当前会话所设置的时区对应的时间。当你的时区更换之后,比如你的服务器更换地址或者更换客户端连接时区设置的话,就会导致你从数据库中读出的时间错误。不要小看这个问题,很多系统就是因为这个问题闹出了很多笑话。
Timestamp 和时区有关。Timestamp 类型字段的值会随着服务器时区的变化而变化,自动换算成相应的时间,说简单点就是在不同时区,查询到同一个条记录此字段的值会不一样。
日期类型 | 存储空间 | 日期展示格式 | 日期范围 | 是否存在时区问题 |
---|---|---|---|---|
Datetime | 8字节 | YYYY-MM-DD HH:MM:SS | 1000-01-01 00:00:00 ~9999-12-31 23:59:59 | 是 |
Timestamp | 4字节 | YYYY-MM-DD HH:MM:SS | 1970-01-01 00:00:00 ~2037-12-31 23:59:59 | 否 |
数值型时间戳 | 4字节(int) 8字节(bigint) | 全数字如1608891850712 | 1970-01-01 00:00:01 之后的时间 | 否 |
个人推荐使用bigint来存储毫秒时间戳,后端存储的是0时区的毫秒时间戳,返回给前端时按用户所在时区展示时间。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)