新日期API
基于尚硅谷java8教程
1. 原有日期api的缺点
- 从jdk1.1开始创建,日期处理没有规范,处于多个包中比如:java.util.Date,java.text.java.text.DateFormat等
- 现有的日期api存在多线程的线程安全问题(当然可以通过比如ThreadLocal等方式规避)
/*存在线程安全问题的旧版本日期api*/
/**
* 存在多线程安全问题
* 异常信息如下:
* java.util.concurrent.ExecutionException: java.lang.NumberFormatException: multiple points
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.seven.jdk8.SimpleDateFormatTest.test(SimpleDateFormatTest.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.seven.jdk8.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:19)
at com.seven.jdk8.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:16)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
*/
@Test
public void test(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
return sdf.parse("20161121");
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(pool.submit(task));
}
try {
for(Future<Date> ft:results){
System.out.println(ft.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
pool.shutdown();
}
/*对于现有的日期api如何规避线程安全问题*/
/*使用ThreadLocal解决线程安全问题*/
@Test
public void test2(){
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
return DateFormatThreadLocal.parse("20151125","yyyyMMdd");
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(pool.submit(task));
}
try {
for(Future<Date> ft:results){
System.out.println(ft.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
pool.shutdown();
}
// 解决这个问题依赖的工具类
package com.seven.jdk8;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
*使用ThreadLocal的方式解决多线程问题
* .
*/
public class DateFormatThreadLocal {
/**
* 锁对象
*/
private static final Object lockObj = new Object();
/**
* 存放不同的日期模板格式的sdf的Map
*/
private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>();
/**
* 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
*
* @param pattern
* @return
*/
private static SimpleDateFormat getSdf(final String pattern) {
ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern);
// 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf
if (tl == null) {
synchronized (lockObj) {
tl = sdfMap.get(pattern);
if (tl == null) {
// 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map
System.out.println("put new sdf of pattern " + pattern + " to map");
// 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat
tl = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
System.out.println("thread: " + Thread.currentThread() + " init pattern: " + pattern);
return new SimpleDateFormat(pattern);
}
};
sdfMap.put(pattern, tl);
}
}
}
return tl.get();
}
/**
* 是用ThreadLocal<SimpleDateFormat>来获取SimpleDateFormat,这样每个线程只会有一个SimpleDateFormat
*
* @param date
* @param pattern
* @return
*/
public static String format(Date date, String pattern) {
return getSdf(pattern).format(date);
}
public static Date parse(String dateStr, String pattern) throws ParseException {
return getSdf(pattern).parse(dateStr);
}
}
2. 新日期api简介
-
优势
- 新日期api是线程安全的
- 统一放在java.time及其子包中
- 关注点分离,对于机器使用的时间戳和人可读的日期进行了类的分类
-
java.time及其子包说明
- java.time包:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。
- java.time.chrono包:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统。
- java.time.format包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法。
- java.time.temporal包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式。
- java.time.zone包:这个包包含支持不同时区以及相关规则的类。
3. 使用范例
package com.seven.jdk8;
import org.junit.Test;
import java.text.DateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalField;
import java.util.Date;
import java.util.Set;
/**
* 新日期api使用测试.
*/
public class LocalDateTimeTest {
/* localDate/localtime/localdateTime */
@Test
public void test1(){
//获取当前日期时间
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
//按照指定时间生成日期
LocalDateTime ldt1 = LocalDateTime.of(2020,12,30,1,2,3);
System.out.println(ldt1);
//指定时间+2年
System.out.println(ldt.plusYears(2));
//指定时间-3月
LocalDateTime ldt2 = ldt.minusMonths(3);
System.out.println(ldt2);
System.out.println(ldt.getYear());//获取年份
System.out.println(LocalDate.now());//获取日期
}
/* 时间戳 (使用Unix元年 1970年1月1日 00:00:00到现在的毫秒数 ) */
@Test
public void test2(){
Instant ins = Instant.now(); //默认使用UTC时区
System.out.println(ins+",,,"+ins.getEpochSecond());
OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));//中国在东八区
System.out.println(odt);
Instant ins1 = Instant.ofEpochSecond(5);
System.out.println(ins1); //1970-01-01T00:00:05Z 从Unix元年偏移5s
}
/**
* Duration : 用于计算两个“时间”间隔
* Period : 用于计算两个“日期”间隔
* */
@Test
public void test3() throws InterruptedException {
Instant ins1 = Instant.now();
Thread.sleep(1000);
Instant ins2 = Instant.now();
System.out.println("两个时间的间隔为--》"+Duration.between(ins1,ins2)); //两个时间的间隔为--》PT1S
LocalDate date1 = LocalDate.of(2011,3,5);
LocalDate date2 = LocalDate.now();
Period pe = Period.between(date1,date2);
System.out.println("两个日期的间隔为--》"+ pe+",,间隔的年为--》"+pe.getYears()); //两个日期的间隔为--》P6Y18D,,间隔的年为--》6
}
/* 时间校正器 TemporalAdjuster */
@Test
public void test4(){
LocalDateTime ldt = LocalDateTime.now();
System.out.println("今天几号--> "+ldt.getDayOfMonth()); //今天几号--> 23
System.out.println("下个星期天是几号--》"+ ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY))); //下个星期天是几号--》2017-03-26T18:36:30.477
// 自定义下一个工作日
LocalDateTime dateTime = ldt.with((temporal)->{
LocalDateTime lt = (LocalDateTime)temporal;
DayOfWeek dow = lt.getDayOfWeek();
if(DayOfWeek.FRIDAY.equals(dow)){
return lt.plusDays(3);
}else if(DayOfWeek.SATURDAY.equals(dow)){
return lt.plusDays(2);
}else{
return lt.plusDays(1);
}
});
System.out.println("下个工作日是--》"+dateTime); //下个工作日是--》2017-03-24T22:42:31.789
}
/* 格式化日期 */
@Test
public void test5(){
// 自定义格式,当然也可以使用默认指定的格式
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime ldt = LocalDateTime.now();
System.out.println(format.format(ldt)); //2017-03-23 22:49:37
//字符串转日期
LocalDateTime ldt2 = LocalDateTime.parse("2017-11-12 23:10:05",format);
System.out.println(ldt2); //2017-11-12T23:10:05
}
/* 带时区的日期 ZonedDate、ZonedTime、ZonedDateTime */
@Test
public void test6(){
LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); //Asia/shanghai time ->2017-03-23T22:57:21.084
System.out.println("Asia/shanghai time ->"+ldt1);
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/Marigot"));
System.out.println(zdt); //2017-03-23T10:59:43.708-04:00[America/Marigot]
System.out.println(zdt.toLocalDateTime());//2017-03-23T11:00:49.177 转换为当前时区时间
System.out.println("------------------------------------------------");
//获取时区ID列表
//ZoneId.getAvailableZoneIds().stream().forEach(System.out::println);
}
}
源代码地址:http://git.oschina.net/johnny/java_base