将UTC时间转换为带有夏令时的本地时间(Java中)

huangapple go评论63阅读模式
英文:

Converting UTC to Local Time with daylight saving in Java

问题

以下是您要翻译的代码部分:

我正在尝试将UTC时间转换为本地时间包括夏令时
夏季时斯德哥尔摩的本地时间应比UTC提前2小时但在Java中转换时只增加了1小时

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
英文:

I am trying to convert UTC time to local time, including daylight saving.
Localtime (Stockholm), in summer, should be 2 hours ahead of UTC, but when I convert it in Java it only adds one hour.

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);
    }
}

Output:

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

答案1

得分: 6

tl;dr

你的代码无意中使用了1970-01-01T02:36:10Z,即1970年第一天的上午2点36分10秒,UTC时间下的时刻。然后你将其调整到斯德哥尔摩时间,但1970年时没有使用夏令时(DST)。因此,你得到的是+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[Europe/Stockholm]

我们看到了你预期的2小时偏移。

避免使用过时的日期时间类

你正在使用已经多年前被现代 java.time 类(JSR 310中定义)所取代的可怕的日期时间类。

在UTC中使用时刻没有意义

仅仅在UTC中询问一个时刻是没有意义的。要确定一个时刻,表示时间轴上的一个点,你需要同时有日期和时间。

在文本中表示时间时,请使用标准的ISO 8601格式。对于一天中的时间,这意味着简单地使用24小时制并补零。

LocalTime lt = LocalTime.parse("02:36:10");

LocalDateOffsetDateTime

使用日期来确定一个时刻。也许你打算使用当前日期,以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);

生成标准的ISO 8601格式的文本。

String output = zdtStockholm.toString();

>2023-05-06T04:36:10+02:00[Europe/Stockholm]

这里我们看到了你预期的两小时偏移。

你的代码使用了1970年

你的代码失败是因为你的 java.util.Date 对象表示的是1970年第一天的上午2点,UTC时间下的时刻。

避免调用 java.util.Date#toString。这个方法不幸地在生成文本时使用了JVM当前的默认时区,这个设计决策让事情变得混淆。

为了方便起见,我们将你的过时的 java.util.Date 对象转换为它的现代替代品,即 java.time.Instant 对象,它们都表示与UTC偏移为零小时-分钟-秒的时刻。幸运的是,Instant#toString 说的是实话,不像 Date#toString

System.out.println(new SimpleDateFormat("hh:mm:ss a").parse("2:36:10 AM").toInstant());

>1970-01-01T10:36:10Z

让我们看一看瑞典在那一刻的时区规则。

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 = false

所以在1970年的那个瑞典时区,没有夏令时(DST)生效。

rules.getTransitions().toString()

在那个过渡列表中,我们可以看到在1949-10-02和1980-04-06之间的偏移是+01:00,而不是你预期的+02:00。

英文:

tl;dr

Your code inadvertently used the moment of 1970-01-01T02:36:10Z, 2 AM on the first day of 1970 as seen in UTC. Then you adjusted to Stockholm time, still in 1970 when no DST was in effect. Thus an offset of one hour (+01:00) rather than the two hours (+02:00) that you expected.

Use modern java.time classes instead.

OffsetDateTime                                     // Represent a date, a time-of-day, and an offset-from-UTC.
        .of(                                       // Static factory method.
                LocalDate.now( ZoneOffset.UTC ) ,  // Date
                LocalTime.parse( "02:36:10" ) ,    // Time-of-day
                ZoneOffset.UTC                     // A constant representing an offset from UTC of zero hours-minutes-seconds.
        )                                          // Returns an `OffsetDateTime` object.
        .atZoneSameInstant(                        // Adjust to a specific time zone. Same moment, different wall-clock time/calendar.
                ZoneId.of( "Europe/Stockholm" )
        )                                          // Returns a `ZonedDateTime` object.
        .toString()                                // Returns a `String` object containing text in standard ISO 8601 format wisely extended by appending the name of the time zone in square brackets.

