java時間日期總結(jié)
[TOC]
Java早期的時間API
Date
Date既能處理時間,又能處理日期摊崭,雖然如此讼油,但是在很多方面天生缺陷,比如在jdk8之前對java的日期類的吐槽主要在這幾個方面:
- Java的日期/時間類的定義不一致呢簸,在java.util和java.sql的包中都有日期類矮台,格式化和解析的類在java.text包里面。
- java.util.Date同時包含日期和時間根时,但是java.sql.Date只包含日期嘿架。
- 所有的日期類都是可變的,因此都不是線程安全的啸箫,最大的問題之一耸彪。
- 日期類不提供國際化,不支持時區(qū)設(shè)置忘苛,因此引入了java.util.Calendar和java.util.TimeZone蝉娜,但存在上面的問題。
date中非常多的方法都已經(jīng)廢棄扎唾,標記成了@Deprecated
現(xiàn)在Date的定位是在時間軸上表示唯一一個時刻召川,它代表一個絕對的時間,也就是說不管在什么地方胸遇,在什么時區(qū)荧呐,當前時間的Date都是一樣的,都是從1970年1月1日0點0分GMT時間起,到目前這一刻的毫秒數(shù)倍阐。
目前還在用的方法有如下幾個:
- public long getTime() :返回內(nèi)部存儲的毫秒數(shù)
- public void setTime(long time):重新設(shè)置內(nèi)存的毫秒數(shù)
- public boolean before(Date when):比較給定的時刻是否早于當前 Date 實例
- public boolean after(Date when):比較給定的時刻是否晚于當前 Date 實例
- 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 這個也可以轉(zhuǎn)成GMT時間峰搪,不過toGMTString已經(jīng)廢了
Date date1 = new java.sql.Date(119,2,5); //對應(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時間,比北京時間慢8小時
Date date3 = Date.from(instant); //Tue Mar 05 11:27:31 CST 2019
Calendar
Calendar 用來表示日歷概耻,是對絕對時間Date的一種描述使套,我們看Date對象的時候肯定不會去看它的毫秒數(shù),而是看某年某月某日某時某分某秒這種形式鞠柄,日歷隨著地區(qū)和時區(qū)的不同而不同侦高,比如美國日期標準格式月/日/年,中國標準是年/月/日厌杜,再比如同一個Date矫膨,在中國東八區(qū)自然比日本東九區(qū)慢一個小時。
Calendar可以對日期進行加減操作期奔,國際化,設(shè)置時區(qū)等危尿,它是一個抽象類呐萌,不能實例化,所以提供了幾個工廠方法獲取對象谊娇。
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
方法需要提供兩個參數(shù)肺孤,一個是時區(qū),一個是語言济欢,沒有的話使用默認的設(shè)置赠堵,因為對于不同的國家,對于時刻的年月日的格式是不一樣的
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime()); //Tue Mar 05 13:29:41 CST 2019 calendar.getTime() 返回一個Date對象
calendar.add(Calendar.YEAR,1);
System.out.println(calendar.getTime()); //Tue Mar 05 13:29:41 CST 2020 calendar.getTime() 返回一個Date對象
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)上面兩個calendar的gettime值是一樣的法褥,calendar1對象里面的時間是0時區(qū)當前的格林威治時間茫叭,這個時間和當前東八區(qū)的格林威治時間是一樣的(就是那個毫秒值是相等的),而Date默認的toString方法(默認調(diào)用`calendar1.getTime().toString()`)是按照當前的默認時區(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 當設(shè)置默認時區(qū)是0時區(qū)的時候,這個時間就和之前差了8小時
//如果要獲取當前時間直接用new Date就成了杀饵,和Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.FRANCE);一毛一樣莽囤,只有需要把一個date表示成日歷的時候才需要用Calendar和TimeZone
//源碼 通過毫秒數(shù)構(gòu)建了一個date對象
public final Date getTime() {
return new Date(getTimeInMillis());
}
DateFormat
DateFormat用于日期的格式轉(zhuǎn)換,是一個抽象類切距,也需要通過工廠方法產(chǎn)生實例對象:
- public final static DateFormat getTimeInstance() //只處理時間的轉(zhuǎn)換
- public final static DateFormat getDateInstance() //只處理日期的轉(zhuǎn)換
- public final static DateFormat getDateTimeInstance() //既可以處理時間朽缎,也可以處理日期
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è)置時區(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è)置時區(qū)
System.out.println(simpleDateFormat.format(date)); //2019-03-05 06:24:28
一般都是使用SimpleDateFormat自定義輸出格式,SimpleDateFormat線程不安全,為什么不安全呢?
因為SimpleDateFormat內(nèi)部有一個Calendar對象话肖,用來存儲和這個SimpleDateFormat相關(guān)的日期信息北秽,simpleDateFormat.format(date)
,simpleDateFormat.parse(str_date)
,這些date都是Calendar來存儲的狼牺,這會帶來一個問題羡儿,如果SimpleDateFormat定義成了static,那么多個thread將會共享這個SimpleDateFormat是钥,同時共享Calendar的引用掠归,造成一個線程的數(shù)據(jù)被另一個線程覆蓋或清除。
// 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();
...
}
解決辦法
- 將SimpleDateFormat定義成局部變量悄泥,但是每調(diào)用一次都會創(chuàng)建一個對象虏冻,比較浪費
- 加同步鎖synchronize,但是可能會在這個地方造成瓶頸弹囚,影響性能
- 使用joda-time厨相,或者java8的時間類,很強大鸥鹉,特別推薦
- 使用ThreadLocal蛮穿,每個線程有一個自己的SimpleDateFormat對象,因為每一個Thread毁渗,都是線性執(zhí)行的践磅,所以不會出現(xiàn)競爭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是不可變類,用來代替Date么伯,表示一個時間戳疟暖,一個絕對的時間阎姥,和時區(qū)田绑,地區(qū)無關(guān)伍俘,可以表示納秒級別的時間装悲,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 是不可變類禀横,線程安全蛾魄,用來處理日期
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
//獲取到當前時區(qū)今天的開始時間并轉(zhuǎn)成Instant奔浅,因為上海是東八區(qū)時間摆屯,所以當天開始時間是0點邻遏,轉(zhuǎn)成Instant后是標準的GMT時間糠亩,所以看上去慢了8小時
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
不可變類,用來處理時間准验,提供 小時赎线,分鐘,秒糊饱,納秒的處理垂寥,默認使用系統(tǒng)的默認時區(qū)處理時間
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
不可變類,表示日期和時間另锋,默認使用系統(tǒng)默認時區(qū)表示GMT時間
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
不可變類滞项,綁定了時區(qū)的LocalDateTime,內(nèi)部包括一個LocalDateTime的實例夭坪,和時區(qū)信息ZoneId文判,時區(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);
//獲取所有時區(qū)名稱
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for (String str : zoneIds){
if (str.startsWith("Asia"))
System.out.println(str);
}
DateTimeFormatter
不可變類,線程安全室梅,用來格式化時間
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
計算時間的差值
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("GMT-8"));
System.out.println(localDateTime1);
//時間的差值
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è)計出來是為了替代Date和Calendar這一套的戏仓,jdk8重寫了java的日期庫,引入了java.time包亡鼠,Joda-Time的作者Stephen Colebourne和Oracle一起共同參與了這些API的設(shè)計和實現(xiàn)赏殃。
** 核心類**
- Instant 表示一個時間軸上一個瞬時的時間,和時區(qū)無關(guān)
- DateTime 替換JDK的Calendar類 處理時區(qū)用這個類
- LocalDate 只包含日期部分
- LocalTime 只包含時間部分
- LocalDateTime 日期-時間
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.reibang.com/p/efdeda608780