java時(shí)間日期總結(jié)

java時(shí)間日期總結(jié)

[TOC]

Java早期的時(shí)間API

Date

Date既能處理時(shí)間,又能處理日期,雖然如此,但是在很多方面天生缺陷,比如在jdk8之前對(duì)java的日期類的吐槽主要在這幾個(gè)方面:

  • Java的日期/時(shí)間類的定義不一致,在java.util和java.sql的包中都有日期類,格式化和解析的類在java.text包里面。
  • java.util.Date同時(shí)包含日期和時(shí)間,但是java.sql.Date只包含日期。
  • 所有的日期類都是可變的,因此都不是線程安全的,最大的問(wèn)題之一。
  • 日期類不提供國(guó)際化,不支持時(shí)區(qū)設(shè)置,因此引入了java.util.Calendar和java.util.TimeZone,但存在上面的問(wèn)題。

date中非常多的方法都已經(jīng)廢棄,標(biāo)記成了@Deprecated
現(xiàn)在Date的定位是在時(shí)間軸上表示唯一一個(gè)時(shí)刻,它代表一個(gè)絕對(duì)的時(shí)間,也就是說(shuō)不管在什么地方,在什么時(shí)區(qū),當(dāng)前時(shí)間的Date都是一樣的,都是從1970年1月1日0點(diǎn)0分GMT時(shí)間起,到目前這一刻的毫秒數(shù)。
目前還在用的方法有如下幾個(gè):

  • public long getTime() :返回內(nèi)部存儲(chǔ)的毫秒數(shù)
  • public void setTime(long time):重新設(shè)置內(nèi)存的毫秒數(shù)
  • public boolean before(Date when):比較給定的時(shí)刻是否早于當(dāng)前 Date 實(shí)例
  • public boolean after(Date when):比較給定的時(shí)刻是否晚于當(dāng)前 Date 實(shí)例
  • public Instant toInstant(): jdk8新增加的方法,轉(zhuǎn)成java8引入的Instant類型
  • public static Date from(Instant instant): jdk8新增加的方法,從Instant轉(zhuǎn)成Date
Date date = new java.util.Date();
System.out.println(date);   //Tue Mar 05 11:04:34 CST 2019
System.out.println(date.getTime());   //1551755808311
System.out.println(date.toGMTString()); //5 Mar 2019 03:23:05 GMT 這個(gè)也可以轉(zhuǎn)成GMT時(shí)間,不過(guò)toGMTString已經(jīng)廢了

Date date1 = new java.sql.Date(119,2,5);    //對(duì)應(yīng)的年份 1900+119,月份2+1
System.out.println(date1);  //2019-03-05 

Date date2 = new java.sql.Date(System.currentTimeMillis());
System.out.println(date2);  //2019-03-05  java.sql.Date 只包含日期

Instant instant = date.toInstant();
System.out.println(instant);    //2019-03-05T03:21:36.191Z  GMT時(shí)間,比北京時(shí)間慢8小時(shí)

Date date3 = Date.from(instant);    //Tue Mar 05 11:27:31 CST 2019

Calendar

Calendar 用來(lái)表示日歷,是對(duì)絕對(duì)時(shí)間Date的一種描述,我們看Date對(duì)象的時(shí)候肯定不會(huì)去看它的毫秒數(shù),而是看某年某月某日某時(shí)某分某秒這種形式,日歷隨著地區(qū)和時(shí)區(qū)的不同而不同,比如美國(guó)日期標(biāo)準(zhǔn)格式月/日/年,中國(guó)標(biāo)準(zhǔn)是年/月/日,再比如同一個(gè)Date,在中國(guó)東八區(qū)自然比日本東九區(qū)慢一個(gè)小時(shí)。

Calendar可以對(duì)日期進(jìn)行加減操作,國(guó)際化,設(shè)置時(shí)區(qū)等,它是一個(gè)抽象類,不能實(shí)例化,所以提供了幾個(gè)工廠方法獲取對(duì)象。

public static Calendar getInstance(){    
    return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}

public static Calendar getInstance(TimeZone zone) {    
    return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
}

