GMT UTC CST ISO 時間戳等的含義、區別及在Java中的處理


詳情見兩篇文章:

GMT UTC CST ISO 夏令時 時間戳,都是些什么鬼?

徹底弄透Java處理GMT/UTC日期時間

更多:日期時間系列

另外,關於世界時UT(GMT)、國際原子時TAI、協調世界時UTC、授時中心、時間服務器及時間同步、牆上時間、單調時間 等的科普可參閱文章:計算機時間到底是怎么來的

 

簡單總結如下

 

GMT時間(或稱UT時間):

概念:格林尼治時間(Greenwich Mean Time,GMTT)、世界標准時間(Universal Time,UT),是指位於英國倫敦郊區的【皇家格林尼治天文台】的標准時間,是本初子午線上的地方時,是0時區的區時。

表示:GMT本地時間 = 0時區時間(即GMT標准時間) + 時區差,如:若現在GMT時間為 15:00,則北京時間(東八區)為同日的 23:00、紐約時間(西五區)為同日的 10:00。

其他:所有HTTP日期/時間戳都必須用格林威治標准時間(GMT)表示,沒有例外。對於HTTP來說,GMT完全等於UTC(協調世界時)。

 

UTC時間:

概念:世界協調時間(Coordinated Universal Time,UTC)。它是以國際原子時(International Atomic Time,TAI,來自法國名字temps atomique International)作為計量單位的時間,計算結果極其嚴謹和精密。它比GMT時間更來得精准,誤差值必須保持在0.9秒以內,倘若大於0.9秒就會通過閏秒來“解決”。1979年12月初內瓦舉行的世界無線電行政大會通過決議,確定用“世界協調時間(UTC時間)”取代“格林威治時間(GMT時間)”,作為無線電通信領域內的國際標准時間。

表示:UTC本地時間 = UTC標准時間 拼上 時間偏移量,偏移量有 ±[hh]:[mm]、±[hh][mm]、±[hh] 三種格式,如:若現在UTC時間是 10:30z(z表示偏移量=0,不可省略),則北京時間為 10:30 +0800、紐約時間為 10:30 -0500,分別表示同日下午6點半、同日上午五點半。

其他:

UTC時間里沒有時區的概念,只有偏移量的概念,時間日期聯盟組織對世界上主要的國家/地區定義了偏移量並給各偏移量取了對應的Time zone name(列表見:Time Zones)。

從效果上看,UTC標准時間恰好與GMT標准時間一樣;

GMT本地時間是由時區換算得到的、UCT本地時間是由附上偏移量得到的,兩者有很大的相似性,但由於時區只有24個,因此GMT本地時間相比於GMT標准時間有24種情況、而UTC本地時間中偏移量有無數個故有無數種情況。

由於有的國家在一年中會采用夏令時、冬令時兩種計時制,故一個國家可能在不同的日期時有不同的偏移量,因此在使用 Java 中日期時間處理的JDK時最好通過zone name(類名為 java.name.ZoneId,值如 "Asia/Shanghai")得到偏移量而非寫死偏移量值,前者內部會自動處理有不同偏移量值的情況(java.time.zone.ZoneRules)、在當前日期得到對應的正確偏移值。詳情參閱上述第二篇文章。

 

日期/時間模板:

格式化的模式由指定的字符串組成,未加引號的大寫/小寫字母(A-Z a-z)代表特定模式,用來表示模式含義,若想原樣輸出可以用單引號''包起來,除了英文字母其它均不解釋原樣輸出/匹配。

 

 

