您的位置:首页 > 编程语言 > Java开发

Java 8 新特性:Java 类库的新特性之日期时间API (Date/Time API )

2017-11-01 23:41 639 查看

日期时间API (Date/Time API )


文 | 莫若吻    

 

1.Java8之前java.util.Date和Calendar类的弊端

1)最开始的时候,Date既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂(不遵守单一职责)。

后来从JDK 1.1 开始,这三项职责分开了:

  • 使用Calendar类实现日期和时间字段之间转换;
  • 使用DateFormat类来格式化和分析日期字符串;
  • Date只用来承载日期和时间信息。
现在原有Date中的相应方法已废弃。无论是Date,还是Calendar,都使用着太不方便,这是API没有设计好的地方。

2)令人无语的year和month(month是从0开始的)

eg:

[java] view plain copy
  1. Date date = new Date(2016,1,1);     
  2. System.out.println(date);   
输出结果:Tue Feb 01 00:00:00 CST 3916
这样得到的结果year为2012+1900,而month明明给定的参数是1,却输出的是二月。

设置日期可以用java.util.Calendar

[java] view plain copy
  1. Calendar calendar = Calendar.getInstance();     
  2. calendar.set(2016, 5, 2);  
虽然Calendar年份的传值不需要减去1900,但Calendar的month也是从0开始的,表达5月份应该用4这个数字。

3)java.util.Date与java.util.Calendar中的所有属性都是可变的,且线程不安全。

eg:

[java] view plain copy
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.         Calendar birth = Calendar.getInstance();  
  5.         birth.set(1975, Calendar.MAY, 26);  
  6.         Calendar now = Calendar.getInstance();  
  7.         System.out.println(daysBetween(birth, now)); // 输出结果为14963,值不固定  
  8.         System.out.println(daysBetween(birth, now)); // 输出结果 显示 0?  
  9.   
  10.     }  
  11. }  
  12.     public static long daysBetween(Calendar begin, Calendar end) {     
  13.          long daysBetween = 0;     
  14.          while(begin.before(end)) {     
  15.              begin.add(Calendar.DAY_OF_MONTH, 1);     
  16.              daysBetween++;     
  17.         }     
  18.         return daysBetween;     
  19.     }   
  20.   
  21. }  
Note:daysBetween有点问题,如果连续计算两个Date实例的话,第二次会取得0,因为Calendar状态是可变的,考虑到重复计算的场合,最好复制一个新的Calendar。修改代码如下
[java] view plain copy
  1. public static long daysBetween(Calendar begin, Calendar end) {     
  2.     Calendar calendar = (Calendar) begin.clone(); // 复制     
  3.         long daysBetween = 0;     
  4.         while(calendar.before(end)) {     
  5.             calendar.add(Calendar.DAY_OF_MONTH, 1);     
  6.             daysBetween++;     
  7.         }     
  8.         return daysBetween;        
  9. }   


2.简述 新的日期时间API

Java 的日期与时间 API 问题由来已久,Java 8 之前的版本中关于时间、日期及其他时间日期格式化类由于线程安全、重量级、序列化成本高等问题而饱受批评。Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。新的 java.time 中包含了所有关于时钟(Clock),本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了了日期时间和本地化的管理。

目前Java8新增了java.time包定义的类表示日期-时间概念的规则,很方便使用;最重要的一点是值不可变,且线程安全。


下图是java.time包下的一些主要的类的日期时间 值的格式,方便理解使用:


Note:不过尽管有了新的API,但仍有一个严重的问题——大量的旧代码和库仍然在使用老的API。现在,Java 8解决了这个问题,它给Date类增加了一个新的方法toInstant(),可以将Date转化成新的实例。这样就可以切换到新的API。


对于新API:

非常有用的值类型:
Instant ----- 与java.util.Date相似
ZonedDateTime ----- ZoneId -时区很重要的时候使用
OffsetDateTime ----- OffsetTime, ZoneOffset -对UTC的偏移处理
Duration, Period ----- 但如果你想找到两个日期之间的时间量,你可能会寻找ChronoUnit代替(详情见下文)
其他有用的类型:
DateTimeFormatter ----- 将日期类型转换成字符串类型
ChronoUnit ----- 计算出两点之间的时间量,例如ChronoUnit.DAYS.between(t1, t2)
TemporalAdjuster ----- 例如date.with(TemporalAdjuster.firstDayOfMonth())

Note:大多数情况下,新的值类型由JDBC提供支持。有一小部分异常,eg:ZonedDateTime在SQL中没有对应的(类型)。