public static Calendar getInstance(Locale aLocale){   
    return createCalendar(TimeZone.getDefault(), aLocale);
}

public static Calendar getInstance(TimeZone zone,Locale aLocale){    
    return createCalendar(zone, aLocale);
}

createCalendar方法需要提供兩個(gè)參數(shù),一個(gè)是時(shí)區(qū),一個(gè)是語(yǔ)言,沒(méi)有的話使用默認(rèn)的設(shè)置,因?yàn)?strong>對(duì)于不同的國(guó)家,對(duì)于時(shí)刻的年月日的格式是不一樣的

Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime());     //Tue Mar 05 13:29:41 CST 2019 calendar.getTime() 返回一個(gè)Date對(duì)象
calendar.add(Calendar.YEAR,1);
System.out.println(calendar.getTime());     //Tue Mar 05 13:29:41 CST 2020 calendar.getTime() 返回一個(gè)Date對(duì)象

Calendar calendar1 = Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.FRANCE);
System.out.println(calendar1.getTime());    //Tue Mar 05 13:29:41 CST 2019 
//可以發(fā)現(xiàn)上面兩個(gè)calendar的gettime值是一樣的,calendar1對(duì)象里面的時(shí)間是0時(shí)區(qū)當(dāng)前的格林威治時(shí)間,這個(gè)時(shí)間和當(dāng)前東八區(qū)的格林威治時(shí)間是一樣的(就是那個(gè)毫秒值是相等的),而Date默認(rèn)的toString方法(默認(rèn)調(diào)用`calendar1.getTime().toString()`)是按照當(dāng)前的默認(rèn)時(shí)區(qū)轉(zhuǎn)換日歷的,所以看到的都是東八區(qū)的日期
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
//TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println(calendar1.getTime());    // Tue Mar 05 06:05:12 GMT 2019 當(dāng)設(shè)置默認(rèn)時(shí)區(qū)是0時(shí)區(qū)的時(shí)候,這個(gè)時(shí)間就和之前差了8小時(shí)
//如果要獲取當(dāng)前時(shí)間直接用new Date就成了,和Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.FRANCE);一毛一樣,只有需要把一個(gè)date表示成日歷的時(shí)候才需要用Calendar和TimeZone

//源碼 通過(guò)毫秒數(shù)構(gòu)建了一個(gè)date對(duì)象
public final Date getTime() {    
    return new Date(getTimeInMillis());
}

DateFormat

DateFormat用于日期的格式轉(zhuǎn)換,是一個(gè)抽象類,也需要通過(guò)工廠方法產(chǎn)生實(shí)例對(duì)象:

  • public final static DateFormat getTimeInstance() //只處理時(shí)間的轉(zhuǎn)換
  • public final static DateFormat getDateInstance() //只處理日期的轉(zhuǎn)換
  • public final static DateFormat getDateTimeInstance() //既可以處理時(shí)間,也可以處理日期
Date date = new Date();

DateFormat dateFormat = DateFormat.getDateInstance();
System.out.println(dateFormat.format(date));    //2019-3-5

DateFormat timeFormat = DateFormat.getTimeInstance();
System.out.println(timeFormat.format(date));    //14:24:28

DateFormat dateTimeFormat=DateFormat.getDateTimeInstance();
System.out.println(dateTimeFormat.format(date));    //2019-3-5 14:24:28
dateTimeFormat.setTimeZone(TimeZone.getTimeZone("Europe/London")); //設(shè)置時(shí)區(qū)
System.out.println(dateTimeFormat.format(date));    //2019-3-5 14:24:28

DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(simpleDateFormat.format(date));  //2019-03-05 14:24:28
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London")); //設(shè)置時(shí)區(qū)
System.out.println(simpleDateFormat.format(date));  //2019-03-05 06:24:28