Java中處理時間、日期:(提倡棄用老舊的 Date,擁抱JSR 310的實現 java.time

java.util.Date及其子類java.sql.Date歷史最久、被使用最廣,但其有諸多缺點(見上述第二篇文章):

定義並不一致,在java.util和java.sql包中都有Date類,且對它進行格式化/解析類又跑到 java.text.SimpleDateFormat 去了;
java.util.Date 等類在建模日期的設計上行為不一致,缺陷明顯。包括易變性、糟糕的偏移值、默認值、命名等等;
java.util.Date 同時包含日期和時間,而其子類 java.sql.Date 卻僅包含日期;
國際化支持得並不是好,比如跨時區操作、夏令時等等;

從JDK 8開始引入了全新的JSR 310日期時間庫(JSR-310源於庫 joda-time 打造)解決了上面提到的所有問題,JSR 310日期/時間 所有的 API都在java.time這個包內:

 關鍵概念/類ZoneId、ZoneOffset、Instant、LocalDateTime、OffsetDateTime、ZonedDateTime、DateTimeFormatter 等,具體參閱上述第二篇文章。使用示例如下:

 1         // 獲取ZoneId
 2         System.out.println(ZoneId.getAvailableZoneIds());// [Asia/Aden, America/Cuiaba, ..., Europe/Monaco]
 3         System.out.println(ZoneId.systemDefault());// Asia/Shanghai
 4         System.out.println(ZoneId.of("Asia/Shanghai"));// Asia/Shanghai
 5 //        System.out.println(ZoneId.of("Asia/xxx"));// 報錯:java.time.zone.ZoneRulesException: Unknown time-zone ID: Asia/xxx
 6         System.out.println(ZoneId.ofOffset("UTC", ZoneOffset.of("+8")));// UTC+08:00
 7         System.out.println(ZoneId.ofOffset("UTC", ZoneOffset.of("Z")));// UTC
 8 
 9         System.out.println(ZoneId.from(ZonedDateTime.now()));// Asia/Shanghai
10         System.out.println(ZoneId.from(ZoneOffset.of("+8")));// +08:00
11 
12 //        System.out.println(ZoneId.from(LocalDateTime.now()));// 只接受帶時區的類型,LocalXXX不行,故報錯:java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor:
13 //        System.out.println(ZoneId.from(LocalDate.now()));// 只接受帶時區的類型,LocalXXX不行,故報錯:java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor:
14         System.out.println();
15 
16         // 獲取ZoneOffset
17         System.out.println(ZoneOffset.MIN);// -18:00
18         System.out.println(ZoneOffset.MAX);// +18:00
19         System.out.println(ZoneOffset.UTC);// Z
20 //        System.out.println(ZoneOffset.of("+20"));//報錯:java.time.DateTimeException: Zone offset hours not in valid range: value 20 is not in the range -18 to 18
21 
22         System.out.println(ZoneOffset.ofHours(8));// +08:00
23         System.out.println(ZoneOffset.ofHoursMinutes(8, 8));// +08:08
24         System.out.println(ZoneOffset.ofHoursMinutesSeconds(8, 8, 8));// +08:08:08
25         System.out.println(ZoneOffset.ofHours(-5));// -05:00
26         System.out.println(ZoneOffset.ofTotalSeconds(8 * 60 * 60));// +08:00
27         System.out.println();
28 
29         // 獲取本地日期/時間,不帶時區。LocalTime
30         System.out.println(LocalDate.now());// 2021-03-01
31         System.out.println(LocalTime.now());// 18:03:24.174
32         System.out.println(LocalDateTime.now());// 2021-03-01T18:03:24.174
33         System.out.println();
34 
35         // 獲取本地日期/時間,帶時區。ZonedDateTime、OffsetDateTime
36         System.out.println(ZonedDateTime.now()); // 2021-03-01T18:03:24.175+08:00[Asia/Shanghai]
37         System.out.println(ZonedDateTime.now(ZoneId.of("America/New_York"))); // 2021-03-01T05:03:24.203-05:00[America/New_York]
38         System.out.println(ZonedDateTime.now(Clock.systemUTC())); // 2021-03-01T10:03:24.206Z
39 
40         System.out.println(OffsetDateTime.now()); // 2021-03-01T18:03:24.208+08:00
41         System.out.println(OffsetDateTime.now(ZoneId.of("America/New_York"))); // 2021-03-01T05:03:24.208-05:00
42         System.out.println(OffsetDateTime.now(Clock.systemUTC())); // 2021-03-01T10:03:24.208Z
43         System.out.println();
44 
45         // 解析字符串日期或時間,分為帶時區與不帶時間的兩種。LocalTime、ZonedDateTime、OffsetDateTime
46         System.out.println(LocalDateTime.parse("2021-05-05T18:00"));// 2021-05-05T18:00
47         System.out.println(LocalDateTime.parse("2021-05-05T18:00").atOffset(ZoneOffset.ofHours(8)));// 2021-05-05T18:00+08:00
48 
49         System.out.println(OffsetDateTime.parse("2021-05-05T18:00-04:00"));// 2021-05-05T18:00-04:00
50         System.out.println(ZonedDateTime.parse("2021-05-05T18:00-05:00[America/New_York]"));// 2021-05-05T18:00-04:00[America/New_York]
51         System.out.println();
52 
53         // JSR310對日期時間的格式化/解析。java.time.format.DateTimeFormatter,線程安全
54         System.out.println(DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.now()));// 2021-03-01
55         System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(LocalTime.now()));// 18:17:15.614
56         System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now()));// 2021-03-01T18:17:15.618
57 
58         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("第Q季度 yyyy-MM-dd HH:mm:ss", Locale.US);
59         System.out.println(formatter.format(LocalDateTime.now()));// 第1季度 2021-03-01 18:19:19
60         System.out.println(formatter.parse("第1季度 2021-03-01 18:19:19", LocalDateTime::from));// 2021-03-01T18:19:19
61         System.out.println(LocalDateTime.parse("第1季度 2021-03-01 18:19:19", formatter));// 2021-03-01T18:19:19
JSR310 API Demo

