我正在尝试将 UTC 时间转换为本地时间,包括夏令时。 当地时间(斯德哥尔摩),在夏天,应该比 UTC 早 2 小时,但是当我用 Java 转换它时,它只增加了一个小时。
public class TimeConverter {
public static void main(String[] args) throws ParseException {
getLocalTime("2:36:10 AM");
}
public static String getLocalTime(String utcTime) throws ParseException {
DateFormat utc = new SimpleDateFormat("hh:mm:ss a");
utc.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = utc.parse(utcTime);
DateFormat local = new SimpleDateFormat("HH:mm:ss");
local.setTimeZone(TimeZone.getDefault());
System.out.println(utc.getTimeZone());
System.out.println(local.getTimeZone());
System.out.println("UTC TIME\t" + utcTime);
System.out.println("LOCAL TIME\t" + local.format(date));
return local.format(date);
}
}
输出:
sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
sun.util.calendar.ZoneInfo[id="Europe/Stockholm",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Europe/Stockholm,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
UTC TIME 2:36:10 AM
LOCAL TIME 03:36:10
您的代码无意中使用了 1970 年第一天凌晨 2 点 1970-01-01T02:36:10Z 的时刻,如 UTC 所示。然后你调整到斯德哥尔摩时间,仍然是 1970 年,当时没有夏令时。因此偏移一小时 (+01:00) 而不是您预期的两小时 (+02:00)。
使用现代 java.time 类代替。
OffsetDateTime
.of(
LocalDate.now( ZoneOffset.UTC ) ,
LocalTime.parse( "02:36:10" ) ,
ZoneOffset.UTC
)
.atZoneSameInstant(
ZoneId.of( "Europe/Stockholm" )
)
.toString()
2023-05-06T04:36:10+02:00[欧洲/斯德哥尔摩]
您正在使用糟糕的日期时间类,这些类现在已经成为遗留问题,多年前被 JSR 310 中定义的现代 java.time 类所取代。
在 UTC 中单独询问一天中的某个时间是没有意义的。你需要一个日期和一个时间来确定一个时刻,代表时间轴上的一个点。
当以文本方式传达数据以表示时间时,请使用标准 ISO 8601 格式。对于一天中的某个时间,这意味着只是带有填充零的 24 小时制。
LocalTime lt = LocalTime.parse( "02:36:10" ) ;
LocalDate
& OffsetDateTime
应用日期来确定时刻。也许您想要 UTC 中看到的当前日期。
LocalDate todayUtc = LocalDate.now( ZoneOffset.UTC ) ;
OffsetDateTime odt = OffsetDateTime.of( todayUtc , lt , ZoneOffset.UTC ) ;
ZonedDateTime
调整时区。同一时刻,不同的挂钟时间和日历。
ZoneId zStockholm = ZoneId.of( "Europe/Stockholm" ) ;
ZonedDateTime zdtStockholm = odt.atZoneSameInstant( zStockholm ) ;
您的代码失败,因为您的
java.util.Date
对象代表 1970 年第一天的凌晨 2 点,如 UTC 所示。
System.out.println( new SimpleDateFormat( "hh:mm:ss a" ).parse( "2:36:10 AM" ).toInstant() );
1970-01-01T10:36:10Z
让我们看看当时瑞典的时区规则。为方便起见,我们将您的遗留
java.util.Date
对象转换为其现代替代品,一个 java.time.Instant
对象——两者都代表一个时刻,与 UTC 的偏移量为零小时-分钟-秒。
Instant instant = new SimpleDateFormat( "hh:mm:ss a" ).parse( "2:36:10 AM" ).toInstant();
ZoneRules rules = ZoneId.of( "Europe/Stockholm" ).getRules();
boolean isDst = rules.isDaylightSavings( instant );
System.out.println( "isDst = " + isDst );
isDst = 假
因此,在 1970 年的那个时刻,瑞典时区没有有效的夏令时 (DST)。
rules.getTransitions().toString()
我们可以在 1949-10-02 和 1980-04-06 之间的转换列表中看到偏移量为 +01:00。所以没有你预期的+02:00。
java.time
是比旧包/类更好的选择。
看这个例子,它以 today 作为日期并从 UTC 转换为 Europe/Stockholm:
public static void main(String[] args) {
// inpute time as String
String someTime = "2:36:10 AM";
// create a formatter for those time Strings
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("h:m:s a", Locale.ENGLISH);
// parse the time (time only, no date or zone involved so far)
LocalTime localTime = LocalTime.parse(someTime, dtf);
// involve a date (today here)
LocalDateTime localDateTime = LocalDateTime.of(LocalDate.now(), localTime);
// create a zone to involve
ZoneId stockholm = ZoneId.of("Europe/Stockholm");
// compose the (previously composed) date and time, and the zone
ZonedDateTime utcTime = ZonedDateTime.of(localDateTime, ZoneOffset.UTC);
// then use that moment in time and express it in a different zone (UTC here)
ZonedDateTime stockholmTime = utcTime.withZoneSameInstant(stockholm);
// print both zoned datetimes
System.out.println("UTC time: " + utcTime);
System.out.println("local time: " + stockholmTime);
}
输出:
UTC time: 2023-05-07T02:36:10Z
local time: 2023-05-07T04:36:10+02:00[Europe/Stockholm]
这回答了 OP 的问题(为什么会这样),因为其他人已经建议使用现代日期/时间类。
SimpleDateFormat#parse(...)
的定义包括:
public Date parse(String text, ParsePosition pos)
解析字符串中的文本以生成日期。
该方法尝试从 pos 给定的索引开始解析文本
...
此解析操作使用日历来生成日期。在解析之前清除所有日历的日期时间字段,并且日历的日期时间字段的默认值用于任何丢失的日期时间信息。例如,如果解析操作中没有给出年份值,则解析日期的年份值为 1970 与 GregorianCalendar。 TimeZone 值可能会被覆盖,具体取决于给定的模式和文本中的时区值。之前通过调用 setTimeZone 设置的任何 TimeZone 值可能需要恢复以供进一步操作。
1970 年斯德哥尔摩没有观察员 DST,因此是 +01:00
也许您还没有为您的 Java 版本更新 IANNA 时区数据库,阅读这篇文章转到 java tine 页面上的索引。(html 页面) https://drive.google.com/file/d/1gjHmdC-BW0Q2vXiQYmp1rzPU497sybNy/view?usp=drivesdk