java8中的日期和时间API

时间:2023-12-01 18:37:32

一、背景

jdk 1.8 之前, Java 时间使用java.util.Date 和 java.util.Calendar 类。

Date today = new Date();
System.out.println(today); // 转为字符串
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String todayStr = sdf.format(today);
System.out.println(todayStr);

Date 的几个问题

1.如果不格式化,Date打印出的日期可读性差;

2.可以使用 SimpleDateFormat 对时间进行格式化,但 SimpleDateFormat 是线程不安全的(阿里巴巴开发手册中禁用static修饰SimpleDateFormat);

  3,Date对时间处理比较麻烦,比如想获取某年、某月、某星期,以及 n 天以后的时间,如果用Date来处理的话真是太难了,并且 Date 类的 getYear()、getMonth() 这些方法都被弃用了

public class DataFormatUtils {
private static DateFormat dateFormat=new SimpleDateFormat("yyyy-mm-dd hh:MM:ss");
public static String format(Date date){
return dateFormat.format(date);
}
public static Date parse(String date){
Date newDate=null;
try {
newDate= dateFormat.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
return newDate;
}
@Test
public void testSimpleDateFormat(){
ThreadPoolExecutor executor=new ThreadPoolExecutor(5, 10, 10L, TimeUnit.SECONDS, new BlockingArrayQueue<>(10),new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
while (true){
executor.execute(new Runnable() {
@Override
public void run() {
try {
String dateString = dateFormat.format(new Date());
String dateString1=dateFormat.format(dateFormat.parse(dateString));
System.out.println("thread running:" + Thread.currentThread().getName()+" :"+dateString.equals(dateString1));
} catch (ParseException e) {
e.printStackTrace();
}
}
});
}
}
}
class MyThreadFactory implements ThreadFactory{
private AtomicInteger counter=new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread thread= new Thread(r);
int c = counter.getAndIncrement();
thread.setName("MyThreadPool:"+c);
return thread;
}
}

上述代码执行结果:

thread running:MyThreadPool:0  :false
Exception in thread "MyThreadPool:1" Exception in thread "MyThreadPool:7" Exception in thread "MyThreadPool:3" Exception in thread "MyThreadPool:8" Exception in thread "MyThreadPool:4" Exception in thread "MyThreadPool:6" java.lang.NumberFormatException: For input string: ""
thread running:MyThreadPool:0 :true
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:601)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
thread running:MyThreadPool:0 :false
at com.bjmashibing.system.baierhu.stream.DateFormat.DataFormatUtils$1.run(DataFormatUtils.java:36)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
thread running:MyThreadPool:14 :false
thread running:MyThreadPool:9 :true
thread running:MyThreadPool:17 :true
thread running:MyThreadPool:0 :true
Exception in thread "MyThreadPool:15" java.lang.NumberFormatException: empty String
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
thread running:MyThreadPool:14 :true

结果在多线程情况下证明同一时间格式化之后返回结果不一致,查看format方法,可以看到

calendar.setTime(date);

多个线程使用同一个calendar,set时间,肯定会发生线程问题

解决方案:

1.在多线程中每次new 一个simpleDateFormat,注意对象的引用在多线程内,不能放在成员变量,否则还是会有多线程问题

2.使用threadLocal,threadLocal会保存每个线程中保存对象的副本,不会有多线程问题

public class DataFormatUtils {

