Java 8个时区转换

Java 8 timezone conversion

我知道有类似的问题,但我不太能找到与我的相似的例子。我了解了 LocalDateTimeZonedDateTime 但我不知道如何告诉我的 ZonedDateTime 什么是解析日期的假定时区。

我正在从 Java 7 迁移到 Java 8。在我的代码中,我有一个这样的方法:

public String changeTZ(String tzFrom, String tzTo, String dateToChange) {
    ZoneId zoneFrom = ZoneId.of(tzFrom);
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat.toPattern());
    LocalDateTime localtDateAndTime = LocalDateTime.parse(dateToChange, formatter);
    ZonedDateTime dateAndTimeINeed = ZonedDateTime.of(localtDateAndTime, zoneFrom );

    ZonedDateTime rezDate = dateAndTimeINeed.withZoneSameInstant(ZoneId.of(tzTo));

    return formatter.format(rezDate);
}

示例用法是:

String rez = changeTZ("CEST", "UTC", "2017-08-10 14:23:58");

如您所见,它以字符串形式接收日期时间,时区日期在 tzFrom 变量中,我需要 TZ(toTo 变量)。

在 Java 7 中执行此操作的代码既荒谬又复杂,因此我不会在此处列出。你能告诉我如何在不改变方法接口(参数和 return 类型)的情况下在 Java 8 中实现相同的目标吗?夏令时呢?是在Java8自动处理吗?

请注意,该方法还支持 "CEST"、"CET"、"UTC" 形式的时区以及 "Europe/London".

等标准时区

此解决方案使用 Hugo 在评论中提到的 IANA 时区名称来获取 ZoneId(更多详细信息 here)。如果将它与 CEST.

一起使用,将抛出异常
public static String changeTZ(String tzFrom, String tzTo, String dateToChange){
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of(tzFrom));
    ZonedDateTime zdt = ZonedDateTime.parse(dateToChange, dtf);
    DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of(tzTo));  
    return zdt.format(dtf2);
}

像这样使用:

String rez = changeTZ("US/Alaska", "Europe/Berlin", "2017-08-10 14:23:58");

Java 8 使用 IANA timezones names(格式总是 Region/City,如 America/Sao_PauloEurope/Berlin)。 避免使用短缩写(如 CESTPST),因为它们是 ambiguous and not standard.

实际上,这些名称不适用于 ZoneId,主要是因为这种歧义(例如,CST 可以是 "Central Standard Time"、"Cuba Standard Time" 或 "China Standard Time") . 实际上,由于追溯兼容性的原因,其中一些可能会起作用,但不能保证对所有它们都起作用。

我假设 CEST 是 Central European Summer Time。目前有许多不同的国家(和时区)处于 CEST,因此 API 无法决定选择哪个时区,如果您只是将 "CEST" 传递给它。

那是因为一个时区包含一个地区在其历史上所有不同的偏移量。今天可能有很多国家/地区使用 CEST,但它们过去的历史不同(有些国家可能在不同年份使用 DST,或者使用不同的偏移量然后更改等),这就是为什么它们每个都有一个时区。

要使用这样的短名称(如 CEST),您可以为每个名称定义一些默认值(这将是一个任意选择)并将这些选择放在一个映射中:

// map of custom zone names
Map<String, String> map = new HashMap<>();
// setting my arbitrary choices for each name
map.put("CEST", "Europe/Berlin"); // Berlin during DST period
map.put("CET", "Europe/Berlin"); // Berlin during non-DST period
// ... and so on

然后您可以使用此地图创建 ZoneId:

// use the custom map to create the ZoneId
ZoneId zoneFrom = ZoneId.of(tzFrom, map);
...
// use the custom map to create the ZoneId
ZonedDateTime rezDate = dateAndTimeINeed.withZoneSameInstant(ZoneId.of(tzTo, map));

我选择了Europe/Berlin,当然您可以将其更改为您需要的任何时区。您可以通过调用 ZoneId.getAvailableZoneIds().

获取可用时区列表(并选择最适合您的系统的时区)

使用上面的地图:

System.out.println(changeTZ("CEST", "UTC", "2017-08-10 14:23:58"));

此代码输出:

2017-08-10 12:23:58

请注意 CEST 中的 14:23(我选择 Europe/Berlin)在 UTC 中是 12:23,这是正确的,因为在 8 月 柏林采用夏令时(偏移量为 +02:00)。

ZoneIdZonedDateTime 类 自动处理 DST 效果。您可以通过选择一月份的日期(当 DST 在柏林不生效时)来检查:

// January is not DST, so use CET
System.out.println(changeTZ("CET", "UTC", "2017-01-10 14:23:58"));

输出为:

2017-01-10 13:23:58

一月柏林不在夏令时,所以偏移量是+01:00,然后柏林的14:23变成UTC的13:23。


当然理想的是始终使用全名(如 Europe/Berlin),但如果您无法控制输入,自定义地图是一种替代方法。

Java 8 也有一个 built-in predefined map,但与任何其他预定义的东西一样,选择是任意的,不一定是你需要的。

这里有一个 class 可以完成这项工作。

public class TimeZoneConverter {

    private static String datePattern = "yyyy-MM-dd HH:mm:ss";

    public static void main(String[] args) throws ParseException {
        String sourceDate = "2017-02-27 16:00:00";
        String resultDate = convertTimeZone("EET", "UTC", sourceDate);

        System.out.println("EET: "+ sourceDate);
        System.out.println("UTC: "+ resultDate);
    }

    public static String convertTimeZone(String timeZoneFrom, String timeZoneTo, String date) throws ParseException {
        long timestamp = stringToTimestamp(date, timeZoneFrom);
        String result = timestampToString(timestamp, timeZoneTo);
        return result;
    }

    public static long stringToTimestamp(String time, String timeZone) throws ParseException {
        DateFormat format = getDateFormat(timeZone);
        return format.parse(time).getTime();
    }

    public static String timestampToString(long timestamp, String timeZone) {
        DateFormat format = getDateFormat(timeZone);
        return format.format(new Date(timestamp));
    }

    private static DateFormat getDateFormat(String timeZone) {
        DateFormat format = new SimpleDateFormat(datePattern);
        format.setTimeZone(TimeZone.getTimeZone(timeZone));
        return format;
    }

}