需要注意的是,OffsetDateTime、ZonedDateTime的輸出中時間是本地時間(即 ISO8601 時間格式,如 2021-03-01T18:03:24.208+08:00)而不是前面說的UTC時間的表示格式(UTC標准時間 + 偏移量),也就是說這里的18:03是加了偏移量后的時間而非0時區的時間。實際上,從它們的toString方法就可以看出:

  1 package com.marchon.learning.pice;
  2 
  3 import java.time.Clock;
  4 import java.time.Duration;
  5 import java.time.LocalDate;
  6 import java.time.LocalDateTime;
  7 import java.time.LocalTime;
  8 import java.time.OffsetDateTime;
  9 import java.time.Period;
 10 import java.time.ZoneId;
 11 import java.time.ZoneOffset;
 12 import java.time.ZonedDateTime;
 13 import java.time.format.DateTimeFormatter;
 14 import java.util.Locale;
 15 
 16 public class JSR310_TimeAPI {
 17 
 18     static String zoneIdShanghai = "Asia/Shanghai";
 19     static String zoneIdNewyork = "America/New_York";
 20 
 21     public static void main(String[] args) {
 22 
 23         // 1 獲取ZoneId
 24         System.err.println("== 獲取ZoneId ==");
 25         System.out.println(ZoneId.getAvailableZoneIds());// [Asia/Aden, America/Cuiaba, ..., Europe/Monaco]
 26         System.out.println(ZoneId.systemDefault());// Asia/Shanghai
 27         System.out.println(ZoneId.of(zoneIdShanghai));// Asia/Shanghai
 28 //    System.out.println(ZoneId.of("Asia/xxx"));// 報錯:java.time.zone.ZoneRulesException: Unknown time-zone ID: Asia/xxx
 29         System.out.println(ZoneId.ofOffset("UTC", ZoneOffset.of("+8")));// UTC+08:00
 30         System.out.println(ZoneId.ofOffset("UTC", ZoneOffset.of("Z")));// UTC
 31 
 32         System.out.println(ZoneId.from(ZonedDateTime.now()));// Asia/Shanghai
 33         System.out.println(ZoneId.from(ZoneOffset.of("+8")));// +08:00
 34 
 35 //    System.out.println(ZoneId.from(LocalDateTime.now()));// 只接受帶時區的類型,LocalXXX不行,故報錯:java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor:
 36 //    System.out.println(ZoneId.from(LocalDate.now()));// 只接受帶時區的類型,LocalXXX不行,故報錯:java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor:
 37         System.out.println();
 38 
 39         // 2 獲取ZoneOffset
 40         System.err.println("== 獲取ZoneOffset ==");
 41         System.out.println(ZoneOffset.MIN);// -18:00
 42         System.out.println(ZoneOffset.MAX);// +18:00
 43         System.out.println(ZoneOffset.UTC);// Z
 44 //    System.out.println(ZoneOffset.of("+20"));//報錯:java.time.DateTimeException: Zone offset hours not in valid range: value 20 is not in the range -18 to 18
 45 
 46         System.out.println(ZoneOffset.ofHours(8));// +08:00
 47         System.out.println(ZoneOffset.ofHoursMinutes(8, 8));// +08:08
 48         System.out.println(ZoneOffset.ofHoursMinutesSeconds(8, 8, 8));// +08:08:08
 49         System.out.println(ZoneOffset.ofHours(-5));// -05:00
 50         System.out.println(ZoneOffset.ofTotalSeconds(8 * 60 * 60));// +08:00
 51         System.out.println();
 52 
 53         // 3 獲取本地日期/時間,不帶時區,會默認采用系統時區。LocalDate、LocalTime、LocalDateTime
 54         System.err.println("== 獲取本地日期/時間,不帶時區 ==");
 55         System.out.println(LocalDate.now());// 2021-03-01
 56         System.out.println(LocalTime.now());// 18:03:24.174
 57         System.out.println(LocalDateTime.now());// 2021-03-01T18:03:24.174
 58         System.out.println(LocalDateTime.of(2021, 3, 1, 10, 20));// 2021-03-01T10:20
 59         System.out.println(LocalDateTime.of(2021, 3, 1, 10, 20, 1));// 2021-03-01T10:20:01
 60         System.out.println();
 61 
 62         // 4 獲取本地日期/時間,帶時區。ZonedDateTime、OffsetDateTime
 63         System.err.println("== 獲取本地日期/時間,帶時區 ==");
 64         System.out.println(ZonedDateTime.now()); // 2021-03-01T18:03:24.175+08:00[Asia/Shanghai]
 65         System.out.println(ZonedDateTime.now(ZoneId.of(zoneIdNewyork))); // 2021-03-01T05:03:24.203-05:00[America/New_York]
 66         System.out.println(ZonedDateTime.now(Clock.systemUTC())); // 2021-03-01T10:03:24.206Z
 67         System.out.println(ZonedDateTime.of(2021, 3, 1, 10, 20, 1, 0, ZoneId.of(zoneIdNewyork)));// 2021-03-01T10:20:01-05:00[America/New_York]
 68 
 69         System.out.println(OffsetDateTime.now()); // 2021-03-01T18:03:24.208+08:00
 70         System.out.println(OffsetDateTime.now(ZoneId.of(zoneIdNewyork))); // 2021-03-01T05:03:24.208-05:00
 71         System.out.println(OffsetDateTime.now(Clock.systemUTC())); // 2021-03-01T10:03:24.208Z
 72         System.out.println(OffsetDateTime.of(2021, 3, 1, 10, 20, 1, 0, ZoneOffset.ofHours(8)));// 2021-03-01T10:20:01+08:00
 73 
 74         System.out.println();
 75 
 76         // 5 解析字符串日期或時間,分為帶時區與不帶時間的兩種。LocalTime、ZonedDateTime、OffsetDateTime
 77         System.err.println("== 解析字符串日期或時間,分為帶時區與不帶時間的兩種 ==");
 78         System.out.println(LocalDateTime.parse("2021-05-05T18:00"));// 2021-05-05T18:00
 79         System.out.println(LocalDateTime.parse("2021-05-05T18:00").atOffset(ZoneOffset.ofHours(8)));// 2021-05-05T18:00+08:00
 80 
 81         System.out.println(OffsetDateTime.parse("2021-05-05T18:00-04:00"));// 2021-05-05T18:00-04:00
 82         System.out.println(ZonedDateTime.parse("2021-05-05T18:00-05:00[America/New_York]"));// 2021-05-05T18:00-04:00[America/New_York]
 83         System.out.println();
 84 
 85         // 6 JSR310對日期時間的格式化/解析。java.time.format.DateTimeFormatter,線程安全
 86         System.err.println("== JSR310對日期時間的格式化/解析 ==");
 87         System.out.println(DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.now()));// 2021-03-01
 88         System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(LocalTime.now()));// 18:17:15.614
 89         System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now()));// 2021-03-01T18:17:15.618
 90 
 91         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("第Q季度 yyyy-MM-dd HH:mm:ss", Locale.US);
 92         System.out.println(formatter.format(LocalDateTime.now()));// 第1季度 2021-03-01 18:19:19
 93         System.out.println(formatter.parse("第1季度 2021-03-01 18:19:19", LocalDateTime::from));// 2021-03-01T18:19:19
 94         System.out.println(LocalDateTime.parse("第1季度 2021-03-01 18:19:19", formatter));// 2021-03-01T18:19:19
 95 
 96         System.out.println();
 97 
 98         // 7 計算相差的日期或時間長。Period、Duration
 99         System.err.println("== 計算相差的日期或時間長 ==");