3.Java 新旧日期API的区别



4.java.time包下的类

4.1 Clock类

Clock类提供了访问当前日期和时间的方法。Clock使用时区来访问当前的instant, date和time。Clock类可以替换 System.currentTimeMillis() 和 TimeZone.getDefault()。

eg:

[java] view plain copy
  1. //Clock 时钟  
  2.             Clock clock1 = Clock.systemDefaultZone();//获取系统默认时区 (当前瞬时时间 )  
  3.             System.out.println( "系统时间日期:"+clock1.instant() );  
  4.             System.out.println( "时间毫秒:"+clock1.millis() );  
  5.                       
  6.             final Clock clock = Clock.systemUTC();//获取系统时钟,并将其转换成使用UTC时区的日期和时间  
  7.             System.out.println( "时间日期:"+clock.instant() );  
  8.             System.out.println( "时间毫秒值:"+clock.millis() );  

输出结果:
系统时间日期:2016-05-12T07:42:37.883Z
时间毫秒:1463038957894

时间日期:2016-05-12T07:42:37.894Z

时间毫秒值:1463038957894


某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。

eg:

[java] view plain copy
  1. Instant instant = clock1.instant();  
  2.             Date javadate = Date.from(instant);   
  3.             System.out.println( "date:"+javadate);  

输出结果:

date:Thu May 12 15:47:00 CST 2016


4.2 ZoneId(时区)

在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of()来获取到。时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。

eg:

[java] view plain copy
  1. // 输出所有可见的时区ID,eg:Asia/Aden, America/Cuiaba, Etc/GMT+9等  
  2. System.out.println(ZoneId.getAvailableZoneIds());  
  3.   
  4. ZoneId zone1 = ZoneId.of("Europe/Berlin");  
  5. ZoneId zone2 = ZoneId.of("Brazil/East");  
  6. System.out.println(zone1.getRules());  
  7. System.out.println(zone2.getRules());  
  8. //输出结果: ZoneRules[currentStandardOffset=+01:00]  
  9. //输出结果: ZoneRules[currentStandardOffset=-03:00]  


4.3 LocalTime(本地时间)

LocalTime 定义了一个没有时区信息的时间。

eg:

1)获取现在的本地时间

[java] view plain copy
  1. // Get the local date and local time  
  2.             final LocalTime time = LocalTime.now();  
  3.             final LocalTime timeFromClock = LocalTime.now( clock );  
  4.             System.out.println( time );  
  5.             System.out.println( timeFromClock );  
输出结果:

16:03:23.212
08:03:23.212

2)按时区显示时间

[java] view plain copy
  1. ZoneId zone1 = ZoneId.of("Europe/Berlin");  
  2.         ZoneId zone2 = ZoneId.of("Brazil/East");  
  3.           
  4.         LocalTime now1 = LocalTime.now(zone1);  
  5.         LocalTime now2 = LocalTime.now(zone2);  
  6.         System.out.println("时区:Europe/Berlin---"+now1);   
  7.         System.out.println("时区:Brazil/East---"+now2);   

输出结果:

时区:Europe/Berlin---10:03:23.217
时区:Brazil/East---05:03:23.217

LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。

eg:

[java] view plain copy
  1. LocalTime late = LocalTime.of(22, 12, 18);//时分秒  
  2.         System.out.println(late); // 输出结果:22:12:18  
  3.   
  4.         DateTimeFormatter germanFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)  
  5.                 .withLocale(Locale.GERMAN);  
  6.   
  7.         LocalTime leetTime = LocalTime.parse("15:39", germanFormatter);  
  8.         System.out.println(leetTime); // 输出结果: 15:39  

4.4 LocalDate(本地日期)

LocalDate 表示了一个确切的日期(eg: 2014-03-11)。该对象值是不可变的,使用方式和LocalTime基本一致。

eg:

[java] view plain copy
  1. Clock clock = Clock.systemDefaultZone();// 获取系统默认时区 (当前瞬时时间 )  
  2.   
  3.         // Get the local date and local time  
  4.         final LocalDate date = LocalDate.now();  
  5.         final LocalDate dateFromClock = LocalDate.now(clock);  
  6.         System.out.println(date);  
  7.         System.out.println(dateFromClock);  
输出结果:

2016-05-12
2016-05-12


从字符串解析一个LocalDate类型和解析LocalTime一样简单.

eg:

