正如@Matt's answer 中解释的那样,忽略偏移量 (GMT-8) 并不是一件好事,因为SimpleDateFormat 将使用系统的默认时区——并且该时区在每个设备/环境中可能不同,并且你无法控制它。即使默认时区是正确的,它也可以更改,即使在运行时也是如此,所以你不能假设它总是你需要的。
示例:我的默认时区是America/Sao_Paulo,当前偏移量是-03:00(或GMT-3,或比UTC 晚3 小时)。如果我使用您的代码,它将假定它是圣保罗的 13:34(UTC 时间是 16:34 - millis 值是4876130061000,这是错误的,因为它不等于原始输入(13:34 in GMT-8))。
如果我将默认时区更改为GMT-8,代码只会给出正确的值。为了不依赖于此,您可以设置格式化程序的时区。
SimpleDateFormathas some patterns to parse timezone/offset,但它们都没有与GMT-8一起工作(似乎它只接受GMT-08:00),所以一种解决方案是从输入中删除它并使用它来设置正确的时区格式化程序:
String input = "Tue, 08 Jul 2124 13:34:21 GMT-8";
String[] v = input.split(" GMT"); // split the string, before and after "GMT"
SimpleDateFormat serverDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.ENGLISH);
// set the formatter timezone to the offset (in this case, to "GMT-8")
serverDateFormat.setTimeZone(TimeZone.getTimeZone("GMT" + v[1]));
// parse the date/time part
Date date = serverDateFormat.parse(v[0]);
日期毫秒值将为4876148061000,相当于2124-07-08T21:34:21Z(21:34 UTC = 13:34 in GMT-8)。
另一个细节是,当你这样做时:
System.out.println(date);
它调用Date::toString 方法,该方法获取毫秒值并转换为系统的默认时区,给人以日期对象具有时区的错误印象 - but that's wrong:日期仅包含自1970-01-01T00:00Z 以来的毫秒数.
toString 通常也会在您在调试器中看到日期时调用
如果您想打印某些特定时区的等效日期和时间值,您可以使用SimpleDateFormat 并在此格式化程序中设置您想要的时区。使用 Date::toString 总是会误导您的价值观。
要使用服务器使用的相同偏移量打印日期/时间,您可以创建一个SimpleDateFormat 并将GMT-8 设置为它,如上:
// display the date in a specific format
SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// use the same offset used by the server (GMT-8)
outputFormat.setTimeZone(TimeZone.getTimeZone("GMT-8"));
System.out.println(outputFormat.format(date)); // 08/07/2124 13:34:21
输出将是:
08/07/2124 13:34:21
如果您不指定时区,它将使用设备/系统的默认值,根据设备的配置给出不同的结果。
请注意,我还使用Locale.ENGLISH 来解析输入。这是必需的,因为月份和星期几是英文的。如果我不指定java.util.Locale,格式化程序将使用系统的默认值,并且不保证始终为英文。而且这也可以在运行时更改,因此最好使用显式而不是依赖默认值。
Java 新的日期/时间 API
旧的类(Date、Calendar 和 SimpleDateFormat)有 lots of problems 和 design issues,它们正在被新的 API 取代。
在 Android 中,您可以使用 ThreeTen Backport,这是 Java 8 新日期/时间类的一个很好的反向移植。为了使它工作,你还需要ThreeTenABP(更多关于如何使用它here)。
由于输入具有日期、时间和偏移量,您可以使用org.threeten.bp.format.DateTimeFormatter 将其解析为org.threeten.bp.OffsetDateTime。
一个细节是 2124 年 7 月 8 日th 是星期六,所以我必须更改输入字符串,否则会出错。 SimpleDateFormat 没有给出这个错误,因为众所周知它过于宽容,忽略了很多错误,并尝试以不那么聪明的方式“修复”(这可能有点基于意见,但许多人认为这很糟糕这就是为什么新的 API 对此更加严格)。
String input = "Sat, 08 Jul 2124 13:34:21 GMT-8";
DateTimeFormatter parser = DateTimeFormatter.ofPattern("EE, dd MMM yyyy HH:mm:ss O", Locale.ENGLISH);
OffsetDateTime odt = OffsetDateTime.parse(input, parser);
odt 将等同于 2124-07-08T13:34:21-08:00。
无需在格式化程序中设置时区,因为它可以正确解析输入的偏移量。要获取毫秒值,只需执行以下操作:
// the same value as date.getTime()
long millis = odt.toInstant().toEpochMilli();
要转换为java.util.Date,您可以使用org.threeten.bp.DateTimeUtils 类:
// convert to java.util.Date
Date date = DateTimeUtils.toDate(odt.toInstant());
要向用户显示,您可以使用不同的DateTimeFormatter。由于OffsetDateTime 保持正确的值,因此无需在格式化程序中设置时区:
// display date in a specific format
DateTimeFormatter outputFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
System.out.println(odt.format(outputFormat)); // 08/07/2124 13:34:21
输出将是:
08/07/2124 13:34:21
要转换到另一个时区或偏移量,您可以使用 org.threeten.bp.ZoneId 和 org.threeten.bp.ZoneOffset 类:
// convert to UTC
System.out.println(outputFormat.format(odt.withOffsetSameInstant(ZoneOffset.UTC)));
// convert to offset +05:00
System.out.println(outputFormat.format(odt.withOffsetSameInstant(ZoneOffset.ofHours(5))));
// convert to Europe/Berlin timezone
System.out.println(outputFormat.format(odt.atZoneSameInstant(ZoneId.of("Europe/Berlin"))));
输出是:
08/07/2124 21:34:21
2124 年 9 月 7 日 02:34:21
2124 年 8 月 7 日 23:34:21
请注意,我使用了时区 Europe/Berlin。
API 使用IANA timezones names(始终采用Continent/City 格式,如America/Sao_Paulo 或Europe/Berlin)。
避免使用三个字母的缩写(如CST 或PST),因为它们是ambiguous and not standard。
您可以使用ZoneId.getAvailableZoneIds() 获取所有可用时区的列表(并进行相应选择)。