    //private static DateFormat dateFormat;
private static final ThreadLocal<DateFormat>dateFormat=new ThreadLocal<DateFormat>(){
@Override /**创建的时候初始化值*/
protected DateFormat initialValue() {
return new SimpleDateFormat();
}
}; public static String format(Date date){
return dateFormat.get().format(date);
}
public static Date parse(String date){
Date newDate=null;
try {
newDate= dateFormat.get().parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
return newDate;
}
@Test
public void testSimpleDateFormat(){
ThreadPoolExecutor executor=new ThreadPoolExecutor(5, 10, 10L, TimeUnit.SECONDS, new BlockingArrayQueue<>(10),new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
while (true){
executor.execute(new Runnable() {
@Override
public void run() {
try {
//SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-mm-dd hh:MM:ss");
String dateString = DataFormatUtils.format(new Date());
System.out.println("dateString = " + dateString+"=="+Thread.currentThread().getName());
Date date=DataFormatUtils.parse(dateString);
System.out.println("date = " + date);
String dateString1=DataFormatUtils.format(date);
System.out.println("thread running:" + Thread.currentThread().getName()+" :"+dateString.equals(dateString1));
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}
class MyThreadFactory implements ThreadFactory{
private AtomicInteger counter=new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread thread= new Thread(r);
int c = counter.getAndIncrement();
thread.setName("MyThreadPool:"+c);
return thread;
}
}

执行结果:

pr 11 17:22:00 CST 2021
thread running:MyThreadPool:9 :true
date = Sun Apr 11 17:22:00 CST 2021
thread running:MyThreadPool:5 :true
dateString = 21-4-11 下午5:22==MyThreadPool:2
dateString = 21-4-11 下午5:22==MyThreadPool:1
dateString = 21-4-11 下午5:22==MyThreadPool:6
date = Sun Apr 11 17:22:00 CST 2021
thread running:main :true
dateString = 21-4-11 下午5:22==main --- main线程出现的原因是选择的线程池拒绝策略为当队列中的任务线程池处理不过来时使用提交任务的线程去处理该任务
date = Sun Apr 11 17:22:00 CST 2021
thread running:MyThreadPool:7 :true
dateString = 21-4-11 下午5:22==MyThreadPool:3

使用jdk1.8的新api

public class DataFormatUtils {

    private static DateTimeFormatter dateFormat=DateTimeFormatter.ofPattern("yyyy-mm-dd HH:MM:ss");
public static String format(LocalDateTime date){
return dateFormat.format(date);
}
public static LocalDateTime parse(String date){
LocalDateTime newDate=LocalDateTime.parse(date,dateFormat);
return newDate;
}
@Test
public void testSimpleDateFormat(){
ThreadPoolExecutor executor=new ThreadPoolExecutor(5, 10, 10L, TimeUnit.SECONDS, new BlockingArrayQueue<>(10),new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
while (true){
executor.execute(new Runnable() {
@Override
public void run() {
try {
String dateString = DataFormatUtils.format(LocalDateTime.now());
System.out.println("dateString = " + dateString+"=="+Thread.currentThread().getName());
LocalDateTime date=DataFormatUtils.parse(dateString);
System.out.println("date = " + date);
String dateString1=DataFormatUtils.format(date);
System.out.println("thread running:" + Thread.currentThread().getName()+" :"+dateString.equals(dateString1));
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}

输出结果正常:

dateString = 2021-14-11 20:04:35==MyThreadPool:2
date = 2021-04-11T20:14:35
thread running:MyThreadPool:2 :true
dateString = 2021-14-11 20:04:35==MyThreadPool:2
date = 2021-04-11T20:14:35
thread running:MyThreadPool:2 :true
date = 2021-04-11T20:14:35
thread running:MyThreadPool:9 :true
thread running:MyThreadPool:4 :true
dateString = 2021-14-11 20:04:35==MyThreadPool:6
date = 2021-04-11T20:14:35
thread running:MyThreadPool:6 :true
dateString = 2021-14-11 20:04:35==MyThreadPool:3
date = 2021-04-11T20:14:35
thread running:MyThreadPool:3 :true

二、JDK 1.8 新的日期时间类型

Java8引入的新的一系列API,对时间日期的处理提供了更好的支持,清楚的定义了时间日期的一些概念,比如说,瞬时时间(Instant),持续时间(duration),日期(date),时间(time),时区(time-zone)以及时间段(Period)。

  1. LocalDate:不包含时间的日期,比如2019-10-14。可以用来存储生日,周年纪念日,入职日期等。
  2. LocalTime:与LocalDate想对照,它是不包含日期的时间。
  3. LocalDateTime:包含了日期及时间,没有偏移信息(时区)。
  4. ZonedDateTime:包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。
  5. Instant:时间戳,与System.currentTimeMillis()类似。
  6. Duration:表示一个时间段。
  7. Period:用来表示以年月日来衡量一个时间段。
  8. DateTimeFormatter:新的日期解析格式化类。

2.1 LocalDate

LocalDate类内只包含日期,不包含具体时间。只需要表示日期而不包含时间,就可以使用它。

@Test
public void dateTest(){
LocalDate maxDate= LocalDate.MAX;
LocalDate minDate= LocalDate.MIN;
System.out.println("maxDate = " + maxDate);
System.out.println("minDate = " + minDate);
/**
* 输出:
* maxDate = +999999999-12-31
* minDate = -999999999-01-01
* */ LocalDate dateOf= LocalDate.of(2019,12,31);
System.out.println("dateOf = " + dateOf);
/**
* 输出:dateOf = 2019-12-31
* */ // LocalDate dateOfErr=LocalDate.of(20100, Month.JULY,33);
// System.out.println("dateOfErr = " + dateOfErr);
/**输出
* java.time.DateTimeException: Invalid value for DayOfMonth (valid values 1 - 28/31): 33
* */
LocalDate now= LocalDate.now();
System.out.println(now);
/**输出:2021-04-11*/
LocalDate zonshanghai= LocalDate.now(Clock.system(ZoneId.of(ZoneId.SHORT_IDS.get("CTT"))));
LocalDate zon= LocalDate.now(Clock.system(ZoneId.of(ZoneId.SHORT_IDS.get("SST"))));
System.out.println("zonshanghai = " + zonshanghai);
System.out.println("zon = " + zon);
/**输出:zonshanghai = 2021-04-11*/
/**输出:zon = 2021-04-11*/ int thisYear = zonshanghai.getYear();
int thisYearAnother = zonshanghai.get(ChronoField.YEAR);
System.out.println("今年是" + thisYear + "年");
System.out.println("今年是" + thisYearAnother + "年");
/**输出:
* 今年是2021年
* 今年是2021年
* */ }

2.2 LocalTime

LocalTime只会获取时间,不获取日期。LocalTimeLocalDate类似,区别在于LocalDate不包含具体时间,而LocalTime不包含具体日期。

 @Test
public void localTime(){
LocalTime localTime= LocalTime.now();
System.out.println("localTime = " + localTime);
/**输出:localTime = 19:00:26.023*/
//获取小时的两种方式
int hour = localTime.getHour();
int thisHour = localTime.get(ChronoField.HOUR_OF_DAY);
System.out.println("当前时:" + hour);
System.out.println("当前时:" + thisHour);
/**输出:当前时:19
当前时:19*/
LocalTime anotherTime = LocalTime.of(20, 8, 8);
System.out.println("构造指定时间:" + anotherTime);
/**输出:构造指定时间:20:08:08*/
}

2.3 LocalDateTime

LocalDateTime表示日期和时间组合。可以通过of()方法直接创建,也可以调用LocalDateatTime()方法或LocalTimeatDate()方法将LocalDateLocalTime合并成一个LocalDateTime

 @Test
public void LocalDateTime(){
// 当前日期和时间
LocalDateTime today = LocalDateTime.now();
System.out.println("现在是:" + today);
/**现在是:2021-04-11T19:06:25.950*/ // 创建指定日期和时间
LocalDateTime anotherDay = LocalDateTime.of(2008, Month.AUGUST, 8, 8, 8, 8);
System.out.println("创建的指定时间是:" + anotherDay);
/**创建的指定时间是:2008-08-08T08:08:08*/
// 拼接日期和时间
// 使用当前日期,指定时间生成的 LocalDateTime
LocalDateTime thisTime = LocalTime.now().atDate(LocalDate.of(2008, 8, 8));
System.out.println("拼接的日期是:" + thisTime);
/**拼接的日期是:2008-08-08T19:06:25.951*/
// 使用当前日期,指定时间生成的 LocalDateTime
LocalDateTime thisDay = LocalDate.now().atTime(LocalTime.of(12, 24, 12));
System.out.println("拼接的日期是:" + thisDay);
/**拼接的日期是:2021-04-11T12:24:12*/
// 指定日期和时间生成 LocalDateTime
LocalDateTime thisDayAndTime = LocalDateTime.of(LocalDate.of(2008, 8, 8), LocalTime.of(12, 24, 12));
System.out.println("拼接的日期是:" + thisDayAndTime);
/**拼接的日期是:2008-08-08T12:24:12*/ // 获取LocalDate
LocalDate todayDate = today.toLocalDate();
System.out.println("今天日期是:" + todayDate);
/**今天日期是:2021-04-11*/
// 获取LocalTime
LocalTime todayTime = today.toLocalTime();
System.out.println("现在时间是:" + todayTime);
/**现在时间是:19:06:25.950*/
         long lo=today.getSecond();
System.out.println(lo);
/**当前时间处于的秒*/
    }

2.4 Instant

Instant用于一个获取时间戳,与System.currentTimeMillis()类似,但Instant可以精确到纳秒。

@Test
public void instant(){
// 创建Instant对象
Instant instant = Instant.now();
// 通过ofEpochSecond方法创建(第一个参数表示秒,第二个参数表示纳秒)
Instant another = Instant.ofEpochSecond(365 * 24 * 60, 100); // 获取到秒数
long currentSecond = instant.getEpochSecond();
System.out.println("获取到秒数:" + currentSecond);
/**输出:获取到秒数:1618139520 为1970-01-01T00:00:00到现在的时间*/
// 获取到毫秒数
long currentMilli = instant.toEpochMilli();
System.out.println("获取到毫秒数:" + currentMilli);
/**输出:获取到毫秒数:1618139520871*/
}

2.5 Duration

Duration的内部实现与Instant类似,但Duration表示时间段,通过between方法创建,还可以通过of()方法创建。

@Test
public void duridTest(){
LocalDateTime from = LocalDateTime.now();
LocalDateTime to = LocalDateTime.now().plusSeconds(10);
// 通过between()方法创建
Duration duration = Duration.between(from, to);
System.out.println("duration = " + duration.getSeconds());
/**输出10,可以使用该方法判断时间差*/
// 通过of()方法创建,该方法参数为时间段长度和时间单位。
// 7天
Duration duration1 = Duration.of(7, ChronoUnit.DAYS);
// 60秒
Duration duration2 = Duration.of(60, ChronoUnit.SECONDS);
}

可以用来判断时间差

2.6 Period

PeriodDuration类似,获取一个时间段,只不过单位为年月日,也可以通过of方法和between方法创建,between方法接收的参数为LocalDate

private static void period() {
// 通过of方法
Period period = Period.of(2012, 12, 24);
// 通过between方法
Period period1 = Period.between(LocalDate.now(), LocalDate.of(2020,12,31));
}

三、时间操作

3.1 时间比较

isBefore()isAfter()判断给定的时间或日期是在另一个时间/日期之前还是之后。
LocalDate为例,LocalDateTime/LocalTime同理

public static void compare() {
LocalDate thisDay = LocalDate.of(2008, 8, 8);
LocalDate otherDay = LocalDate.of(2018, 8, 8); // 晚于
boolean isAfter = thisDay.isAfter(otherDay);
System.out.println(isAfter); // 早于
boolean isBefore = thisDay.isBefore(otherDay);
System.out.println(isBefore);
}

3.2 增加/减少年数、月数、天数

public static void plusAndMinus() {
// 增加
LocalDateTime today = LocalDateTime.now();
LocalDateTime nextYearDay = today.plusYears(1);
System.out.println("下一年的今天是:" + nextYearDay);
LocalDateTime nextMonthDay = today.plus(1, ChronoUnit.MONTHS);
System.out.println("下一个月的今天是:" + nextMonthDay); //减少
LocalDateTime lastMonthDay = today.minusMonths(1);
LocalDateTime lastYearDay = today.minus(1, ChronoUnit.YEARS);
System.out.println("一个月前是:" + lastMonthDay);
System.out.println("一年前是:" + lastYearDay);
}

3.3 时间修改

public static void edit() {
LocalDateTime today = LocalDateTime.now();
// 修改年为2012年
LocalDateTime thisYearDay = today.withYear(2012);
System.out.println("修改年后的时间为:" + thisYearDay);
// 修改为12月
LocalDateTime thisMonthDay = today.with(ChronoField.MONTH_OF_YEAR, 12);
System.out.println("修改月后的时间为:" + thisMonthDay);
}

3.4 时间计算

 @Test
public void compute() {
// TemporalAdjusters 的静态方法
LocalDate today = LocalDate.now();
// 获取今年的第一天
LocalDate date = today.with(firstInMonth(DayOfWeek.MONDAY));
System.out.println("这一月的星期一:" + date); // Duration 计算
LocalDateTime from = LocalDateTime.now();
LocalDateTime to = LocalDateTime.now().plusMonths(1);
Duration duration = Duration.between(from, to); // 区间统计换算
// 总天数
long days = duration.toDays();
System.out.println("相隔" + days + "天");
// 小时数
long hours = duration.toHours();
System.out.println("相隔" + hours + "小时");
// 分钟数
long minutes = duration.toMinutes();
System.out.println("相隔" + minutes + "分钟");
}
  • TemporalAdjusters的更多方法

java8中的日期和时间API

四、时间日期格式化

DateTimeFormatter默认提供了多种格式化方式,如果默认提供的不能满足要求,可以通过DateTimeFormatterofPattern方法创建自定义格式化方式。

 @Test
public void format() {
LocalDateTime today = LocalDateTime.now();
// 两种默认格式化时间方式
String todayStr1 = today.format(DateTimeFormatter.BASIC_ISO_DATE);
String todayStr2 = today.format(DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println("格式化时间:" + todayStr1);
/**输出:格式化时间:20210411*/
System.out.println("格式化时间:" + todayStr2);
/**输出:格式化时间:2021-04-11*/ String todayStr4 = today.format(DateTimeFormatter.ISO_LOCAL_TIME);
System.out.println(todayStr4);
/**输出:19:49:06.619*/ //自定义格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy hh:MM:ss");
String todayStr3 = today.format(dateTimeFormatter);
System.out.println("自定义格式化时间:" + todayStr3);
/**输出:自定义格式化时间:11/04/2021 07:04:47*/
}

4.2 解析时间

4.1 中以何种方式格式化,这里需以同样方式解析。

@Test
public void parse() {
LocalDate date1 = LocalDate.parse("20080808", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2008-08-08", DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
LocalDateTime dateTime= LocalDateTime.parse("2020-08-03 22:11:23",dateTimeFormatter);
System.out.println(date1);
System.out.println(date2);
System.out.println(dateTime);
/**输出结果:
* 2008-08-08
* 2008-08-08
* 2020-08-03T22:11:23 这里的T是toString()方法中的,其本身是没有的
* */
}

五、总结

相较于Date 的优势

  1. Instant 的精确度更高,可以精确到纳秒级;
  2. Duration 可以便捷得到时间段内的天数、小时数等;
  3. LocalDateTime 能够快速地获取年、月、日、下一月等;
  4. TemporalAdjusters 类中包含许多常用的静态方法,避免自己编写工具类;
  5. Date的格式化方式SimpleDateFormat相比,DateTimeFormatter是线程安全的。

本文大部分转自:https://www.cnblogs.com/vandusty/p/12269050.html

相关文章