[java] view plain copy
  1. DateTimeFormatter germanFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)  
  2.         .withLocale(Locale.GERMAN);  
  3.   
  4. LocalDate xmas = LocalDate.parse("25.10.2016", germanFormatter);  
  5. System.out.println(xmas);  
输出结果:

2016-10-25


4.5 LocalDateTime(本地日期时间)

表示了具体时间和日期。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。

eg:

1)

[java] view plain copy
  1. Clock clock = Clock.systemDefaultZone();// 获取系统默认时区 (当前瞬时时间 )  
  2.   
  3.         // Get the local date/time  
  4.         final LocalDateTime datetime = LocalDateTime.now();  
  5.         final LocalDateTime datetimeFromClock = LocalDateTime.now(clock);  
  6.         System.out.println(datetime);  
  7.         System.out.println(datetimeFromClock);  
输出结果:

2016-05-12T16:33:17.546
2016-05-12T16:33:17.546

2)

[java] view plain copy
  1. LocalDateTime sylvester = LocalDateTime.of(2016, Month.DECEMBER, 31, 23, 59, 59);  
  2.        
  3.     DayOfWeek dayOfWeek = sylvester.getDayOfWeek();  
  4.     System.out.println(dayOfWeek);        
  5.        
  6.     Month month = sylvester.getMonth();  
  7.     System.out.println(month);          
  8.        
  9.     long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);  
  10.     System.out.println(minuteOfDay);    

输出结果:

SATURDAY
DECEMBER
1439


只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
eg:

[java] view plain copy
  1. LocalDateTime sylvester = LocalDateTime.of(2016, Month.DECEMBER, 31, 23, 59, 59);  
  2.         Instant instant = sylvester  
  3.                 .atZone(ZoneId.systemDefault())  
  4.                 .toInstant();  
  5.            
  6.         Date legacyDate = Date.from(instant);  
  7.         System.out.println(legacyDate);  

输出结果:

Sat Dec 31 23:59:59 CST 2016


格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我也可以自定义格式。

eg:

[java] view plain copy
  1. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM dd, yyyy - HH:mm");  
  2.     LocalDateTime parsed = LocalDateTime.parse("05 03, 2016 - 07:13", formatter);  
  3.     String string = formatter.format(parsed);  
  4.     System.out.println(string);  
输出结果:
05 03, 2016 - 07:13


Note:和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。


4.6 ZonedDateTime(日期时间和时区信息)

使用ZonedDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。

eg:

[java] view plain copy
  1. Clock clock = Clock.systemDefaultZone();// 获取系统默认时区 (当前瞬时时间 )  
  2. // Get the zoned date/time  
  3. final ZonedDateTime zonedDatetime = ZonedDateTime.now();  
  4. final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now(clock);  
  5. final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));  
  6. System.out.println(zonedDatetime);  
  7. System.out.println(zonedDatetimeFromClock);  
  8. System.out.println(zonedDatetimeFromZone);  

输出结果:

2016-05-12T16:59:55.779+08:00[Asia/Shanghai]
2016-05-12T16:59:55.779+08:00[Asia/Shanghai]
2016-05-12T01:59:55.781-07:00[America/Los_Angeles]

4.7 Duration类

Duration持有的时间精确到纳秒。很容易计算两个日期中间的差异。

eg:求时间差

[java] view plain copy
  1.               // Get duration between two dates  
  2. final LocalDateTime from = LocalDateTime.of(2014, Month.APRIL, 16, 0, 0, 0);//年月日时分秒  
  3. final LocalDateTime to = LocalDateTime.of(2015, Month.APRIL, 16, 23, 59, 59);  
  4. final Duration duration = Duration.between(from, to);  
  5. System.out.println("Duration in days: " + duration.toDays());  
  6. System.out.println("Duration in hours: " + duration.toHours());</span>  

输出结果:
Duration in days: 365
Duration in hours: 8783

还有一种获取时间差值的方式:ChronoUnit

[java] view plain copy
  1. ZoneId zone1 = ZoneId.of("America/Cuiaba");  
  2.          ZoneId zone2 = ZoneId.of("Brazil/East");  
  3.          LocalTime now1 = LocalTime.now(zone1);  
  4.          LocalTime now2 = LocalTime.now(zone2);  
  5.          long hoursBetween = ChronoUnit.HOURS.between(now1, now2);  
  6.          long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);  
  7.          System.out.println(hoursBetween); // 1  
  8.          System.out.println(minutesBetween); // 60  


5.新日期时间API示例