>2023-05-06T04:36:10+02:00[Europe/Stockholm]

We see your 2 hour offset, as expected.

Avoid legacy date-time classes

You are using terrible date-time classes that are now legacy, supplanted years ago by the modern java.time classes defined in JSR 310.

Time-of-day in UTC makes no sense

Asking about a time of day alone in UTC makes no sense. You need a date as well as a time to determine a moment, to represent a point on the timeline.

When communicating data textually to represent a time, use standard ISO 8601 format. For a time of day that means simply 24-hour clock with padding zeros.

LocalTime lt = LocalTime.parse( "02:36:10" ) ;

LocalDate & OffsetDateTime

Apply a date to determine a moment. Perhaps you intended the current date as seen in UTC.

LocalDate todayUtc = LocalDate.now( ZoneOffset.UTC ) ;
OffsetDateTime odt = OffsetDateTime.of( todayUtc , lt , ZoneOffset.UTC ) ;

ZonedDateTime

Adjust to a time zone. Same moment, different wall-clock time & calendar.

ZoneId zStockholm = ZoneId.of( "Europe/Stockholm" ) ;
ZonedDateTime zdtStockholm = odt.atZoneSameInstant( zStockholm ) ;

Generate text in standard ISO 8601 format.

String output = zdtStockholm.toString() ;

>2023-05-06T04:36:10+02:00[Europe/Stockholm]

There we see the two-hour offset you expected.

Your code uses 1970

Your code failed because your java.util.Date object represents 2 AM on the first day of 1970 as seen in UTC.

Avoid calling java.util.Date#toString. That method unfortunately applies the JVM‘s current default time zone while generating its text. That poor design decision muddies the water.

For convenience, we convert your legacy java.util.Date object to its modern replacement, a java.time.Instant object — both represent a moment as seen with an offset from UTC of zero hours-minutes-seconds. Fortunately, Instant#toString tells the truth, unlike Date#toString.

System.out.println( new SimpleDateFormat( "hh:mm:ss a" ).parse( "2:36:10 AM" ).toInstant() );

>1970-01-01T10:36:10Z

Let's look at the time zone rules for Sweden for that moment.

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 = false

So there was 👉 no Daylight Saving Time (DST) in effect in that Sweden time zone at that moment in 1970.

rules.getTransitions().toString()

We can see in that list of transitions that between 1949-10-02 and 1980-04-06 the offset was +01:00. So no +02:00 as you expected.

答案2

得分: 1

java.time 是比旧的包/类更好的选择。

看看这个示例,它将今天作为日期,从UTC转换为Europe/Stockholm:

public static void main(String[] args) {
	// 输入时间作为字符串
	String someTime = "2:36:10 AM";
	// 为这些时间字符串创建一个格式化器
	DateTimeFormatter dtf = DateTimeFormatter.ofPattern("h:m:s a", Locale.ENGLISH);
	// 解析时间(只有时间,没有日期或区域涉及到)
	LocalTime localTime = LocalTime.parse(someTime, dtf);
	// 涉及日期(这里是今天)
	LocalDateTime localDateTime = LocalDateTime.of(LocalDate.now(), localTime);
	// 创建一个涉及的区域
	ZoneId stockholm = ZoneId.of("Europe/Stockholm");
	// 组合(先前组合的)日期和时间以及区域
	ZonedDateTime utcTime = ZonedDateTime.of(localDateTime, ZoneOffset.UTC);
	// 然后使用该时刻,并在不同的区域(这里是UTC)中表示它
	ZonedDateTime stockholmTime = utcTime.withZoneSameInstant(stockholm);
	// 打印两个区域日期时间
	System.out.println("UTC 时间:   " + utcTime);
	System.out.println("本地时间: " + stockholmTime);
}

输出:

