java.util.Date 到 java.util.Calendar 奇怪的时区变化

问题描述

我想将 java.util.Date 对象设置为 java.util.Calendar 对象以更改 Date 对象的年份,如下所示:

TimezoneId 等于“Europe/Istanbul”。年份是 2018。

日期对象为 1969-12-31 22:00:00.0 (1969-12-31T22:00:00.000Z),区域信息如下:

"sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]"

欧洲/伊斯坦布尔时区是 UTC+3,但是当我用这个日期对象运行代码时,新的 GregorianCalendar 将日期对象的时间更改为 -1 小时。有些地方工作不正常,我找不到为什么这不将 UTC 转换为 UTC+3 .Returned Date 对象等于 Sun Dec 31 21:00:00 UTC 2017.UTC-1 对于欧洲/伊斯坦布尔不正确。我该如何解决这个问题?

public static Date setYearOfDate(Date date,int year,String timeZoneId)
    {
        Calendar customCalendar = new GregorianCalendar(TimeZone.getTimeZone(timeZoneId));
        customCalendar.setTime(date);
        customCalendar.set(Calendar.YEAR,year);

        return customCalendar.getTime();
    }

注意:customCalendar zoneinfo 如下: "sun.util.calendar.ZoneInfo[id="Europe/Istanbul",offset=10800000,transitions=130,lastRule=null]"

解决方法

java.time

我建议您使用 java.time API。

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

class Main {
    public static void main(String[] args) {
        String dateTimeString = "1969-12-31T22:00:00.000Z";
        System.out.println(setYearOfDate(dateTimeString,2018,"Europe/Istanbul"));
    }

    public static String setYearOfDate(String dateTimeString,int year,String zoneId) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX",Locale.ENGLISH)
                                                    .withZone(ZoneId.of(zoneId));

        ZonedDateTime zdt = ZonedDateTime.parse(dateTimeString);
        zdt = zdt.withYear(year);

        return dtf.format(zdt);
    }
}

输出:

2019-01-01T01:00:00.000+03

java.util 日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用它们并切换到 modern date-time API

SimpleDateFormat错误行为的一个很好的例子如下所示:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;

class Main {
    public static void main(String[] args) throws ParseException {
        String dateTimeString = "1969-12-31T22:00:00.000Z";
        String timeZoneId = "Europe/Istanbul";
        System.out.println(setYearOfDate(dateTimeString,timeZoneId));
    }

    public static String setYearOfDate(String dateTimeString,String timeZoneId) throws ParseException {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX",Locale.ENGLISH);
        calendar.setTime(sdf.parse(dateTimeString));
        calendar.set(Calendar.YEAR,year);
        sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));
        return sdf.format(calendar.getTime());
    }
}

输出:

2019-01-01T02:00:00.000+03

您还将在 1968 年和 1970 年观察到 SimpleDateFormat 的这种行为。但是,如果您将输入字符串中的年份更改为其他年份,它将以预期的方式运行,例如相同的代码将为输入字符串 2019-01-01T01:00:00.000+03 输出 1979-12-31T22:00:00.000Z

,

我认为@Live_and_Let_Live 的答案是正确的,要打印 Date,您需要将 TimeZone 提供给 SimpleDateFotmat,实际上您不需要使用 SimpleDateFotmat设置年份;使用您的代码并正确应用 TimeZone,您将获得预期的结果:

public static void main (String[] args) throws java.text.ParseException
{
    String initialDate = "1969-12-31T22:00:00.000Z";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
    
    Date startDate = sdf.parse(initialDate);
            
    System.out.println("Start date: " + startDate);
    System.out.println("Start date formatted: " + sdf.format(startDate));
    
    Date resultDate = setYearOfDate(startDate,"Europe/Istanbul");
    System.out.println("Result date: " + resultDate);
    System.out.println("Result date formatted: " + sdf.format(resultDate));
        
    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
    sdf2.setTimeZone(TimeZone.getTimeZone("Europe/Istanbul"));
    System.out.println("Result date formatted with TimeZone: " + sdf2.format(resultDate));
}

输出:

Start date: Wed Dec 31 22:00:00 GMT 1969
Start date formatted: 1969-12-31T22:00:00.000Z
Result date: Sun Dec 31 21:00:00 GMT 2017
Result date formatted: 2017-12-31T21:00:00.000Z
Result date formatted with TimeZone: 2018-01-01T00:00:00.000+03

编辑: 这是 Java 8 日期和时间 API 替代方案:

public static void main(String[] args) {
    String initialDate = "1969-12-31T22:00:00.000Z";
    ZoneId zone = ZoneId.of("Europe/Istanbul");
    DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(zone);
    
    ZonedDateTime startDate = ZonedDateTime.parse(initialDate,formatter);

    System.out.println("Start date: " + startDate);
    System.out.println("Start date formatted with TimeZone: " + formatter.format(startDate));

    ZonedDateTime resultDate = setYearOfDate(startDate,"Europe/Istanbul");
    System.out.println("Result date: " + resultDate);
    System.out.println("Result date formatted with TimeZone: " + formatter.format(resultDate));
}

static ZonedDateTime setYearOfDate(ZonedDateTime date,String timeZoneId)
{
    GregorianCalendar calendar = GregorianCalendar.from(date);
    calendar.set(GregorianCalendar.YEAR,year); 
    return calendar.toZonedDateTime();
}

输出:

Start date: 1970-01-01T00:00+02:00[Europe/Istanbul]
Start date formatted with TimeZone: 1970-01-01T00:00:00+02:00[Europe/Istanbul]
Result date: 2018-01-01T00:00+03:00[Europe/Istanbul]
Result date formatted with TimeZone: 2018-01-01T00:00:00+03:00[Europe/Istanbul]