1. 程式人生 > >關於Java中的時間處理,你真的瞭解嗎?

關於Java中的時間處理,你真的瞭解嗎?

之前在我的知識星球的直面Java板塊中,給粉絲們出了這樣一道題:

在Java中,如何獲取不同時區的當前時間?

你知道這道題的正確答案應該如何回答嗎?背後的原理又是什麼呢?

然後,緊接著,我又提出了以下問題:

為什麼以下程式碼無法得到美國時間。(在東八區的計算機上)

System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")).getTime());

接下來,本文就圍繞這兩個問題,來帶領讀者一起學習一下哪些和Java中的時間有關的概念。

 

時區


前面提到了時區,可能很多讀者不知道什麼是時區,先來簡單介紹一下。

時區是地球上的區域使用同一個時間定義。以前,人們通過觀察太陽的位置(時角)決定時間,這就使得不同經度的地方的時間有所不同(地方時)。1863年,首次使用時區的概念。時區通過設立一個區域的標準時間部分地解決了這個問題。

世界各個國家位於地球不同位置上,因此不同國家,特別是東西跨度大的國家日出、日落時間必定有所偏差。這些偏差就是所謂的時差。

為了照顧到各地區的使用方便,又使其他地方的人容易將本地的時間換算到別的地方時間上去。有關國際會議決定將地球表面按經線從東到西,劃成一個個區域,並且規定相鄰區域的時間相差1小時。在同一區域內的東端和西端的人看到太陽升起的時間最多相差不過1小時。當人們跨過一個區域,就將自己的時鐘校正1小時(向西減1小時,向東加1小時),跨過幾個區域就加或減幾小時。這樣使用起來就很方便。

世界時區

現今全球共分為24個時區。由於實用上常常1個國家,或1個省份同時跨著2個或更多時區,為了照顧到行政上的方便,常將1個國家或1個省份劃在一起。所以時區並不嚴格按南北直線來劃分,而是按自然條件來劃分。例如,中國幅員寬廣,差不多跨5個時區,但為了使用方便簡單,實際上在只用東八時區的標準時即北京時間為準。

格林威治時間


前面提到了,時區通過設立一個區域的標準時間部分地解決了不同地方看到的太陽位置不一樣而無法定義時間的問題。那麼這個標準時間是什麼呢?

前面還提到。中國位於東八區,一般是用GMT+8來表示東八區這個時區。那麼,看起來GMT

就是這個所謂的標準時間。GMT是個什麼東西呢?為什麼要在他的基礎上+8來表示東八區呢?

GMT,是Greenwich Mean Time的縮寫,及格林尼治(格林威治)平時,是指位於英國倫敦郊區的皇家格林尼治天文臺當地的平太陽時,因為本初子午線被定義為通過那裡的經線。

格林威治子午線

自1924年2月5日開始,格林尼治天文臺負責每隔一小時向全世界發放調時資訊。國際天文學聯合會於1928年決定,將由格林威治平子夜起算的平太陽時作為世界時,也就是通常所說的格林威治時間

一般使用GMT+8表示中國的時間,是因為中國位於東八區,時間上比格林威治時間快8個小時。

北京時間還可以用CST表示,即China Standard Time,又名中國標準時間,是中國的標準時間。當格林威治時間為凌晨0:00時,中國標準時間正好為上午8:00。

所以,有等式:CST=GMT +8 小時

 

時間戳


前面提到了全世界各個時區的時間可能都是不一樣的,那麼有沒有一個什麼樣的辦法可以不受時區的限制,可以精確的表示時間呢。

其實是有的,這個方法就是時間戳。

時間戳(timestamp),一個能表示一份資料在某個特定時間之前已經存在的、 完整的、 可驗證的資料,通常是一個字元序列,唯一地標識某一刻的時間。

時間戳是指格林威治時間1970年01月01日00時00分00秒起至現在的總秒數。

有了時間戳,無論我們深處哪個時區,從格林威治時間1970年01月01日00時00分00秒到現在這一時刻的總秒數應該是一樣的。所以說,時間戳是一份能夠表示一份資料在一個特定時間點已經存在的完整的可驗證的資料。

1970-01-01

不知道大家有沒有注意到一個比較特殊的時間,1970-01-01,相信每一個開發者對這個時間都並不陌生。一般如果軟體系統中出現這個時間的時候,代表著出現了網路故障、線上bug等。

微信手機充值Bug

當有些計算機儲存或者傳輸時間戳出錯時,這個時間戳就會取預設值。而在計算機中,預設值通常是 0。

當 Timestamp 為 0,就表示時間(GMT)1970年1月1日0時0分0秒。中國使用北京時間,處於東 8 區,相應就是早上 8 點。因此在中國這邊,時間出錯了,就經常會顯示成 1970年1月1日 08:00。

System.out.println(new Date(0));
//Thu Jan 01 08:00:00 CST 1970

當我們在Java程式碼中使用new Date(0)來建立時間的時候,得到的結果就是Thu Jan 01 08:00:00 CST 1970,既1970年1月1日 上午08點整。

 

 

Date


前面提到了java.util.Java中的Date類,這個類通常用來表示時間。你可以通過getTime()方法訪問java.util.Date例項的日期和時間,比如像這樣:

Date date = new Date();
long time = date.getTime();

以上程式碼,其實得到的就是時間戳,在原始碼中也有明確的表述:

所以,我們就可以認為java.util.Java其實表示的就是從格林威治1970年1月1日零點到現在這一時刻的總秒數。