UTC 时间:   2023-05-07T02:36:10Z
本地时间: 2023-05-07T04:36:10+02:00[Europe/Stockholm]
英文:

java.time is a better choice than older packages/classes.

See this example, which takes today as date and converts from UTC to Europe/Stockholm:

public static void main(String[] args) {
	// input 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);
}

Output:

UTC time:   2023-05-07T02:36:10Z
local time: 2023-05-07T04:36:10+02:00[Europe/Stockholm]

答案3

得分: 1

这解答了提问者的问题(为什么会发生这种情况),正如其他人已经建议使用现代日期/时间类一样。

SimpleDateFormat#parse(...) 的定义包括:

public Date parse​(String text, ParsePosition pos) 解析文本以生成日期。

该方法尝试从由 pos 给出的索引开始解析文本。

...

此解析操作使用日历来生成日期。在解析之前,所有日历的日期时间字段都会被清除,并且如果解析操作中缺少日期时间信息,则会使用日历的日期时间字段的默认值。例如,如果没有从解析操作中提供年份值,则解析的日期的年份值将为 1970(如果使用 GregorianCalendar)。根据给定的模式和文本中的时区值,可能会覆盖 TimeZone 值。如果以前调用过 setTimeZone 来设置了 TimeZone 值,可能需要将任何先前设置的 TimeZone 值恢复为进行进一步操作。

1970 年,斯德哥尔摩不遵守夏令时,因此为 +01:00。

英文:

This answers the OP's question (why is this happening), as others have already recommended to use the modern date/time classes.

The definition of SimpleDateFormat#parse(...) includes:

> public Date parse​(String text,
> ParsePosition pos)
>Parses text from a string to produce a Date.
>
>The method attempts to parse text starting at the index given by pos.
>
> ...
>
>This parsing operation uses the calendar to produce a Date. All of the calendar's date-time fields are cleared before parsing, and the calendar's default values of the date-time fields are used for any missing date-time information. For example, the year value of the parsed Date is 1970 with GregorianCalendar if no year value is given from the parsing operation. The TimeZone value may be overwritten, depending on the given pattern and the time zone value in text. Any TimeZone value that has previously been set by a call to setTimeZone may need to be restored for further operations.

In 1970 Stockholm did not observer DST and was therefore +01:00

答案4

得分: -3

Maybe you have not updated the IANA time zones database for your Java version, read this go to the index on the page for java time.
也许你还没有更新你的Java版本的IANA时区数据库,阅读此内容并转到Java时间页面的索引。

And that's also a truth Calendar and TimeZone in java.util are thereabout completely deprecated since Sun/Oracle Java 1.8.0.
这也是事实,java.util中的Calendar和TimeZone自Sun/Oracle Java 1.8.0以来几乎已完全弃用。

Calendar TimeZones Offset has two different offsets "Raw" (geographic official iso) and actual, and are set in seconds +/-.
日历时区偏移有两种不同的偏移量,"原始"(地理官方ISO)和实际,以秒为单位设置为+/-。

Second, Calendar for most European purpose is actually the Gregorian subclass instance, Calendar itself is simply a top-level abstraction class.
其次,对于大多数欧洲用途来说,日历实际上是Gregorian子类的实例,日历本身只是一个顶级抽象类。

英文:

Maybe you have not updated the IANNA time zones database for your Java version, read this go to the index on the page for java time.(html page)
https://drive.google.com/file/d/1gjHmdC-BW0Q2vXiQYmp1rzPU497sybNy/view?usp=drivesdk

And that's also a truth Calendar and TimeZone in java.util are thereabout completely deprecated since Sun/Oracle Java 1.8.0
Calendar TimeZones Offset has two different offsets "Raw" (geographic official iso) and actual, and are set in seconds +/- .

Second, Calendar for most European purpose is actually the Gregorian sub class instance, Calendar itself is simply a top level abstraction class.

huangapple
  • 本文由 发表于 2023年5月7日 05:31:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76191242.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定