一般都是使用SimpleDateFormat自定義輸出格式,SimpleDateFormat線程不安全,為什么不安全呢?
因?yàn)镾impleDateFormat內(nèi)部有一個(gè)Calendar對(duì)象,用來(lái)存儲(chǔ)和這個(gè)SimpleDateFormat相關(guān)的日期信息,simpleDateFormat.format(date)simpleDateFormat.parse(str_date),這些date都是Calendar來(lái)存儲(chǔ)的,這會(huì)帶來(lái)一個(gè)問(wèn)題,如果SimpleDateFormat定義成了static,那么多個(gè)thread將會(huì)共享這個(gè)SimpleDateFormat,同時(shí)共享Calendar的引用,造成一個(gè)線程的數(shù)據(jù)被另一個(gè)線程覆蓋或清除。

// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
                            FieldDelegate delegate) {
    // Convert input date to time field list
    calendar.setTime(date);
    ...
}
Calendar establish(Calendar cal) {
    ...
    cal.clear();
    ...
}

解決辦法

  1. 將SimpleDateFormat定義成局部變量,但是每調(diào)用一次都會(huì)創(chuàng)建一個(gè)對(duì)象,比較浪費(fèi)
  2. 加同步鎖synchronize,但是可能會(huì)在這個(gè)地方造成瓶頸,影響性能
  3. 使用joda-time,或者java8的時(shí)間類,很強(qiáng)大,特別推薦
  4. 使用ThreadLocal,每個(gè)線程有一個(gè)自己的SimpleDateFormat對(duì)象,因?yàn)槊恳粋€(gè)Thread,都是線性執(zhí)行的,所以不會(huì)出現(xiàn)競(jìng)爭(zhēng)Calendar的情況。
public class DateUtilTest {
    private static final Object lockObj = new Object();
    private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<>();


    private static SimpleDateFormat getSimpleDateFormat(final String pattren){
        ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattren);
        if (tl == null){
            synchronized (lockObj){
                tl = sdfMap.get(pattren);
                if (tl == null){
                    tl = ThreadLocal.withInitial(() -> new SimpleDateFormat(pattren));
                    sdfMap.put(pattren, tl);
                }
            }
        }
        return tl.get();
    }
}

Java8新引入的API

Instant,LocalDate,LocalTime,LocalDateTime都是不可變類,線程安全,放心使用

Instant

Instant是不可變類,用來(lái)代替Date,表示一個(gè)時(shí)間戳,一個(gè)絕對(duì)的時(shí)間,和時(shí)區(qū),地區(qū)無(wú)關(guān),可以表示納秒級(jí)別的時(shí)間,Date只能到毫秒。

Instant instant = Instant.now();
System.out.println(instant);    //2019-03-05T08:02:09.391Z

Instant instant1 = Instant.now(Clock.systemDefaultZone());
System.out.println(instant1);   //2019-03-05T08:02:09.481Z

Instant instant2 = Instant.ofEpochSecond(60,1234);
System.out.println(instant2);   //1970-01-01T00:01:00.000001234Z

Instant instant3 = Instant.ofEpochMilli(1000);
System.out.println(instant3);   //1970-01-01T00:00:01Z

Date date1 = Date.from(instant3);
System.out.println(date1);  //Thu Jan 01 08:00:01 CST 1970

Date date = new Date();
Instant instant4 = date.toInstant();
System.out.println(instant4);   //2019-03-05T08:02:46.703Z

Instant.parse("1970-01-01T00:00:01Z");

LocalDate

LocalDate 是不可變類,線程安全,用來(lái)處理日期

LocalDate localDate = LocalDate.now();
System.out.println(localDate);  //2019-03-05
localDate.atStartOfDay();   //2019-03-05T00:00
localDate.getDayOfMonth();  //5
localDate.getDayOfYear();   //64
localDate.getDayOfWeek();   //TUESDAY
//獲取到當(dāng)前時(shí)區(qū)今天的開始時(shí)間并轉(zhuǎn)成Instant,因?yàn)樯虾J菛|八區(qū)時(shí)間,所以當(dāng)天開始時(shí)間是0點(diǎn),轉(zhuǎn)成Instant后是標(biāo)準(zhǔn)的GMT時(shí)間,所以看上去慢了8小時(shí)
System.out.println(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).getZone());  //Asia/Shanghai
System.out.println(localDate.atStartOfDay().atZone(ZoneId.systemDefault()));    //2019-03 -07T00:00+08:00[Asia/Shanghai]
System.out.println(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());    //2019-03-06T16:00:00Z

