話說Java一連設計了兩套時間工具,分別是日期型別Date,以及日曆型別Calendar,按理說用在編碼開發中綽綽有餘了。然而隨著Java的日益廣泛使用,人們還是發現了它們的種種弊端。且不說先天不良的Date型別,單說後起之秀的Calendar型別,這個日曆工具在實際開發中仍然存在以下毛病:
1、日曆工具獲取當前月份的時候,與Date一樣都是從0開始計數,比如通過get方法獲得的一月份數值為0;
2、日曆工具獲取當天是星期幾的時候,星期日是排在最前面的,通過get方法獲得的星期日數值為1,而星期一數值居然是2!
3、日曆工具能夠表達的最小時間單位是毫秒,使得時間精度不夠高,難以用在更加精密的科學運算場合。
4、日曆工具沒有提供閏年的判斷方法。
5、日曆工具缺乏自己的格式化工具,居然還得藉助於Date型別那邊的格式化工具SimpleDateFormat,方能將日期時間按照指定格式輸出為字串。
總而言之,不管是Date還是Calendar,在解決複雜問題之時的編碼都很彆扭,故而每個Java工程基本要重新編寫一個日期處理工具DateUtil,在新工具內部封裝常見的日期處理操作,這樣才能滿足實際業務的開發要求。於是Date和Calendar兩個難兄難弟從JDK1.1開始並肩作戰,一路走到Java5、Java6乃至Java7,後來估摸著無可救藥了,乾爹Oracle一不做、二不休,終於在Java8推出了全新的日期時間型別,意圖通過新型別一勞永逸治好Date和Calendar的沉痾宿疾。
全新的日期時間型別不單單是一個型別,而是一個家族,它的成員主要有LocalDate、LocalTime、LocalDateTime等等,接下來分別介紹這幾個日期時間型別:
1、本地日期型別LocalDate
獲取本地日期的例項很簡單,呼叫該型別的now方法即可,並且顧名思義得到的是當前日期。通過本地日期獲取年月日的數值,就是日常生活中的習慣數字,例如一月份對應的數值是1,十二月份對應的數值是12,星期一對應的數值是1,星期日對應的數值是7等等。此外,本地日期額外提供了幾個常用的統計方法,包括:該日期所在的年份一共有多少天、該日期所在的月份一共有多少天、該日期所在的年份是否為閏年等。下面的程式碼便演示瞭如何從本地日期獲取各種數值的例子:
// 獲得本地日期的例項 LocalDate date = LocalDate.now(); System.out.println("date=" + date.toString()); // 獲得該日期所在的年份 int year = date.getYear(); System.out.println("year=" + year); // 獲得該日期所在的月份。注意getMonthValue方法返回的是數字月份,而getMonth方法返回的是英文月份 int month = date.getMonthValue(); System.out.println("month=" + month + ", english month=" + date.getMonth()); // 獲得該日期所在的日子 int dayOfMonth = date.getDayOfMonth(); System.out.println("dayOfMonth=" + dayOfMonth); // 獲得該日期在一年當中的序號 int dayOfYear = date.getDayOfYear(); System.out.println("dayOfYear=" + dayOfYear); // 獲得該日期是星期幾。注意getDayOfWeek方法返回的是英文的星期幾,後面跟著的getValue方法才返回數字的星期幾 int dayOfWeek = date.getDayOfWeek().getValue(); System.out.println("dayOfWeek=" + dayOfWeek + ", english weekday=" + date.getDayOfWeek()); // 獲得該日期所在的年份一共有多少天 int lengthOfYear = date.lengthOfYear(); System.out.println("lengthOfYear=" + lengthOfYear); // 獲得該日期所在的月份一共有多少天 int lengthOfMonth = date.lengthOfMonth(); System.out.println("lengthOfMonth=" + lengthOfMonth); // 判斷該日期所在的年份是否為閏年 boolean isLeapYear = date.isLeapYear(); System.out.println("isLeapYear=" + isLeapYear);
除了建立處於當前日期的本地例項,LocalDate還支援建立指定日期的本地例項,就像以下程式碼示範的那樣:
// 構造一個指定年月日的日期例項 LocalDate dateManual = LocalDate.of(2018, 11, 22); System.out.println("dateManual=" + dateManual.toString());
至於針對某個單位的數值,LocalDate也提供了專門的修改方法,例如以plus打頭的系列方法用來增加日期數值,以minus打頭的系列方法用來減少日期數值,以with打頭的系列方法用來設定日期數值,這些日期修改的具體用法示例如下:
dateManual = dateManual.plusYears(0); // 增加若干年份 dateManual = dateManual.plusMonths(0); // 增加若干月份 dateManual = dateManual.plusDays(0); // 增加若干日子 dateManual = dateManual.plusWeeks(0); // 增加若干星期 dateManual = dateManual.minusYears(0); // 減少若干年份 dateManual = dateManual.minusMonths(0); // 減少若干月份 dateManual = dateManual.minusDays(0); // 減少若干日子 dateManual = dateManual.minusWeeks(0); // 減少若干星期 dateManual = dateManual.withYear(2000); // 設定指定的年份 dateManual = dateManual.withMonth(12); // 設定指定的月份 dateManual = dateManual.withDayOfYear(1); // 設定當年的日子 dateManual = dateManual.withDayOfMonth(1); // 設定當月的日子
此外,作為一種日期型別,LocalDate一如既往地支援判斷兩個日期例項的早晚關係,比如equals方法用於判斷兩個日期是否相等,isBefore方法用於判斷A日期是否在B日期之前,isAfter方法用於判斷A日期是否在B日期之後等。具體的本地日期校驗程式碼如下所示:
// 判斷兩個日期是否相等 boolean equalsDate = date.equals(dateManual); System.out.println("equalsDate=" + equalsDate); // 判斷A日期是否在B日期之前 boolean isBeforeDate = date.isBefore(dateManual); System.out.println("isBeforeDate=" + isBeforeDate); // 判斷A日期是否在B日期之後 boolean isAfterDate = date.isAfter(dateManual); System.out.println("isAfterDate=" + isAfterDate); // 判斷A日期是否與B日期相等 boolean isEqualDate = date.isEqual(dateManual); System.out.println("isEqualDate=" + isEqualDate);
2、本地時間型別LocalTime
前面介紹的LocalDate只能操作年月日,若要操作時分秒則需通過本地時間型別LocalTime。獲取本地時間的例項依然要呼叫該型別的now方法,接著就能通過該例項分別獲取對應的時分秒乃至納秒(一秒的十億分之一),下面便演示瞭如何呼叫LocalTime的基本方法:
// 獲得本地時間的例項 LocalTime time = LocalTime.now(); System.out.println("time=" + time.toString()); // 獲得該時間所在的時鐘 int hour = time.getHour(); System.out.println("hour=" + hour); // 獲得該時間所在的分鐘 int minute = time.getMinute(); System.out.println("minute=" + minute); // 獲得該時間所在的秒鐘 int second = time.getSecond(); System.out.println("second=" + second); // 獲得該時間秒鐘後面的納秒單位。一秒等於一千毫秒,一毫秒等於一千微秒,一微秒等於一千納秒,算下來一秒等於十億納秒 int nano = time.getNano(); System.out.println("nano=" + nano);
如同本地日期LocalDate那樣,LocalTime也允許建立指定時分秒的時間例項,還支援單獨修改時鐘、分鐘、秒鐘和納秒。當然修改時間的途徑包括plus系列方法、minus系列方法、with系列方法等等,它們的呼叫方式示例如下:
// 構造一個指定時分秒的時間例項 LocalTime timeManual = LocalTime.of(14, 30, 25); System.out.println("timeManual=" + timeManual.toString()); timeManual = timeManual.plusHours(0); // 增加若干時鐘 timeManual = timeManual.plusMinutes(0); // 增加若干分鐘 timeManual = timeManual.plusSeconds(0); // 增加若干秒鐘 timeManual = timeManual.plusNanos(0); // 增加若干納秒 timeManual = timeManual.minusHours(0); // 減少若干時鐘 timeManual = timeManual.minusMinutes(0); // 減少若干分鐘 timeManual = timeManual.minusSeconds(0); // 減少若干秒鐘 timeManual = timeManual.minusNanos(0); // 減少若干納秒 timeManual = timeManual.withHour(0); // 設定指定的時鐘 timeManual = timeManual.withMinute(0); // 設定指定的分鐘 timeManual = timeManual.withSecond(0); // 設定指定的秒鐘 timeManual = timeManual.withNano(0); // 設定指定的納秒
另外,LocalTime依然提供了equals、isBefore、isAfter等方法用於判斷兩個時間的先後關係,具體的方法呼叫如下所示:
// 判斷兩個時間是否相等 boolean equalsTime = time.equals(timeManual); System.out.println("equalsTime=" + equalsTime); // 判斷A時間是否在B時間之前 boolean isBeforeTime = time.isBefore(timeManual); System.out.println("isBeforeTime=" + isBeforeTime); // 判斷A時間是否在B時間之後 boolean isAfterTime = time.isAfter(timeManual); System.out.println("isAfterTime=" + isAfterTime);
3、本地日期時間型別LocalDateTime
現在有了LocalDate專門處理年月日,又有了LocalTime專門處理時分秒,還需要一種型別能夠同時處理年月日和時分秒,它就是本地日期時間型別LocalDateTime。LocalDateTime基本等價於LocalDateTime與LocalTime的合集,它同時擁有二者的絕大部分方法,故這裡不再贅述。下面是建立該型別例項的程式碼片段,讀者可參考之前LocalDateTime與LocalTime的呼叫程式碼,嘗試補齊LocalDateTime的方法呼叫過程。
// 演示LocalDateTime的各種方法 private static void showLocalDateTime() { // 獲得本地日期時間的例項 LocalDateTime datetime = LocalDateTime.now(); System.out.println("datetime=" + datetime.toString()); // LocalDateTime的方法是LocalDate與LocalTime的合集, // 也就是說LocalDate與LocalTime的大部分方法可以直接拿來給LocalDateTime使用, // 因而下面不再演示LocalDateTime的詳細方法如何呼叫了。 // 注意LocalDateTime不提供lengthOfYear、lengthOfMonth、isLeapYear這三個方法。 }
更多Java技術文章參見《Java開發筆記(序)章節目錄》