從Date的原始碼中也可以看到,Date中是不包含時區有關的資訊的,因為時間戳和時區沒有關係。

那麼,如果想要把一個時間戳轉換成不同時區的時間輸出應該怎麼做呢?

 

 

顯示不同時區的時間


想要把時間戳轉換成對應時區的時間,總要有個地方可以獲取時區吧。其實,我們的計算機中是有時區相關的資訊的。

無論我們使用的是哪種作業系統的電腦,都是可以檢視時間的,而一般情況下,我們拿到的電腦都會展示中國時間,那是因為作業系統中已經設定了一個預設時區。

其實,Java中的時區資訊也是從作業系統中取到的,預設情況下會使用作業系統的時區。

當我們使用System.out.println來輸出一個時間的時候,他會呼叫Date類的toString方法,而該方法會讀取作業系統的預設時區來進行時間的轉換。

public String toString() {
   // "EEE MMM dd HH:mm:ss zzz yyyy";
   BaseCalendar.Date date = normalize();
   ...
}

private final BaseCalendar.Date normalize() {
   ...
   TimeZone tz = TimeZone.getDefaultRef();
   if (tz != cdate.getZone()) {
       cdate.setZone(tz);
       CalendarSystem cal = getCalendarSystem(cdate);
       cal.getCalendarDate(fastTime, cdate);
   }
   return cdate;
}

static TimeZone getDefaultRef() {
   TimeZone defaultZone = defaultTimeZone;
   if (defaultZone == null) {
       // Need to initialize the default time zone.
       defaultZone = setDefaultZone();
       assert defaultZone != null;
   }
   // Don't clone here.
   return defaultZone;
}

主要程式碼如上。也就是說如果我們想要通過System.out.println輸出一個Date類的時候,輸出美國洛杉磯時間的話,就需要想辦法把defaultTimeZone改為America/Los_Angeles,這個方法就是:

TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));

所以,當我們想要輸出美國洛杉磯時間時,可以選擇這種方式:

TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
Date date =new Date();System.out.println(date);

還有一種方式,就是通過SimpleDateFormat來處理,這種方式我們在為什麼阿里巴巴禁止把SimpleDateFormat定義為static型別的中也介紹過。這裡就不再展開了。

接下來,我們再回到文章開始的那個問題:

為什麼以下程式碼無法得到美國時間。(在東八區的計算機上)

System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")).getTime());

其實答案前面也已經說過了,我們通過檢視Date.toString的原始碼,發現在輸出的過程中該方法只會去獲取系統的預設時區,只有修改了預設時區才會顯示該時區的時間。

但是,通過閱讀Calendar的原始碼,我們可以發現,getInstance方法雖然有一個引數可以傳入時區,但是並沒有將預設時區設定成傳入的時區。

而在Calendar.getInstance.getTime後得到的時間只是一個時間戳,其中未保留任何和時區有關的資訊,所以,在輸出時,還是顯示的是當前系統預設時區的時間。

 

 

Java 8與時區


瞭解Java8 的朋友可能都知道,Java8提供了一套新的時間處理API,這套API比以前的時間處理API要友好的多。

Java8 中加入了對時區的支援,帶時區的時間為分別為:ZonedDate、ZonedTime、ZonedDateTime。其中每個時區都對應著 ID,地區ID都為 “{區域}/{城市}”的格式,如Asia/ShanghaiAmerica/Los_Angeles等。

在Java8中,直接使用以下程式碼即可輸出美國洛杉磯的時間:

 

 

總結


世界上有很多時區,不同的時區的時間不一樣,中國使用東八區的時間作為標準時間。美國自東海岸至西海岸橫跨西五區至西十區,共六個時區。

所謂東八區,一般表示成GMT+8,這裡的GMT指的是格林威治時間。計算機中經常使用時間戳來表示時間,時間戳指的就是當前時間舉例格林威治的1970-01-01 00:00:00的總秒數。

而Java中的Date類中是不包含時區資訊的,在使用System.out.println列印Date的時候,回撥用Date.toString方法,該方法會獲取系統的預設時區來轉換時間。

在Java8中可以使用ZonedTime、ZonedDate和ZonedDateTime來表示帶有時區資訊的時間。

LocalDateTime now = LocalDateTime.now(ZoneId.of("America/Los_Angeles"));
System.out.println(now);

 

 

拓展知識


什麼是冬令時?什麼是夏令時?

夏令時、冬令時的出現,是為了充分利用夏天的日照,所以時鐘要往前撥快一小時,冬天再把表往回撥一小時。其中夏令時從3月第二個週日持續到11月第一個週日。

冬令時:北京和洛杉磯時差16小時,北京和紐約時差13小時。 夏令時:北京和洛杉磯時差15小時,北京和紐約時差15小時。

CET,UTC,GMT,CST幾種常見時間的含義和關係?

CET,歐洲中部時間(英語:Central European Time,CET)是比世界標準時間(UTC)早一個小時的時區名稱之一。

UTC,協調世界時,又稱世界標準時間或世界協調時間,簡稱UTC。

GMT,格林尼治標準時間,是指位於英國倫敦郊區的皇家格林尼治天文臺的標準時間,因為本初子午線被定義在通過那裡的經線。

CST,北京時間,China Standard Time,又名中國標準時間,是中國的標準時間。

CET=UTC/GMT + 1小時、CST=UTC/GMT +8 小時、CST=CET+9