LocalDate localDate1 = LocalDate.of(2019, 3, 31);
System.out.println(localDate1); //2019-03-31
localDate1.plusYears(-1).plusMonths(-1).plusDays(-1);   //2018-02-27
localDate1.minusYears(-1).minusMonths(-1).minusDays(-1);    //2020-05-01
localDate1.isLeapYear();    //false
localDate1.lengthOfYear();  //365

//Date轉(zhuǎn)LocalDate
Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
LocalDate localDate2 = localDateTime.toLocalDate();

LocalDate localDate3 = LocalDateTime.ofInstant(new Date().toInstant(),ZoneId.systemDefault()).toLocalDate()
//LocalDate轉(zhuǎn)Date
Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())

LocalTime

不可變類,用來(lái)處理時(shí)間,提供 小時(shí),分鐘,秒,納秒的處理,默認(rèn)使用系統(tǒng)的默認(rèn)時(shí)區(qū)處理時(shí)間

LocalTime localTime = LocalTime.now();
System.out.println(localTime);  //16:50:15.309

LocalTime localTime1 = LocalTime.of(12,34,56,789);
System.out.println(localTime1); //12:34:56.000000789

// Date轉(zhuǎn)LocalTime
System.out.println(LocalDateTime.ofInstant(new Date().toInstant(),ZoneId.systemDefault()).toLocalTime());   //16:50:15.309

//LocalTime轉(zhuǎn)Date
Date.from(LocalTime.now().atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant())   //Tue Mar 05 16:56:09 CST 2019

LocalDateTime

不可變類,表示日期和時(shí)間,默認(rèn)使用系統(tǒng)默認(rèn)時(shí)區(qū)表示GMT時(shí)間

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);  //2019-03-05T16:59:48.227

LocalDateTime localDateTime1 = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println(localDateTime1); //2019-03-05T16:59:48.227

LocalDateTime localDateTime2 = LocalDateTime.now(ZoneId.of("GMT-5"));
System.out.println(localDateTime2); //2019-03-05T03:59:48.227

LocalDateTime localDateTime3 = LocalDateTime.ofInstant(Instant.now(),ZoneId.of("GMT"));
System.out.println(localDateTime3); //2019-03-05T09:01:44.650

//Date 轉(zhuǎn) LocalDateTime
LocalDateTime.ofInstant(new Date().toInstant(),ZoneId.systemDefault())
//LocalDateTime 轉(zhuǎn)  Date
Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant())

ZonedDateTime

不可變類,綁定了時(shí)區(qū)的LocalDateTime,內(nèi)部包括一個(gè)LocalDateTime的實(shí)例,和時(shí)區(qū)信息ZoneId,時(shí)區(qū)偏移量ZoneOffset

ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);  //2019-03-05T17:08:00.309+08:00[Asia/Shanghai]
ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("GMT-8"));   //2019-03-05T01:37:57.640-08:00[GMT-08:00]
System.out.println(zonedDateTime1);

//獲取所有時(shí)區(qū)名稱
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for (String str : zoneIds){
    if (str.startsWith("Asia"))
        System.out.println(str);
}

DateTimeFormatter

不可變類,線程安全,用來(lái)格式化時(shí)間

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(dateTimeFormatter.format(localDateTime));    //2019-03-05 17:49:40


String str = "2019-03-05 17:48:14";
LocalDateTime localDateTime1 = LocalDateTime.parse(str, dateTimeFormatter);
System.out.println(localDateTime1); //2019-03-05T17:48:14

Period Duration

計(jì)算時(shí)間的差值

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("GMT-8"));
System.out.println(localDateTime1);

//時(shí)間的差值
Duration duration = Duration.between(localDateTime, localDateTime1);
System.out.println(duration.toHours());

//日期的差值
Period period = Period.between(localDateTime.toLocalDate(), localDateTime1.toLocalDate());
System.out.println(period.getDays());

Joda -Time

<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10.1</version>
</dependency>

