详情见两篇文章:
GMT UTC CST ISO 夏令时 时间戳,都是些什么鬼?
更多:日期时间系列
另外,关于世界时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
需要注意的是,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 }
几个概念间的关系:
某个瞬时值或某个时刻由 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() 等方法。