某些日期无法在 Java 中正确转换为特定时区午夜的纪元时间戳
Some dates cannot be converted correctly in Java to an epoch timestamps at the midnight of a specific timezone
这个 Java 代码,给定一个字符串日期,应该在 CET 区域的午夜打印同一日期的纪元时间戳(假设我不在同一个区域)。
public static void main(String[] args) throws ParseException {
String dateStr = "1995-06-06";
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
formatter.setTimeZone(TimeZone.getTimeZone("CET"));
Date date = formatter.parse(dateStr);
Calendar c = new GregorianCalendar();
c.setTimeZone(TimeZone.getTimeZone("CET"));
c.setTime(date);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
System.out.println("Epoch timestamp = " + c.getTime().getTime());
}
如果我运行上面的程序我应该得到打印:
Epoch timestamp = 802389600000
我可以在这里验证它是否正确:
https://www.epochconverter.com/timezones?q=802389600&tz=Europe%2FMalta
现在,这适用于大多数日期。然而,有一些奇怪的日期,如“1975-09-19”,它不起作用。事实上,它生成 180313200000 作为时间戳,它给出的是凌晨 1 点而不是午夜:
https://www.epochconverter.com/timezones?q=180313200&tz=Europe%2FMalta
你能解释一下为什么吗?我错过了什么?
您的日期示例之间的夏令时似乎有所不同。
如果我使用 java.time
(自 Java 8 起应始终使用),我会得到具有不同偏移量的结果:
"+02:00"
对于 "1995-06-06"
和
"+01:00"
对于 "1975-09-19"
我是这样得到结果的:
public static void main(String[] args) {
// provide two sample dates
String workingDateStr = "1995-06-06";
String failingDateStr = "1975-09-19";
// and a formatter that parses the format
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// then parse them to date objects that don't know about time or zone
LocalDate workingDate = LocalDate.parse(workingDateStr, dtf);
LocalDate failingDate = LocalDate.parse(failingDateStr, dtf);
/*
* then create an objects that are aware of time and zone
* by using the parsed dates, adding a time of 00:00:00 and a zone
*/
ZonedDateTime workingZdt = ZonedDateTime.of(workingDate, LocalTime.MIN, ZoneId.of("CET"));
ZonedDateTime failingZdt = ZonedDateTime.of(failingDate, LocalTime.MIN, ZoneId.of("CET"));
// finally, print different representations of the results
System.out.println(workingZdt + " ——> " + workingZdt.toInstant().toEpochMilli());
System.out.println(failingZdt + " ——> " + failingZdt.toInstant().toEpochMilli());
}
输出:
1995-06-06T00:00+02:00[CET] ——> 802389600000
1975-09-19T00:00+01:00[CET] ——> 180313200000
这意味着您最好使用特定的偏移量而不是区域。
这个问题可能是由于马耳他引入夏令时的时间问题,请查看以下代码及其输出:
public static void main(String[] args) {
// provide two sample dates
String failingDateStr = "1975-09-19";
// and a formatter that parses the format
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// then parse them to date objects that don't know about time or zone
LocalDate failingDate = LocalDate.parse(failingDateStr, dtf);
/*
* then create an objects that are aware of time and zone
* by using the parsed dates, adding a time of 00:00:00 and a zone
*/
ZonedDateTime failingZdt = ZonedDateTime.of(failingDate, LocalTime.MIN, ZoneId.of("CET"));
// add some years to 1975 and...
for (int year = 0; year < 4; year++) {
// ... print the different representations of the result
System.out.println(failingZdt.plusYears(year) + " ——> "
+ failingZdt.plusYears(year).toInstant().toEpochMilli());
}
}
输出:
1975-09-19T00:00+01:00[CET] ——> 180313200000
1976-09-19T00:00+01:00[CET] ——> 211935600000
1977-09-19T00:00+02:00[CET] ——> 243468000000
1978-09-19T00:00+02:00[CET] ——> 275004000000
此输出表示 1977 年的介绍...对吗?
时区差异
您的 Java 代码使用 CET,这不是真正的时区(例如,因为大多数使用它的地区在一年中的大部分时间都使用 CEST)。 Java 将 CET 转换为 Europe/Paris。法国和巴黎在 1975 年不使用夏令时 (DST)。1976 年 3 月重新引入。
您的 link 到纪元转换器指定了马耳他时区 (Europe/Malta)。马耳他 确实 在 1975 年使用夏令时:当年 4 月 20 日至 9 月 21 日是 CEST。
这解释了结果的差异。
在Java代码中
如果你想要马耳他时间:
String dateStr = "1975-09-19";
long epochTimestamp =
LocalDate
.parse(dateStr)
.atStartOfDay(ZoneId.of("Europe/Malta"))
.toInstant()
.toEpochMilli();
System.out.println("Epoch timestamp = " + epochTimestamp);
这会打印:
Epoch timestamp = 180309600000
并且您 link 访问的纪元转换器很乐意同意:
Conversion results (180309600)
180309600 converts to Friday September 19, 1975 00:00:00 (am) in
time zone Europe/Malta (CEST) The offset (difference to Greenwich
Time/GMT) is +02:00 or in seconds 7200. This date is in daylight
saving time.
在 Java 中,请使用现代 Java 日期和时间 API java.time 作为您的日期和时间工作。与 SimpleDateFormat
、TimeZone
、Date
和 Calendar
等旧日期和时间 类 相比,使用它要好得多。将小时等设置为 0 也不是获取当天第一时刻的正确方法。有些情况下夏令时从一天的开始开始,所以一天的第一时刻是 01:00:00。 Java 知道这一点,因此 atStartOfDay
方法将为您提供当天的正确时刻。
并且无论使用过时的还是现代的 类 始终以 region/city 格式指定时区,例如 Europe/Paris 或 Europe/Malta。三、四和五个字母的时区缩写通常含糊不清,通常不是真正的时区,因此不可依赖。
链接
这个 Java 代码,给定一个字符串日期,应该在 CET 区域的午夜打印同一日期的纪元时间戳(假设我不在同一个区域)。
public static void main(String[] args) throws ParseException {
String dateStr = "1995-06-06";
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
formatter.setTimeZone(TimeZone.getTimeZone("CET"));
Date date = formatter.parse(dateStr);
Calendar c = new GregorianCalendar();
c.setTimeZone(TimeZone.getTimeZone("CET"));
c.setTime(date);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
System.out.println("Epoch timestamp = " + c.getTime().getTime());
}
如果我运行上面的程序我应该得到打印:
Epoch timestamp = 802389600000
我可以在这里验证它是否正确:
https://www.epochconverter.com/timezones?q=802389600&tz=Europe%2FMalta
现在,这适用于大多数日期。然而,有一些奇怪的日期,如“1975-09-19”,它不起作用。事实上,它生成 180313200000 作为时间戳,它给出的是凌晨 1 点而不是午夜:
https://www.epochconverter.com/timezones?q=180313200&tz=Europe%2FMalta
你能解释一下为什么吗?我错过了什么?
您的日期示例之间的夏令时似乎有所不同。
如果我使用 java.time
(自 Java 8 起应始终使用),我会得到具有不同偏移量的结果:
"+02:00"
对于"1995-06-06"
和"+01:00"
对于"1975-09-19"
我是这样得到结果的:
public static void main(String[] args) {
// provide two sample dates
String workingDateStr = "1995-06-06";
String failingDateStr = "1975-09-19";
// and a formatter that parses the format
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// then parse them to date objects that don't know about time or zone
LocalDate workingDate = LocalDate.parse(workingDateStr, dtf);
LocalDate failingDate = LocalDate.parse(failingDateStr, dtf);
/*
* then create an objects that are aware of time and zone
* by using the parsed dates, adding a time of 00:00:00 and a zone
*/
ZonedDateTime workingZdt = ZonedDateTime.of(workingDate, LocalTime.MIN, ZoneId.of("CET"));
ZonedDateTime failingZdt = ZonedDateTime.of(failingDate, LocalTime.MIN, ZoneId.of("CET"));
// finally, print different representations of the results
System.out.println(workingZdt + " ——> " + workingZdt.toInstant().toEpochMilli());
System.out.println(failingZdt + " ——> " + failingZdt.toInstant().toEpochMilli());
}
输出:
1995-06-06T00:00+02:00[CET] ——> 802389600000
1975-09-19T00:00+01:00[CET] ——> 180313200000
这意味着您最好使用特定的偏移量而不是区域。
这个问题可能是由于马耳他引入夏令时的时间问题,请查看以下代码及其输出:
public static void main(String[] args) {
// provide two sample dates
String failingDateStr = "1975-09-19";
// and a formatter that parses the format
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// then parse them to date objects that don't know about time or zone
LocalDate failingDate = LocalDate.parse(failingDateStr, dtf);
/*
* then create an objects that are aware of time and zone
* by using the parsed dates, adding a time of 00:00:00 and a zone
*/
ZonedDateTime failingZdt = ZonedDateTime.of(failingDate, LocalTime.MIN, ZoneId.of("CET"));
// add some years to 1975 and...
for (int year = 0; year < 4; year++) {
// ... print the different representations of the result
System.out.println(failingZdt.plusYears(year) + " ——> "
+ failingZdt.plusYears(year).toInstant().toEpochMilli());
}
}
输出:
1975-09-19T00:00+01:00[CET] ——> 180313200000
1976-09-19T00:00+01:00[CET] ——> 211935600000
1977-09-19T00:00+02:00[CET] ——> 243468000000
1978-09-19T00:00+02:00[CET] ——> 275004000000
此输出表示 1977 年的介绍...对吗?
时区差异
您的 Java 代码使用 CET,这不是真正的时区(例如,因为大多数使用它的地区在一年中的大部分时间都使用 CEST)。 Java 将 CET 转换为 Europe/Paris。法国和巴黎在 1975 年不使用夏令时 (DST)。1976 年 3 月重新引入。
您的 link 到纪元转换器指定了马耳他时区 (Europe/Malta)。马耳他 确实 在 1975 年使用夏令时:当年 4 月 20 日至 9 月 21 日是 CEST。
这解释了结果的差异。
在Java代码中
如果你想要马耳他时间:
String dateStr = "1975-09-19";
long epochTimestamp =
LocalDate
.parse(dateStr)
.atStartOfDay(ZoneId.of("Europe/Malta"))
.toInstant()
.toEpochMilli();
System.out.println("Epoch timestamp = " + epochTimestamp);
这会打印:
Epoch timestamp = 180309600000
并且您 link 访问的纪元转换器很乐意同意:
Conversion results (180309600)
180309600 converts to Friday September 19, 1975 00:00:00 (am) in time zone Europe/Malta (CEST) The offset (difference to Greenwich Time/GMT) is +02:00 or in seconds 7200. This date is in daylight saving time.
在 Java 中,请使用现代 Java 日期和时间 API java.time 作为您的日期和时间工作。与 SimpleDateFormat
、TimeZone
、Date
和 Calendar
等旧日期和时间 类 相比,使用它要好得多。将小时等设置为 0 也不是获取当天第一时刻的正确方法。有些情况下夏令时从一天的开始开始,所以一天的第一时刻是 01:00:00。 Java 知道这一点,因此 atStartOfDay
方法将为您提供当天的正确时刻。
并且无论使用过时的还是现代的 类 始终以 region/city 格式指定时区,例如 Europe/Paris 或 Europe/Malta。三、四和五个字母的时区缩写通常含糊不清,通常不是真正的时区,因此不可依赖。