Joda設(shè)計(jì)出來(lái)是為了替代Date和Calendar這一套的,jdk8重寫了java的日期庫(kù),引入了java.time包,Joda-Time的作者Stephen Colebourne和Oracle一起共同參與了這些API的設(shè)計(jì)和實(shí)現(xiàn)。
** 核心類**

  • Instant 表示一個(gè)時(shí)間軸上一個(gè)瞬時(shí)的時(shí)間,和時(shí)區(qū)無(wú)關(guān)
  • DateTime 替換JDK的Calendar類 處理時(shí)區(qū)用這個(gè)類
  • LocalDate 只包含日期部分
  • LocalTime 只包含時(shí)間部分
  • LocalDateTime 日期-時(shí)間
org.joda.time.Instant instant = new org.joda.time.Instant();
System.out.println(instant);    //2019-03-07T03:05:14.904Z
System.out.println(org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(DateTimeZone.forID("Asia/Tokyo")).print(instant));   //2019-03-07 03:05:14

org.joda.time.LocalDate localDate = org.joda.time.LocalDate.now();
System.out.println(localDate);  //2019-03-07
System.out.println(localDate.toString("MM-dd-yyyy"));   //03-07-2019

org.joda.time.LocalTime localTime = org.joda.time.LocalTime.now();
System.out.println(localTime);  //11:07:06.920
System.out.println(localTime.toString("HH:mm:ss")); //11:07:06

org.joda.time.LocalDateTime localDateTime = org.joda.time.LocalDateTime.now();
System.out.println(localDateTime);  //2019-03-07T11:08:30.802
System.out.println(localDateTime.toString("yyyy-MM-dd HH:mm:ss"));  //2019-03-07 11:08:30

System.out.println(org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(DateTimeZone.forID("Asia/Tokyo")).withLocale(Locale.JAPAN).print(localDate));   //2019-03-07 ??:??:??
System.out.println(org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(DateTimeZone.forID("Asia/Tokyo")).print(localTime));   //????-??-?? 11:28:21
System.out.println(org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(DateTimeZone.forID("GMT")).print(localDateTime));   //2019-03-07 11:28:21

DateTime dateTime = new DateTime();
System.out.println(dateTime);   //2019-03-07T11:13:16.440+08:00
DateTime dateTime1 = new DateTime(new Date());
System.out.println(dateTime1);  //2019-03-07T11:13:16.440+08:00
DateTime dateTime2 = new DateTime(System.currentTimeMillis());
System.out.println(dateTime2);  //2019-03-07T11:13:16.455+08:00
DateTime dateTime3 = new DateTime(2019,3,7,11,13,16,455);
System.out.println(dateTime3);  //2019-03-07T11:15:05.455+08:00
DateTime dateTime4 = new DateTime("2019-03-07T11:13:16.455+08:00");
System.out.println(dateTime4);  //2019-03-07T11:13:16.455+08:00

System.out.println(dateTime.dayOfWeek().getAsText(Locale.FRANCE));  //jeudi
System.out.println(dateTime.dayOfWeek().getAsText(Locale.KOREA));   //???

System.out.println(dateTime.dayOfMonth().roundCeilingCopy().toString("yyyy-MM-dd HH:mm:ss"));   //2019-03-08 00:00:00
System.out.println(dateTime.dayOfMonth().roundFloorCopy().toString("yyyy-MM-dd HH:mm:ss"));     //2019-03-07 00:00:00
System.out.println(dateTime.hourOfDay().roundCeilingCopy().toString("yyyy-MM-dd HH:mm:ss"));    //2019-03-07 12:00:00
System.out.println(dateTime.minuteOfHour().roundCeilingCopy().toString("yyyy-MM-dd HH:mm:ss")); //2019-03-07 11:23:00

System.out.println(dateTime.withZone(DateTimeZone.forID("Asia/Tokyo")).toString("yyyy-MM-dd HH:mm:ss"));    //2019-03-07 12:25:58

參考鏈接

https://segmentfault.com/q/1010000000178306/a-1020000000178984
https://juejin.im/post/5adb06cdf265da0b7b3579fb
https://blog.csdn.net/csdn_ds/article/details/72984646
https://juejin.im/post/5addc7a66fb9a07aa43bd2a0
http://www.lxweimin.com/p/efdeda608780

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容