之前在我的知識星球的直面Java板塊中,給粉絲們出了這樣一道題:
**在Java中,如何獲取不同時區的當前時間?**
你知道這道題的正確答案應該如何回答嗎?背后的原理又是什么呢?
然后,緊接著,我又提出了以下問題:
**為什么以下代碼無法得到美國時間。(在東八區的計算機上)**
~~~
System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")).getTime());
~~~
接下來,本文就圍繞這兩個問題,來帶領讀者一起學習一下哪些和Java中的時間有關的概念。
#1. 時區
前面提到了時區,可能很多讀者不知道什么是時區,先來簡單介紹一下。
時區是地球上的區域使用同一個時間定義。以前,人們通過觀察太陽的位置(時角)決定時間,這就使得不同經度的地方的時間有所不同(地方時)。1863年,首次使用時區的概念。時區通過設立一個區域的標準時間部分地解決了這個問題。
世界各個國家位于地球不同位置上,因此不同國家,特別是東西跨度大的國家日出、日落時間必定有所偏差。這些偏差就是所謂的時差。
> 為了照顧到各地區的使用方便,又使其他地方的人容易將本地的時間換算到別的地方時間上去。有關國際會議決定將地球表面按經線從東到西,劃成一個個區域,并且規定相鄰區域的時間相差1小時。在同一區域內的東端和西端的人看到太陽升起的時間最多相差不過1小時。當人們跨過一個區域,就將自己的時鐘校正1小時(向西減1小時,向東加1小時),跨過幾個區域就加或減幾小時。這樣使用起來就很方便。
現今全球共分為24個時區。由于實用上常常1個國家,或1個省份同時跨著2個或更多時區,為了照顧到行政上的方便,常將1個國家或1個省份劃在一起。所以時區并不嚴格按南北直線來劃分,而是按自然條件來劃分。**例如,中國幅員寬廣,差不多跨5個時區,但為了使用方便簡單,實際上在只用東八時區的標準時即北京時間為準。**
# 2. 格林威治時間
前面提到了,時區通過設立一個區域的標準時間部分地解決了不同地方看到的太陽位置不一樣而無法定義時間的問題。那么這個標準時間是什么呢?
前面還提到。中國位于東八區,一般是用`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 小時`
#3. 時間戳
前面提到了全世界各個時區的時間可能都是不一樣的,那么有沒有一個什么樣的辦法可以不受時區的限制,可以精確的表示時間呢。
其實是有的,這個方法就是時間戳。
時間戳(timestamp),一個能表示一份數據在某個特定時間之前已經存在的、 完整的、 可驗證的數據,通常是一個字符序列,唯一地標識某一刻的時間。
**時間戳是指格林威治時間1970年01月01日00時00分00秒起至現在的總秒數。**
有了時間戳,無論我們深處哪個時區,從格林威治時間1970年01月01日00時00分00秒到現在這一時刻的總秒數應該是一樣的。所以說,時間戳是一份能夠表示一份數據在一個特定時間點已經存在的完整的可驗證的數據。
**1970-01-01**
不知道大家有沒有注意到一個比較特殊的時間,1970-01-01,相信每一個開發者對這個時間都并不陌生。一般如果軟件系統中出現這個時間的時候,代表著出現了網絡故障、線上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點整。
#4. 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中是不包含時區有關的信息的,因為時間戳和時區沒有關系。**
那么,如果想要把一個時間戳轉換成不同時區的時間輸出應該怎么做呢?
# 5. 顯示不同時區的時間
想要把時間戳轉換成對應時區的時間,總要有個地方可以獲取時區吧。其實,我們的計算機中是有時區相關的信息的。
> 無論我們使用的是哪種操作系統的電腦,都是可以查看時間的,而一般情況下,我們拿到的電腦都會展示中國時間,那是因為操作系統中已經設置了一個默認時區。
其實,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嗎](http://www.hollischuang.com/archives/3017)中也介紹過。這里就不再展開了。
接下來,我們再回到文章開始的那個問題:
**為什么以下代碼無法得到美國時間。(在東八區的計算機上)**
~~~
System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")).getTime());
~~~
其實答案前面也已經說過了,我們通過查看`Date.toString`的源碼,發現在輸出的過程中該方法只會去獲取系統的默認時區,只有修改了默認時區才會顯示該時區的時間。
但是,通過閱讀Calendar的源碼,我們可以發現,`getInstance`方法雖然有一個參數可以傳入時區,但是并沒有將默認時區設置成傳入的時區。
而在Calendar.getInstance.getTime后得到的時間只是一個時間戳,其中未保留任何和時區有關的信息,所以,在輸出時,還是顯示的是當前系統默認時區的時間。
#6. Java 8與時區
了解Java8 的朋友可能都知道,Java8提供了一套新的時間處理API,這套API比以前的時間處理API要友好的多。
Java8 中加入了對時區的支持,帶時區的時間為分別為:ZonedDate、ZonedTime、ZonedDateTime。其中每個時區都對應著 ID,地區ID都為 “{區域}/{城市}”的格式,如`Asia/Shanghai`、`America/Los_Angeles`等。
在Java8中,直接使用以下代碼即可輸出美國洛杉磯的時間:
~~~
LocalDateTime now = LocalDateTime.now(ZoneId.of("America/Los_Angeles"));
System.out.println(now);
~~~
# 9. 總結
世界上有很多時區,不同的時區的時間不一樣,中國使用東八區的時間作為標準時間。美國自東海岸至西海岸橫跨西五區至西十區,共六個時區。
所謂東八區,一般表示成`GMT+8`,這里的GMT指的是格林威治時間。計算機中經常使用時間戳來表示時間,時間戳指的就是當前時間舉例格林威治的1970-01-01 00:00:00的總秒數。
> 而Java中的Date類中是不包含時區信息的,在使用`System.out.println`打印Date的時候,回調用Date.toString方法,該方法會獲取系統的默認時區來轉換時間。
在Java8中可以使用ZonedTime、ZonedDate和ZonedDateTime來表示帶有時區信息的時間。
# 10. **什么是冬令時?什么是夏令時?**
夏令時、冬令時的出現,是為了充分利用夏天的日照,所以時鐘要往前撥快一小時,冬天再把表往回撥一小時。其中夏令時從3月第二個周日持續到11月第一個周日。
冬令時:北京和洛杉磯時差16小時,北京和紐約時差13小時。 夏令時:北京和洛杉磯時差12小時,北京和紐約時差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
- 計算機網絡
- 基礎_01
- tcp/ip
- http轉https
- Let's Encrypt免費ssl證書(基于haproxy負載)
- what's the http?
- 網關
- 網絡IO
- http
- 工具
- Git
- 初始本地倉庫并上傳
- git保存密碼
- Gitflow
- maven
- 1.生命周期命令
- 聚合與繼承
- 插件管理
- assembly
- 資源管理插件
- 依賴范圍
- 分環境打包
- dependencyManagement
- 版本分類
- 找不到主類
- 無法加載主類
- 私服
- svn
- gradle
- 手動引入第三方jar包
- 打包exe文件
- Windows
- java
- 設計模式
- 七大原則
- 1.開閉原則
- 2. 里式替換原則
- 3. 依賴倒置原則
- 4. 單一職責原則
- 單例模式
- 工廠模式
- 簡單工廠
- 工廠方法模式
- 抽象工廠模式
- 觀察者模式
- 適配器模式
- 建造者模式
- 代理模式
- 適配器模式
- 命令模式
- json
- jackson
- poi
- excel
- easy-poi
- 規則
- 模板
- 合并單元格
- word
- 讀取
- java基礎
- 類路徑與jar
- 訪問控制權限
- 類加載
- 注解
- 異常處理
- String不可變
- 跨域
- transient關鍵字
- 二進制編碼
- 泛型1
- 與或非
- final詳解
- Java -jar
- 正則
- 讀取jar
- map
- map計算
- hashcode計算原理
- 枚舉
- 序列化
- URLClassLoader
- 環境變量和系統變量
- java高級
- java8
- 1.Lambda表達式和函數式接口
- 2.接口的默認方法和靜態方法
- 3.方法引用
- 4.重復注解
- 5.類型推斷
- 6.拓寬注解的應用場景
- java7-自動關閉資源機制
- 泛型
- stream
- 時區的正確理解
- StringJoiner字符串拼接
- 注解
- @RequestParam和@RequestBody的區別
- 多線程
- 概念
- 線程實現方法
- 守護線程
- 線程阻塞
- 筆試題
- 類加載
- FutureTask和Future
- 線程池
- 同步與異步
- 高效簡潔的代碼
- IO
- ThreadLocal
- IO
- NIO
- 圖片操作
- KeyTool生成證書
- 壓縮圖片
- restful
- 分布式session
- app保持session
- ClassLoader.getResources 能搜索到的資源路徑
- java開發規范
- jvm
- 高并發
- netty
- 多線程與多路復用
- 異步與事件驅動
- 五種IO模型
- copy on write
- code style
- 布隆過濾器
- 筆試
- 數據庫
- mybatis
- mybatis與springboot整合配置
- pagehelper
- 分頁數據重復問題
- Java與數據庫之間映射
- 攔截器
- 攔截器應用
- jvm
- 堆內存測試
- 線程棧
- 直接內存
- 內存結構
- 內存模型
- 垃圾回收
- 調優
- 符號引用
- 運行參數
- 方法區
- 分帶回收理論
- 快捷開發
- idea插件
- 注釋模板
- git
- pull沖突
- push沖突
- Excel處理
- 圖片處理
- 合并單元格
- easypoi
- 模板處理
- 響應式編程
- reactor
- reactor基礎
- jingyan
- 規范
- 數據庫