100         LocalDateTime localDateTime = LocalDateTime.of(2021, 3, 10, 10, 20);
101         System.out.println(localDateTime);// 2021-03-10T10:20
102 
103         LocalDateTime afterLocalDateTime = localDateTime.plusMonths(1).plusDays(-3).minusHours(3);
104         System.out.println(afterLocalDateTime);// 2021-04-07T07:20
105 
106         Period period = Period.between(localDateTime.toLocalDate(), afterLocalDateTime.toLocalDate());
107         System.out.println(period.getMonths());// 0
108         System.out.println(period.getDays());// 28
109 
110         Duration duration = Duration.between(localDateTime.toLocalTime(), afterLocalDateTime.toLocalTime());
111         System.out.println(duration.toHours());// -3
112 
113         System.out.println();
114 
115         // 8日期或時間轉換。LocalDateTime、OffsetDateTime、ZonedDateTime 之間
116         System.err.println("== 日期或時間轉換 ==");
117 
118         localDateTime = LocalDateTime.of(2021, 3, 1, 18, 0, 0);
119         System.out.println(localDateTime);// 2021-03-01T18:00
120 
121         // 8.1 LocalDateTime to [OffsetDateTime、ZonedDateTime]
122         OffsetDateTime offsetDateTime1 = localDateTime.atOffset(ZoneOffset.ofHours(8));
123         OffsetDateTime offsetDateTime2 = OffsetDateTime.ofInstant(offsetDateTime1.toInstant(), ZoneOffset.ofHours(-5));
124         System.out.println(offsetDateTime1);// 2021-03-01T18:00+08:00
125         System.out.println(offsetDateTime2);// 2021-03-01T05:00-05:00
126 
127         ZonedDateTime zonedDateTime1 = localDateTime.atZone(ZoneId.of(zoneIdShanghai));
128         ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(zonedDateTime1.toInstant(), ZoneId.of(zoneIdNewyork));
129         System.out.println(zonedDateTime1);// 2021-03-01T18:00+08:00[Asia/Shanghai]
130         System.out.println(zonedDateTime2);// 2021-03-01T05:00-05:00[America/New_York]
131 
132         // 8.2 OffsetDateTime、ZonedDateTime間轉換
133         System.out.println(offsetDateTime1.toZonedDateTime());// 2021-03-01T18:00+08:00
134         System.out.println(offsetDateTime1.atZoneSameInstant(ZoneId.of(zoneIdNewyork)));// 2021-03-01T05:00-05:00[America/New_York]
135         System.out.println(offsetDateTime1.atZoneSimilarLocal(ZoneId.of(zoneIdNewyork)));// 2021-03-01T18:00-05:00[America/New_York]
136 
137         System.out.println(zonedDateTime1.toOffsetDateTime());// 2021-03-01T18:00+08:00
138 
139         // 8.3 [LocalDateTime, ZonedDateTime] to LocalDateTime
140         System.out.println(offsetDateTime1.toLocalDateTime());// 2021-03-01T18:00
141         System.out.println(zonedDateTime1.toLocalDateTime());// 2021-03-01T18:00
142 
143         // 8.4 不同zone間轉換
144         System.out.println(zonedDateTime1.withZoneSameInstant(ZoneId.of(zoneIdNewyork)));// 2021-03-01T05:00-05:00[America/New_York]
145         System.out.println(zonedDateTime1.withZoneSameLocal(ZoneId.of(zoneIdNewyork)));// 2021-03-01T18:00-05:00[America/New_York]
146  
147 
148     }
149 
150     public static ZoneOffset getOffsetByBjtime(LocalDateTime bjTime, String zoneIdStr) {
151 
152         ZonedDateTime bjZonedDateTime = bjTime.atZone(ZoneId.of(zoneIdShanghai));
153 
154         ZonedDateTime tarZonedDateTime = bjZonedDateTime.withZoneSameInstant(ZoneId.of(zoneIdStr));
155         System.err.println(tarZonedDateTime.toLocalDateTime());
156         System.err.println(tarZonedDateTime.toOffsetDateTime());
157         return tarZonedDateTime.getOffset();
158 //        LocalDateTime.ofInstant(LocalDateTime.now().to, ZoneId.of(zoneIdShanghai));
159 
160     }
161 }
View Code

 