eg:(注:感兴趣自己运行结果试试各种方法,很easy。)

[java] view plain copy
  1. import java.time.Clock;  
  2. import java.time.Duration;  
  3. import java.time.Instant;  
  4. import java.time.LocalDateTime;  
  5. import java.time.ZoneId;  
  6. import java.time.ZonedDateTime;  
  7. import java.time.chrono.ChronoLocalDateTime;  
  8. import java.time.chrono.Chronology;  
  9. import java.time.chrono.HijrahChronology;  
  10. import java.time.format.DateTimeFormatter;  
  11. import java.time.temporal.IsoFields;  
  12. import java.util.Date;  
  13.   
  14. public class TimeTest {  
  15.       
  16.     public static void main(String[] args) throws InterruptedException {  
  17.         testClock();  
  18. //      testInstant();  
  19. //      testLocalDateTime();  
  20. //      testZonedDateTime();  
  21. //      testDuration();  
  22. //      testChronology();  
  23. //      testNewOldDateConversion();  
  24.     }  
  25.     public static void testClock() throws InterruptedException {  
  26.         // 时钟提供给我们用于访问某个特定 时区的 瞬时时间、日期 和 时间的。     
  27.         Clock c1 = Clock.systemUTC(); // 系统默认UTC时钟(当前瞬时时间   
  28.         System.out.println(c1.millis()); // 每次调用将返回当前瞬时时间(UTC)  
  29.           
  30.         //相当于System.currentTimeMillis())  
  31.         Clock c2 = Clock.systemDefaultZone(); // 系统默认时区时钟(当前瞬时时间)  
  32.           
  33.         Clock c31 = Clock.system(ZoneId.of("Europe/Paris")); // 巴黎时区  
  34.         System.out.println(c31.instant()); // 每次调用将返回当前瞬时时间(UTC)  
  35.         Clock c32 = Clock.system(ZoneId.of("Asia/Shanghai"));// 上海时区  
  36.         System.out.println(c32.instant());// 每次调用将返回当前瞬时时间(UTC)  
  37.           
  38.         Clock c4 = Clock.fixed(Instant.now(), ZoneId.of("Asia/Shanghai"));// 固定上海时区时钟  
  39.         System.out.println(c4.millis());  
  40.         Thread.sleep(1000);  
  41.         System.out.println(c4.millis()); // 不变 即时钟时钟在那一个点不动  
  42.           
  43.         Clock c5 = Clock.offset(c1, Duration.ofSeconds(2)); // 相对于系统默认时钟两秒的时钟  
  44.         System.out.println(c1.millis());  
  45.         System.out.println(c5.millis());  
  46.     }  
  47.   
  48.     public static void testInstant() {  
  49.         // 瞬时时间 相当于以前的System.currentTimeMillis()  
  50.         Instant instant1 = Instant.now();  
  51.         System.out.println(instant1.getEpochSecond());// 精确到秒 得到相对于1970-01-01  
  52.                                                         // 00:00:00 UTC的一个时间  
  53.         System.out.println(instant1.toEpochMilli()); // 精确到毫秒  
  54.         Clock clock1 = Clock.systemUTC(); // 获取系统UTC默认时钟  
  55.         Instant instant2 = Instant.now(clock1);// 得到时钟的瞬时时间  
  56.         System.out.println(instant2.toEpochMilli());  
  57.         Clock clock2 = Clock.fixed(instant1, ZoneId.systemDefault()); // 固定瞬时时间时钟  
  58.         Instant instant3 = Instant.now(clock2);// 得到时钟的瞬时时间  
  59.         System.out.println(instant3.toEpochMilli());// equals instant1  
  60.     }  
  61.   
  62.     public static void testLocalDateTime() {  
  63.         // 使用默认时区时钟瞬时时间创建 Clock.systemDefaultZone() -->即相对于  
  64.         // ZoneId.systemDefault()默认时区  
  65.         LocalDateTime now = LocalDateTime.now();  
  66.         System.out.println(now);  
  67.         // 自定义时区  
  68.         LocalDateTime now2 = LocalDateTime.now(ZoneId.of("Europe/Paris"));  
  69.         System.out.println(now2);// 会以相应的时区显示日期  
  70.         // 自定义时钟  
  71.         Clock clock = Clock.system(ZoneId.of("Asia/Dhaka"));  
  72.         LocalDateTime now3 = LocalDateTime.now(clock);  
  73.         System.out.println(now3);// 会以相应的时区显示日期  
  74.         // 不需要写什么相对时间 如java.util.Date 年是相对于1900 月是从0开始  
  75.         // 2013-12-31 23:59  
  76.         LocalDateTime d1 = LocalDateTime.of(2013, 12, 31, 23, 59);  
  77.         // 年月日 时分秒 纳秒  
  78.         LocalDateTime d2 = LocalDateTime.of(2013, 12, 31, 23, 59, 59, 11);  
  79.         // 使用瞬时时间 + 时区  
  80.         Instant instant = Instant.now();  
  81.         LocalDateTime d3 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());  
  82.         System.out.println(d3);  
  83.         // 解析String--->LocalDateTime  
  84.         LocalDateTime d4 = LocalDateTime.parse("2013-12-31T23:59");  
  85.         System.out.println(d4);  
  86.         LocalDateTime d5 = LocalDateTime.parse("2013-12-31T23:59:59.999");// 999毫秒  
  87.                                                                             // 等价于999000000纳秒  
  88.         System.out.println(d5);  
  89.         // 使用DateTimeFormatter API 解析 和 格式化  
  90.         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");  
  91.         LocalDateTime d6 = LocalDateTime.parse("2013/12/31 23:59:59", formatter);  
  92.         System.out.println(formatter.format(d6));  
  93.         // 时间获取  
  94.         System.out.println(d6.getYear());  
  95.         System.out.println(d6.getMonth());  
  96.         System.out.println(d6.getDayOfYear());  
  97.         System.out.println(d6.getDayOfMonth());  
  98.         System.out.println(d6.getDayOfWeek());  
  99.         System.out.println(d6.getHour());  
  100.         System.out.println(d6.getMinute());  
  101.         System.out.println(d6.getSecond());  
  102.         System.out.println(d6.getNano());  
  103.         // 时间增减  
  104.         LocalDateTime d7 = d6.minusDays(1);  
  105.         LocalDateTime d8 = d7.plus(1, IsoFields.QUARTER_YEARS);  
  106.         // LocalDate 即年月日 无时分秒  
  107.         // LocalTime即时分秒 无年月日  
  108.         // API和LocalDateTime类似就不演示了  
  109.     }  
  110.   
  111.     public static void testZonedDateTime() {  
  112.         // 即带有时区的date-time 存储纳秒、时区和时差(避免与本地date-time歧义)。  
  113.         // API和LocalDateTime类似,只是多了时差(如2013-12-20T10:35:50.711+08:00[Asia/Shanghai])  
  114.         ZonedDateTime now = ZonedDateTime.now();  
  115.         System.out.println(now);  
  116.         ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("Europe/Paris"));  
  117.         System.out.println(now2);  
  118.         // 其他的用法也是类似的 就不介绍了  
  119.         ZonedDateTime z1 = ZonedDateTime.parse("2013-12-31T23:59:59Z[Europe/Paris]");  
  120.         System.out.println(z1);  
  121.     }  
  122.   
  123.     public static void testDuration() {  
  124.         // 表示两个瞬时时间的时间段  
  125.         Duration d1 = Duration.between(Instant.ofEpochMilli(System.currentTimeMillis() - 12323123), Instant.now());  
  126.         // 得到相应的时差  
  127.         System.out.println(d1.toDays());  
  128.         System.out.println(d1.toHours());  
  129.         System.out.println(d1.toMinutes());  
  130.         System.out.println(d1.toMillis());  
  131.         System.out.println(d1.toNanos());  
  132.         // 1天时差 类似的还有如ofHours()  
  133.         Duration d2 = Duration.ofDays(1);  
  134.         System.out.println(d2.toDays());  
  135.     }  
  136.   
  137.     public static void testChronology() {  
  138.         // 提供对java.util.Calendar的替换,提供对年历系统的支持  
  139.         Chronology c = HijrahChronology.INSTANCE;  
  140.         ChronoLocalDateTime d = c.localDateTime(LocalDateTime.now());  
  141.         System.out.println(d);  
  142.     }  
  143.   
  144.     /** 
  145.      * 新旧日期转换 
  146.      */  
  147.     public static void testNewOldDateConversion() {  
  148.         Instant instant = new Date().toInstant();  
  149.         Date date = Date.from(instant);  
  150.         System.out.println(instant);  
  151.         System.out.println(date);  
  152.     }  
  153.   
  154.       
  155. }  




版权声明:此文为本博主原创文章,转载请您必须注明出处,谢谢!原创博主:http://blog.csdn.net/sun_promise 阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: