Date和Calendar
此文章來源于廖雪峰博客:https://www.liaoxuefeng.com/wiki/1252599548343744/1303791989162017
在計算機中,應該如何表示日期和時間呢?
我們經常看到的日期和時間表示方式如下:
- 2019-11-20 0:15:01 GMT+00:00
- 2019年11月20日8:15:01
- 11/19/2019 19:15:01 America/New_York
如果直接以字符串的形式存儲,那么不同的格式,不同的語言會讓表示方式非常繁瑣。
在理解日期和時間的表示方式之前,我們先要理解數據的存儲和展示。
當我們定義一個整型變量并賦值時:
int n = 123400;編譯器會把上述字符串(程序源碼就是一個字符串)編譯成字節碼。在程序的運行期,變量n指向的內存實際上是一個4字節區域:
┌──┬──┬──┬──┐ │00│01│e2│08│ └──┴──┴──┴──┘注意到計算機內存除了二進制的0/1外沒有其他任何格式。上述十六機制是為了簡化表示。
當我們用System.out.println(n)打印這個整數的時候,實際上println()這個方法在內部把int類型轉換成String類型,然后打印出字符串123400。
類似的,我們也可以以十六進制的形式打印這個整數,或者,如果n表示一個價格,我們就以$123,400.00的形式來打印它:
import java.text.*; import java.util.*;public class Main {public static void main(String[] args) {int n = 123400;// 123400System.out.println(n);// 1e208System.out.println(Integer.toHexString(n));// $123,400.00System.out.println(NumberFormat.getCurrencyInstance(Locale.US).format(n));} }?Run
123400 1e208 $123,400.00可見,整數123400是數據的存儲格式,它的存儲格式非常簡單。而我們打印的各種各樣的字符串,則是數據的展示格式。展示格式有多種形式,但本質上它就是一個轉換方法:
String toDisplay(int n) { ... }理解了數據的存儲和展示,我們回頭看看以下幾種日期和時間:
- 2019-11-20 0:15:01 GMT+00:00
- 2019年11月20日8:15:01
- 11/19/2019 19:15:01 America/New_York
它們實際上是數據的展示格式,分別按英國時區、中國時區、紐約時區對同一個時刻進行展示。而這個“同一個時刻”在計算機中存儲的本質上只是一個整數,我們稱它為Epoch Time。
Epoch Time是計算從1970年1月1日零點(格林威治時區/GMT+00:00)到現在所經歷的秒數,例如:
1574208900表示從從1970年1月1日零點GMT時區到該時刻一共經歷了1574208900秒,換算成倫敦、北京和紐約時間分別是:
1574208900 = 北京時間2019-11-20 8:15:00= 倫敦時間2019-11-20 0:15:00= 紐約時間2019-11-19 19:15:00?
因此,在計算機中,只需要存儲一個整數1574208900表示某一時刻。當需要顯示為某一地區的當地時間時,我們就把它格式化為一個字符串:
String displayDateTime(int n, String timezone) { ... }Epoch Time又稱為時間戳,在不同的編程語言中,會有幾種存儲方式:
- 以秒為單位的整數:1574208900,缺點是精度只能到秒;
- 以毫秒為單位的整數:1574208900123,最后3位表示毫秒數;
- 以秒為單位的浮點數:1574208900.123,小數點后面表示零點幾秒。
它們之間轉換非常簡單。而在Java程序中,時間戳通常是用long表示的毫秒數,即:
long t = 1574208900123L;轉換成北京時間就是2019-11-20T8:15:00.123。要獲取當前時間戳,可以使用System.currentTimeMillis(),這是Java程序獲取時間戳最常用的方法。
標準庫API
我們再來看一下Java標準庫提供的API。Java標準庫有兩套處理日期和時間的API:
- 一套定義在java.util這個包里面,主要包括Date、Calendar和TimeZone這幾個類;
- 一套新的API是在Java 8引入的,定義在java.time這個包里面,主要包括LocalDateTime、ZonedDateTime、ZoneId等。
為什么會有新舊兩套API呢?因為歷史遺留原因,舊的API存在很多問題,所以引入了新的API。
那么我們能不能跳過舊的API直接用新的API呢?如果涉及到遺留代碼就不行,因為很多遺留代碼仍然使用舊的API,所以目前仍然需要對舊的API有一定了解,很多時候還需要在新舊兩種對象之間進行轉換。
本節我們快速講解舊API的常用類型和方法。
Date
java.util.Date是用于表示一個日期和時間的對象,注意與java.sql.Date區分,后者用在數據庫中。如果觀察Date的源碼,可以發現它實際上存儲了一個long類型的以毫秒表示的時間戳:
public class Date implements Serializable, Cloneable, Comparable<Date> {private transient long fastTime;... }我們來看Date的基本用法:
import java.util.*;public class Main {public static void main(String[] args) {// 獲取當前時間:Date date = new Date();System.out.println(date.getYear() + 1900); // 必須加上1900System.out.println(date.getMonth() + 1); // 0~11,必須加上1System.out.println(date.getDate()); // 1~31,不能加1// 轉換為String:System.out.println(date.toString());// 轉換為GMT時區:System.out.println(date.toGMTString());// 轉換為本地時區:System.out.println(date.toLocaleString());} }?Run
Note:?Main.java?uses?or?overrides?a?deprecated?API. Note:?Recompile?with?-Xlint:deprecation?for?details. 2021 1 1 Fri?Jan?01?02:25:18?UTC?2021 1?Jan?2021?02:25:18?GMT Jan?1,?2021,?2:25:18?AM注意getYear()返回的年份必須加上1900,getMonth()返回的月份是0~11分別表示1~12月,所以要加1,而getDate()返回的日期范圍是1~31,又不能加1。
打印本地時區表示的日期和時間時,不同的計算機可能會有不同的結果。如果我們想要針對用戶的偏好精確地控制日期和時間的格式,就可以使用SimpleDateFormat對一個Date進行轉換。它用預定義的字符串表示格式化:
- yyyy:年
- MM:月
- dd: 日
- HH: 小時
- mm: 分鐘
- ss: 秒
我們來看如何以自定義的格式輸出:
import java.text.*; import java.util.*;public class Main {public static void main(String[] args) {// 獲取當前時間:Date date = new Date();var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(sdf.format(date));} }?Run
2021-01-01 02:26:36Java的格式化預定義了許多不同的格式,我們以MMM和E為例:
import java.text.*; import java.util.*;public class Main {public static void main(String[] args) {// 獲取當前時間:Date date = new Date();var sdf = new SimpleDateFormat("E MMM dd, yyyy");System.out.println(sdf.format(date));} }?Run
Fri?Jan?01,?2021上述代碼在不同的語言環境會打印出類似Sun Sep 15, 2019這樣的日期。可以從JDK文檔查看詳細的格式說明。一般來說,字母越長,輸出越長。以M為例,假設當前月份是9月:
- M:輸出9
- MM:輸出09
- MMM:輸出Sep
- MMMM:輸出September
Date對象有幾個嚴重的問題:它不能轉換時區,除了toGMTString()可以按GMT+0:00輸出外,Date總是以當前計算機系統的默認時區為基礎進行輸出。此外,我們也很難對日期和時間進行加減,計算兩個日期相差多少天,計算某個月第一個星期一的日期等。
Calendar
Calendar可以用于獲取并設置年、月、日、時、分、秒,它和Date比,主要多了一個可以做簡單的日期和時間運算的功能。
我們來看Calendar的基本用法:
import java.util.*;public class Main {public static void main(String[] args) {// 獲取當前時間:Calendar c = Calendar.getInstance();int y = c.get(Calendar.YEAR);int m = 1 + c.get(Calendar.MONTH);int d = c.get(Calendar.DAY_OF_MONTH);int w = c.get(Calendar.DAY_OF_WEEK);int hh = c.get(Calendar.HOUR_OF_DAY);int mm = c.get(Calendar.MINUTE);int ss = c.get(Calendar.SECOND);int ms = c.get(Calendar.MILLISECOND);System.out.println(y + "-" + m + "-" + d + " " + w + " " + hh + ":" + mm + ":" + ss + "." + ms);} }?Run
2021-1-1?6?2:28:26.25注意到Calendar獲取年月日這些信息變成了get(int field),返回的年份不必轉換,返回的月份仍然要加1,返回的星期要特別注意,1~7分別表示周日,周一,……,周六。
Calendar只有一種方式獲取,即Calendar.getInstance(),而且一獲取到就是當前時間。如果我們想給它設置成特定的一個日期和時間,就必須先清除所有字段:
import java.text.*; import java.util.*;public class Main {public static void main(String[] args) {// 當前時間:Calendar c = Calendar.getInstance();// 清除所有:c.clear();// 設置2019年:c.set(Calendar.YEAR, 2019);// 設置9月:注意8表示9月:c.set(Calendar.MONTH, 8);// 設置2日:c.set(Calendar.DATE, 2);// 設置時間:c.set(Calendar.HOUR_OF_DAY, 21);c.set(Calendar.MINUTE, 22);c.set(Calendar.SECOND, 23);System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getTime()));// 2019-09-02 21:22:23} }?Run
2019-09-02?21:22:23利用Calendar.getTime()可以將一個Calendar對象轉換成Date對象,然后就可以用SimpleDateFormat進行格式化了。
TimeZone
Calendar和Date相比,它提供了時區轉換的功能。時區用TimeZone對象表示:
import java.util.*;public class Main {public static void main(String[] args) {TimeZone tzDefault = TimeZone.getDefault(); // 當前時區TimeZone tzGMT9 = TimeZone.getTimeZone("GMT+09:00"); // GMT+9:00時區TimeZone tzNY = TimeZone.getTimeZone("America/New_York"); // 紐約時區System.out.println(tzDefault.getID()); // Asia/ShanghaiSystem.out.println(tzGMT9.getID()); // GMT+09:00System.out.println(tzNY.getID()); // America/New_York} }?Run
Etc/UTC GMT+09:00 America/New_York時區的唯一標識是以字符串表示的ID,我們獲取指定TimeZone對象也是以這個ID為參數獲取,GMT+09:00、Asia/Shanghai都是有效的時區ID。要列出系統支持的所有ID,請使用TimeZone.getAvailableIDs()。
有了時區,我們就可以對指定時間進行轉換。例如,下面的例子演示了如何將北京時間2019-11-20 8:15:00轉換為紐約時間:
import java.text.*; import java.util.*;public class Main {public static void main(String[] args) {// 當前時間:Calendar c = Calendar.getInstance();// 清除所有:c.clear();// 設置為北京時區:c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));// 設置年月日時分秒:c.set(2019, 10 /* 11月 */, 20, 8, 15, 0);// 顯示時間:var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));System.out.println(sdf.format(c.getTime()));// 2019-11-19 19:15:00} }?Run
2019-11-19?19:15:00可見,利用Calendar進行時區轉換的步驟是:
因此,本質上時區轉換只能通過SimpleDateFormat在顯示的時候完成。
Calendar也可以對日期和時間進行簡單的加減:
import java.text.*; import java.util.*;public class Main {public static void main(String[] args) {// 當前時間:Calendar c = Calendar.getInstance();// 清除所有:c.clear();// 設置年月日時分秒:c.set(2019, 10 /* 11月 */, 20, 8, 15, 0);// 加5天并減去2小時:c.add(Calendar.DAY_OF_MONTH, 5);c.add(Calendar.HOUR_OF_DAY, -2);// 顯示時間:var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date d = c.getTime();System.out.println(sdf.format(d));// 2019-11-25 6:15:00} }?Run
2019-11-25?06:15:00小結
計算機表示的時間是以整數表示的時間戳存儲的,即Epoch Time,Java使用long型來表示以毫秒為單位的時間戳,通過System.currentTimeMillis()獲取當前時間戳。
Java有兩套日期和時間的API:
- 舊的Date、Calendar和TimeZone;
- 新的LocalDateTime、ZonedDateTime、ZoneId等。
分別位于java.util和java.time包中。
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的Date和Calendar的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos mysql5.6.35_c
- 下一篇: ios退款 怎么定位到是哪个用户_关于i