概述
文章目录
- 一、概述
- 1、时间日期类简介
- 2、Date类型存在的问题
- 3、Calendar与Date
- 二、Java8新的日期时间类
- 1、LocalDate
- 2、LocalTime
- 3、LocalDateTime
- 4、Instant
- 三、日期时间的修改与计算
- 1、时间修改
- 2、SimpleDateFormat的坑
- 2.1 线程不安全
- 2.2 解析的字符串和格式不匹配的容忍高
- 3、格式化日期(常用)
- 四、日期时间类时区
- 1、Date类
- 2、日期时间的两种保存方式
- 3、Java8新日期类解决时区问题
- 五、LocalDateTime在SpringBoot中的应用
- 1、将LocalDateTime字段以时间戳的方式返回给前端 添加日期转化类
- 2、将LocalDateTime字段以指定格式化日期的方式返回给前端
- 3、对前端传入的日期进行格式化
- 4、前后端日期时间转化问题
- 5、全局日期格式转换器
一、概述
1、时间日期类简介
当我们开始使⽤Java操作⽇期和时间的时候,会有⼀些棘⼿。你也许会通过
System.currentTimeMillis()
来返回1970年1⽉1⽇到今天的毫秒数。或者使⽤ Date类来操作⽇期;当遇到加减⽉份、天数的时候 你⼜需要⽤到Calendar类; 当需要格式化⽇期的时候需要使⽤java.text.DateFormat
类
在 Java 8 之前,我们处理日期时间需求时,使用 Date
、Calender
和
SimpleDateFormat
,来声明时间戳、使用日历处理日期和格式化解析日期时间。但是,这
些类的 API 的缺点比较明显,比如可读性差、易用性差、使用起来冗余繁琐,还有线程安
全问题。
2、Date类型存在的问题
- 结构定义混乱
- java.util Date包含日期时间
- java.sql Date包含日期
- java.text 时间格式化
- API不易用
- 非线程安全
- 可变,SimpleDateFormate
- 国际化
Date
如果不格式化,打印出的日期可读性差- Calendar TimeZone
Tue Sep 10 09:34:04 CST 2020
3、Calendar与Date
public class DateTest {
public static void main(String[] args) {
wrong();
right();
better();
}
private static void wrong() {
System.out.println("wrong");
Date date = new Date(2019, 12, 31, 11, 12, 13);
System.out.println(date);
}
private static void wrongfix() {
System.out.println("right");
Date date = new Date(2019 - 1900, 11, 31, 11, 12, 13);
System.out.println(date);
}
private static void right() {
System.out.println("right");
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());
}
private static void better() {
System.out.println("better");
LocalDateTime localDateTime = LocalDateTime.of(2019, Month.DECEMBER, 31, 11, 12, 13);
System.out.println(localDateTime);
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("America/New_York"));
System.out.println(zonedDateTime);
}
}
二、Java8新的日期时间类
1、LocalDate
只会获取年月日
- 创建
LocalDate
//获取当前年月日
LocalDate localDate = LocalDate.now();
//构造指定的年月日
LocalDate localDate1 = LocalDate.of(2020, 10, 10);
- 获取年、月、日、星期几
//获取年
int year = localDate.getYear();
int year1 = localDate.get(ChronoField.YEAR);
//获取月份
Month month = localDate.getMonth();
int month1 = localDate.get(ChronoField.MONTH_OF_YEAR);
//获取天
int day = localDate.getDayOfMonth();
int day1 = localDate.get(ChronoField.DAY_OF_MONTH);
//获取星期几
DayOfWeek dayOfWeek = localDate.getDayOfWeek();
int dayOfWeek1 = localDate.get(ChronoField.DAY_OF_WEEK);
2、LocalTime
只会获取几点几分几秒
- 创建
LocalTime
LocalTime localTime = LocalTime.of(13, 51, 10);
LocalTime localTime1 = LocalTime.now();
- 获取时分秒
//获取小时
int hour = localTime.getHour();
int hour1 = localTime.get(ChronoField.HOUR_OF_DAY);
//获取分
int minute = localTime.getMinute();
int minute1 = localTime.get(ChronoField.MINUTE_OF_HOUR);
//获取秒
int second = localTime.getSecond();
int second1 = localTime.get(ChronoField.SECOND_OF_MINUTE);
3、LocalDateTime
获取年月日时分秒,等于LocalDate+LocalTime
- 创建
LocalDateTime
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime1 = LocalDateTime.of(2020, Month.SEPTEMBER, 10, 14, 46, 56);
LocalDateTime localDateTime2 = LocalDateTime.of(localDate, localTime);
LocalDateTime localDateTime3 = localDate.atTime(localTime);
LocalDateTime localDateTime4 = localTime.atDate(localDate);
- 获取
LocalDate
LocalDate localDate2 = localDateTime.toLocalDate();
- 获取
LocalTime
LocalTime localTime2 = localDateTime.toLocalTime();
4、Instant
获取秒数
- 创建
Instant
对象
Instant instant = Instant.now();
- 获取秒数
long currentSecond = instant.getEpochSecond();
- 获取毫秒数
long currentMilli = instant.toEpochMilli();
如果只是为了获取秒数或者毫秒数,使用
System.currentTimeMillis()
来得更为方便
三、日期时间的修改与计算
**LocalDate
、LocalTime
、LocalDateTime
、Instant
**为不可变对象,修改这些对象对象会返回一个副本
1、时间修改
LocalDateTime localDateTime = LocalDateTime.of(2020, Month.SEPTEMBER, 10,
14, 46, 56);
//增加一年
localDateTime = localDateTime.plusYears(1);
localDateTime = localDateTime.plus(1, ChronoUnit.YEARS);
//减少一个月
localDateTime = localDateTime.minusMonths(1);
localDateTime = localDateTime.minus(1, ChronoUnit.MONTHS);
//使用with进行修改
//修改年为2020
localDateTime = localDateTime.withYear(2020);
//修改为2022
localDateTime = localDateTime.with(ChronoField.YEAR, 2022);
另外比如有些时候想知道这个月的最后一天是几号、下个周末是几号,通过提供的时间和日期API可以很快得到答案,比如通过firstDayOfYear()
返回了当前日期的第一天日期
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)));
System.out.println("//自定义逻辑");
System.out.println(LocalDate.now().with(temporal -> temporal.plus(ThreadLocalRandom.current().nextInt(100), ChronoUnit.DAYS)));
Java 8 中有一个专门的类 Period 定义了日期间隔,通过 Period.between
得到了两个 LocalDate
的差,返回的是两个日期差几年零几月零几天。如果希望得知两个日期之间差几天,直接调用Period
的 getDays()
方法得到的只是最后的“零几天”,而不是算总的间隔天
public static void main(String[] args) throws Exception {
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));
}
// 计算日期差
//11
//P2M11D
//72
2、SimpleDateFormat的坑
2.1 线程不安全
定义的 static 的 SimpleDateFormat 可能会出现线程安全问题,需要每个线程单独new或者通过 ThreadLocal 来存放 SimpleDateFormat
private static ThreadLocal<SimpleDateFormat> threadSafeSimpleDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
2.2 解析的字符串和格式不匹配的容忍高
public static void main(String[] args) throws Exception {
String dateString = "20160901";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
System.out.println("result:" + dateFormat.parse(dateString));
}
// 结果,原因是把 0901 当成了月份,相当于 75 年
// result:Mon Jan 01 00:00:00 CST 2091
3、格式化日期(常用)
LocalDate localDate = LocalDate.of(2020, 10, 10);
String s1 = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
//自定义格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String s3 = localDate.format(dateTimeFormatter);
DateTimeFormatter
默认提供了多种格式化方式,如果默认提供的不能满足要求,可以通过DateTimeFormatter
的ofPattern
方法创建自定义格式化方式
解析时间
LocalDate localDate1 = LocalDate.parse("20201010", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate localDate2 = LocalDate.parse("2020-10-10", DateTimeFormatter.ISO_LOCAL_DATE);
和SimpleDateFormat
相比,DateTimeFormatter
是线程安全的,可以定义为 static 使用,最后,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();
四、日期时间类时区
1、Date类
- Date 并无时区问题,世界上任何一台计算机使用 new Date() 初始化得到的时间都一样。因为,Date中保存的是 UTC 时间,UTC 是以原子钟为基础的统一时间,不以太阳参照计时,并无时区划分
- Date 中保存的是一个时间戳,代表的是从 1970 年 1 月 1 日 0 点(Epoch 时间)到现在的毫秒数。尝试输出 Date(0):
/**
* 因为机器当前的时区是中国上海,相比 UTC 时差+8 小时:
*/
private static void test() {
// 设置默认区域
Locale.setDefault(Locale.SIMPLIFIED_CHINESE);
System.out.println("test");
System.out.println(new Date(0));
//System.out.println(TimeZone.getDefault().getID() + ":" + TimeZone.getDefault().getRawOffset()/3600/1000);
//ZoneId.getAvailableZoneIds().forEach(id -> System.out.println(String.format("%s:%s", id, ZonedDateTime.now(ZoneId.of(id)))));
}
// 结果:Thu Jan 01 08:00:00 CST 1970
2、日期时间的两种保存方式
- 以 UTC 保存,保存的时间没有时区属性,是不涉及时区时间差问题的世界统一时间。我们通常说的时间戳,或 Java 中的 Date 类就是用的这种方式(推荐)
- 以字面量保存,比如
年/月/日 时:分:秒
,一定要同时保存时区信息。只有有了时区信息,我们才能知道这个字面量时间真正的时间点,否则它只是一个给人看的时间表示,只在当前时区有意义。Calendar 是有时区概念的,所以我们通过不同的时区初始化 Calendar,得到了不同的时间
下面是两个时区错乱例子
// 对于同一个时间表示,比如 2020-01-02 22:00:00,不同时区的人转换成 Date会得到不同的时间(时间戳):
private static void wrong1() throws ParseException {
System.out.println("wrong1");
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());
}
// 结果
// Thu Jan 02 22:00:00 CST 2020:1577973600000
// Fri Jan 03 11:00:00 CST 2020:1578020400000
// 格式化后出现的错乱,即同一个 Date,在不同的时区下格式化得到不同的时间表示
private static void wrong2() throws ParseException {
System.out.println("wrong2");
String stringDate = "2020-01-02 22:00:00";
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
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));
}
// 结果
// [2020-01-02 22:00:00 +0800]
// [2020-01-02 09:00:00 -0500]
3、Java8新日期类解决时区问题
Java 8 推出了新的时间日期类 ZoneId
、ZoneOffset
、LocalDateTime
、ZonedDateTime
和 DateTimeFormatter
,处理时区问题更简单清晰
private static void right() {
System.out.println("right");
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 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));
}
// 结果
// Asia/Shanghai2020-01-02 21:00:00 +0800
// America/New_York2020-01-02 08:00:00 -0500
// +09:002020-01-02 22:00:00 +0900
五、LocalDateTime在SpringBoot中的应用
1、将LocalDateTime字段以时间戳的方式返回给前端 添加日期转化类
public class LocalDateTimeConverter extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNumber(value.toInstant(ZoneOffset.of("+8")).toEpochMilli());
}
}
并在LocalDateTime
字段上添加@JsonSerialize(using = LocalDateTimeConverter.class)
注解,如下:
@JsonSerialize(using = LocalDateTimeConverter.class)
protected LocalDateTime gmtModified;
2、将LocalDateTime字段以指定格式化日期的方式返回给前端
在LocalDateTime
字段上添加@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
注解即可,如下:
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
protected LocalDateTime gmtModified;
3、对前端传入的日期进行格式化
在LocalDateTime
字段上添加@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
注解即可,如下:
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
protected LocalDateTime gmtModified;
4、前后端日期时间转化问题
- 方式一
在实体类上加@DatetimeFormat
与@JsonFormat
注解
@DatetimeFormat
将前台日期字符串转换成Date格式 @DateTimeFormat(pattern="yyyy-MM-dd")
@JsonFormat
将服务器端Date日期转换成指定字符串格式 @JsonFormat(pattern="yyyy-MM-dd",timezone="GMT+8")
两个需要同时加,否则会有时区的问题
- 方式二
在applicition.properties中添加如下配置
#时间戳统一转换
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
或者在application.yml中添加如下配置
#时间戳统一转换
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
5、全局日期格式转换器
配置从页面接收的String和json格式的日期转换为Date类型,包括从Bean类转换,从此不再纠结用@DateTimeFormat(patten = "yyyyy-MM-dd")
还是@DateTimeFormat(patten = "yyyyy-MM-dd HH:mm")
下面是配置String类型表单传参转Date的转换器
//Converter<S,T> S: 代表的是源,将要转换的数据类型 T:目标类型,将会转成什么数据类型
@Component
public class GlobalFormDateConvert implements Converter<String, Date> {
//静态初始化定义日期字符串参数列表(需要转换的)
private static final List<String> paramList = new ArrayList<>();
//静态初始化可能初夏你的日期格式
private static final String param1 = "yyyy-MM";
private static final String param2 = "yyyy-MM-dd";
private static final String param3 = "yyyy-MM-dd HH:mm";
private static final String param4 = "yyyy-MM-dd HH:mm:ss";
//静态代码块,将日期参数加入到列表中
static {
paramList.add(param1);
paramList.add(param2);
paramList.add(param3);
paramList.add(param4);
}
//自定义函数,将字符串转Date 参1:传入的日期字符串 参2:格式参数
public Date parseDate(String source, String format) {
System.out.println("parseDate转换日期");
Date date = null;
try {
//日期格式转换器
DateFormat dateFormat = new SimpleDateFormat(format);
date = dateFormat.parse(source);
} catch (Exception e) {
e.printStackTrace();
}
return date;
}
//convert转换方法 ,s是将会传递过来的日期的字符串
@Override
public Date convert(String source) {
System.out.println("convert日期格式转换器");
if(StringUtils.isEmpty(source)){
return null;
}
source = source.trim(); //去除首尾空格
DateFormat dateFormat = new SimpleDateFormat(param1);
//正则表达式判断是哪一种格式参数
if (source.matches("^\d{4}-\d{1,2}$")) {
return parseDate(source, paramList.get(0));
} else if (source.matches("^\d{4}-\d{1,2}-\d{1,2}$")) {
return parseDate(source, paramList.get(1));
} else if (source.matches("^\d{4}-\d{1,2}-\d{1,2} {1}\d{1,2}:\d{1,2}$")) {
return parseDate(source, paramList.get(2));
} else if (source.matches("^\d{4}-\d{1,2}-\d{1,2} {1}\d{1,2}:\d{1,2}:\d{1,2}$")) {
return parseDate(source, paramList.get(3));
} else {
throw new IllegalArgumentException("还未定义该种字符串转Date的日期转换格式 --> 【日期格式】:" + source);
}
}
}
配置Json数据转Date的全局日期转换器
public class GlobalJsonDateConvert extends StdDateFormat {
//静态初始化final,共享
public static final GlobalJsonDateConvert instance = new GlobalJsonDateConvert();
//覆盖parse(String)这个方法即可实现
@Override
public Date parse(String dateStr, ParsePosition pos) {
return getDate(dateStr, pos);
}
@Override
public Date parse(String dateStr) {
ParsePosition pos = new ParsePosition(0);
return getDate(dateStr, pos);
}
private Date getDate(String dateStr, ParsePosition pos) {
System.out.println("json格式日期转换");
SimpleDateFormat sdf = null;
if (StringUtils.isEmpty(dateStr)) {
return null;
} else if (dateStr.matches("^\d{4}-\d{1,2}$")) {
sdf = new SimpleDateFormat("yyyy-MM");
return sdf.parse(dateStr, pos);
} else if (dateStr.matches("^\d{4}-\d{1,2}-\d{1,2}$")) {
sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(dateStr, pos);
} else if (dateStr.matches("^\d{4}-\d{1,2}-\d{1,2} {1}\d{1,2}:\d{1,2}$")) {
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
return sdf.parse(dateStr, pos);
} else if (dateStr.matches("^\d{4}-\d{1,2}-\d{1,2} {1}\d{1,2}:\d{1,2}:\d{1,2}$")) {
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(dateStr, pos);
} else if (dateStr.length() == 23) {
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
return sdf.parse(dateStr, pos);
}
return super.parse(dateStr, pos);
}
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date, toAppendTo, fieldPosition);
}
@Override
public GlobalJsonDateConvert clone() {
return new GlobalJsonDateConvert();
}
}
最后将配置bean交给Spring管理
@Configuration
public class WebConfig {
//JSON格式 全局日期转换器配置
@Bean
public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
//设置日期格式
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(GlobalJsonDateConvert.instance);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
//设置中文编码格式
List<MediaType> list = new ArrayList<MediaType>();
list.add(MediaType.APPLICATION_JSON);
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list);
return mappingJackson2HttpMessageConverter;
}
//表单格式 全局日期转换器
@Bean
@Autowired
public ConversionService getConversionService(GlobalFormDateConvert globalDateConvert){
ConversionServiceFactoryBean factoryBean = new ConversionServiceFactoryBean();
Set<Converter> converters = new HashSet<>();
converters.add(globalDateConvert);
factoryBean.setConverters(converters);
return factoryBean.getObject();
}
}
参考文章:
https://www.cnblogs.com/w-essay/p/11453943.html
https://mp.weixin.qq.com/s/7LzRjG_uGjXq5yIwiSnV2w
最后
以上就是聪慧泥猴桃为你收集整理的Java8 日期时间类整理一、概述二、Java8新的日期时间类三、日期时间的修改与计算四、日期时间类时区五、LocalDateTime在SpringBoot中的应用的全部内容,希望文章能够帮你解决Java8 日期时间类整理一、概述二、Java8新的日期时间类三、日期时间的修改与计算四、日期时间类时区五、LocalDateTime在SpringBoot中的应用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复