幾個概念間的關系:

某個瞬時值或某個時刻由 LocalDateTime + ZoneOffset 唯一確定。

OffsetDateTime、ZonedDateTime、Instant 三者都能在時間線上以納秒精度存儲一個瞬間(也可理解為某個時刻),LocalDateTime則不行;

OffsetDateTime、Instant 可用於模型的字段類型,因為它們都表示瞬間值且值是確定不可變的,所以適合網絡傳輸或者數據庫持久化。而ZonedDateTime不適合網絡傳輸/持久化,因為同一個ZoneId在不同時候對應的ZoneOffset可能不同,因此可能表示兩個瞬時值 ZonedDateTime也可,因為也帶了偏移量;LocalDateTime也不可,因為其不帶時區或偏移量信息從而無法表示一個確定的時刻。

LocalDateTime、OffsetDateTime、ZonedDateTime三者間的相互轉換,可參閱: LocalDateTime、OffsetDateTime、ZonedDateTime相互轉換 。

從效果上看,轉成OffsetDateTime、ZonedDateTime類型時,結果有兩種,具體示例可參閱前面的代碼:

一種是:轉換前后的兩個時間值從字面上看是一樣的但在時間流上並不是同一個時刻。如北京時間、紐約時間都是 2020-3-1 18:00:00,但兩者並不是同一時刻。如 LocalDateTime#atOffset()/atZone() 等方法。

另一種是:轉換前后的字面值不一樣了,但在時間流上看是同一個時刻。如北京時間的 2020-3-1 18:00:00 與紐約時間的 2020-3-1 18:00:00 是同一時刻。如 LocalDateTime#ofInstant() 等方法。

上述幾個新概念與JDK8之前的幾個概念間的關系:可以用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM