最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - ZonedDateTime produces incorrect date at EuropeParis vs GMT+1 - Stack Overflow

programmeradmin0浏览0评论

I am in +07:00 timezone.

Given this:

ZonedDateTime.of( LocalDateTime.of( 1899, 12, 31, 23, 9, 20, 0 ), ZoneOffset.UTC )
.withZoneSameInstant( ZoneId.of( "Europe/Paris" ) )

Would produce: 1899-12-31T23:18:41+00:09:21[Europe/Paris]

In the other hand:

ZonedDateTime.of( LocalDateTime.of( 1899, 12, 31, 23, 9, 20, 0 ), ZoneOffset.UTC )
.withZoneSameInstant( ZoneId.of( "GMT+1" ) )

Would produce: 1900-01-01T00:09:20+01:00[GMT+01:00]

Why they are different, since both ZoneId.of( "Europe/Paris" ).getRules().getOffset( LocalDateTime.now() ) and ZoneId.of( "GMT+1" ).getRules().getOffset( LocalDateTime.now() ) produce +01:00? I expect the fist attempt should give 1900-01-01T00:09:20+01:00[Europe/Paris]. BTW, where are the +00:09:21 at the first try come?

I am in +07:00 timezone.

Given this:

ZonedDateTime.of( LocalDateTime.of( 1899, 12, 31, 23, 9, 20, 0 ), ZoneOffset.UTC )
.withZoneSameInstant( ZoneId.of( "Europe/Paris" ) )

Would produce: 1899-12-31T23:18:41+00:09:21[Europe/Paris]

In the other hand:

ZonedDateTime.of( LocalDateTime.of( 1899, 12, 31, 23, 9, 20, 0 ), ZoneOffset.UTC )
.withZoneSameInstant( ZoneId.of( "GMT+1" ) )

Would produce: 1900-01-01T00:09:20+01:00[GMT+01:00]

Why they are different, since both ZoneId.of( "Europe/Paris" ).getRules().getOffset( LocalDateTime.now() ) and ZoneId.of( "GMT+1" ).getRules().getOffset( LocalDateTime.now() ) produce +01:00? I expect the fist attempt should give 1900-01-01T00:09:20+01:00[Europe/Paris]. BTW, where are the +00:09:21 at the first try come?

Share Improve this question edited 8 hours ago Mark Rotteveel 109k226 gold badges155 silver badges219 bronze badges asked yesterday SoTSoT 1,2131 gold badge20 silver badges53 bronze badges 3
  • 6 en.wikipedia.org/wiki/Time_in_France#History – Sören Commented yesterday
  • 5 Your problem stems from the fact that you seem to think that Europe/Paris and GMT+1 are synonyms. I'm not sure why you do – g00se Commented yesterday
  • 6 why ....getOffset( LocalDateTime.now() ), isn't the date you are testing 1899-12-31 ? Maybe ZoneId.of( "Europe/Paris" ).getRules().getOffset( LocalDateTime.of(1899,12,31,23,9,20) ) wiould be more relevant (time zones changed over the time) – user85421 Commented yesterday
Add a comment  | 

2 Answers 2

Reset to default 12

You are correct that Europe/Paris and GMT+1 have the same offset now (2025-02-09), but they didn't have the same offset back in 1899.

Let's print out their offsets at the Instant that you are actually interested in,

var zdt = ZonedDateTime.of( LocalDateTime.of(1899, 12, 31, 23, 9, 20, 0), ZoneOffset.UTC);
System.out.println(ZoneId.of("Europe/Paris").getRules().getOffset(zdt.toInstant()));
System.out.println(ZoneId.of("GMT+1").getRules().getOffset(zdt.toInstant()));

This prints

+00:09:21
+01:00

That's where +00:09:21 came from. Paris did not standardise their time zones until 1911. It is 9 minutes and 21 seconds ahead of GMT because Paris is just east of Greenwich by that much time. That is offset from GMT of the local mean time of Paris.

The timezone offset of Europe/Paris changed a lot throughout history. See Wikipedia for more details. Or, you can just print

System.out.println(ZoneId.of("Europe/Paris").getRules().getTransitions());

The Answer by Sweeper is correct and smart. I’ll just add some thoughts.

Time zones tracking is very new

As suggested in that other Answer, let's look at the history of changes to the offset used by the people of the Paris region, as decided by their politicians.

ZoneId
        .of ( "Europe/Paris" )
        .getRules ( )
        .getTransitions ( )
        .forEach (
                ( ZoneOffsetTransition rule ) -> System.out.println ( rule.toString ( ) )
        );
Transition[Overlap at 1911-03-11T00:00+00:09:21 to Z]
Transition[Gap at 1916-06-14T23:00Z to +01:00]
Transition[Overlap at 1916-10-02T00:00+01:00 to Z]
Transition[Gap at 1917-03-24T23:00Z to +01:00]
Transition[Overlap at 1917-10-08T00:00+01:00 to Z]
Transition[Gap at 1918-03-09T23:00Z to +01:00]
Transition[Overlap at 1918-10-07T00:00+01:00 to Z]
Transition[Gap at 1919-03-01T23:00Z to +01:00]
Transition[Overlap at 1919-10-06T00:00+01:00 to Z]
Transition[Gap at 1920-02-14T23:00Z to +01:00]
Transition[Overlap at 1920-10-24T00:00+01:00 to Z]
Transition[Gap at 1921-03-14T23:00Z to +01:00]
Transition[Overlap at 1921-10-26T00:00+01:00 to Z]
Transition[Gap at 1922-03-25T23:00Z to +01:00]
Transition[Overlap at 1922-10-08T00:00+01:00 to Z]
Transition[Gap at 1923-05-26T23:00Z to +01:00]
Transition[Overlap at 1923-10-07T00:00+01:00 to Z]
Transition[Gap at 1924-03-29T23:00Z to +01:00]
Transition[Overlap at 1924-10-05T00:00+01:00 to Z]
Transition[Gap at 1925-04-04T23:00Z to +01:00]
Transition[Overlap at 1925-10-04T00:00+01:00 to Z]
Transition[Gap at 1926-04-17T23:00Z to +01:00]
Transition[Overlap at 1926-10-03T00:00+01:00 to Z]
Transition[Gap at 1927-04-09T23:00Z to +01:00]
Transition[Overlap at 1927-10-02T00:00+01:00 to Z]
Transition[Gap at 1928-04-14T23:00Z to +01:00]
Transition[Overlap at 1928-10-07T00:00+01:00 to Z]
Transition[Gap at 1929-04-20T23:00Z to +01:00]
Transition[Overlap at 1929-10-06T00:00+01:00 to Z]
Transition[Gap at 1930-04-12T23:00Z to +01:00]
Transition[Overlap at 1930-10-05T00:00+01:00 to Z]
Transition[Gap at 1931-04-18T23:00Z to +01:00]
Transition[Overlap at 1931-10-04T00:00+01:00 to Z]
Transition[Gap at 1932-04-02T23:00Z to +01:00]
Transition[Overlap at 1932-10-02T00:00+01:00 to Z]
Transition[Gap at 1933-03-25T23:00Z to +01:00]
Transition[Overlap at 1933-10-08T00:00+01:00 to Z]
Transition[Gap at 1934-04-07T23:00Z to +01:00]
Transition[Overlap at 1934-10-07T00:00+01:00 to Z]
Transition[Gap at 1935-03-30T23:00Z to +01:00]
Transition[Overlap at 1935-10-06T00:00+01:00 to Z]
Transition[Gap at 1936-04-18T23:00Z to +01:00]
Transition[Overlap at 1936-10-04T00:00+01:00 to Z]
Transition[Gap at 1937-04-03T23:00Z to +01:00]
Transition[Overlap at 1937-10-03T00:00+01:00 to Z]
Transition[Gap at 1938-03-26T23:00Z to +01:00]
Transition[Overlap at 1938-10-02T00:00+01:00 to Z]
Transition[Gap at 1939-04-15T23:00Z to +01:00]
Transition[Overlap at 1939-11-19T00:00+01:00 to Z]
Transition[Gap at 1940-02-25T02:00Z to +01:00]
Transition[Gap at 1940-06-14T23:00+01:00 to +02:00]
Transition[Overlap at 1942-11-02T03:00+02:00 to +01:00]
Transition[Gap at 1943-03-29T02:00+01:00 to +02:00]
Transition[Overlap at 1943-10-04T03:00+02:00 to +01:00]
Transition[Gap at 1944-04-03T02:00+01:00 to +02:00]
Transition[Overlap at 1944-10-08T01:00+02:00 to +01:00]
Transition[Gap at 1945-04-02T02:00+01:00 to +02:00]
Transition[Overlap at 1945-09-16T03:00+02:00 to +01:00]
Transition[Gap at 1976-03-28T01:00+01:00 to +02:00]
Transition[Overlap at 1976-09-26T01:00+02:00 to +01:00]
Transition[Gap at 1977-04-03T02:00+01:00 to +02:00]
Transition[Overlap at 1977-09-25T03:00+02:00 to +01:00]
Transition[Gap at 1978-04-02T02:00+01:00 to +02:00]
Transition[Overlap at 1978-10-01T03:00+02:00 to +01:00]
Transition[Gap at 1979-04-01T02:00+01:00 to +02:00]
Transition[Overlap at 1979-09-30T03:00+02:00 to +01:00]
Transition[Gap at 1980-04-06T02:00+01:00 to +02:00]
Transition[Overlap at 1980-09-28T03:00+02:00 to +01:00]
Transition[Gap at 1981-03-29T02:00+01:00 to +02:00]
Transition[Overlap at 1981-09-27T03:00+02:00 to +01:00]
Transition[Gap at 1982-03-28T02:00+01:00 to +02:00]
Transition[Overlap at 1982-09-26T03:00+02:00 to +01:00]
Transition[Gap at 1983-03-27T02:00+01:00 to +02:00]
Transition[Overlap at 1983-09-25T03:00+02:00 to +01:00]
Transition[Gap at 1984-03-25T02:00+01:00 to +02:00]
Transition[Overlap at 1984-09-30T03:00+02:00 to +01:00]
Transition[Gap at 1985-03-31T02:00+01:00 to +02:00]
Transition[Overlap at 1985-09-29T03:00+02:00 to +01:00]
Transition[Gap at 1986-03-30T02:00+01:00 to +02:00]
Transition[Overlap at 1986-09-28T03:00+02:00 to +01:00]
Transition[Gap at 1987-03-29T02:00+01:00 to +02:00]
Transition[Overlap at 1987-09-27T03:00+02:00 to +01:00]
Transition[Gap at 1988-03-27T02:00+01:00 to +02:00]
Transition[Overlap at 1988-09-25T03:00+02:00 to +01:00]
Transition[Gap at 1989-03-26T02:00+01:00 to +02:00]
Transition[Overlap at 1989-09-24T03:00+02:00 to +01:00]
Transition[Gap at 1990-03-25T02:00+01:00 to +02:00]
Transition[Overlap at 1990-09-30T03:00+02:00 to +01:00]
Transition[Gap at 1991-03-31T02:00+01:00 to +02:00]
Transition[Overlap at 1991-09-29T03:00+02:00 to +01:00]
Transition[Gap at 1992-03-29T02:00+01:00 to +02:00]
Transition[Overlap at 1992-09-27T03:00+02:00 to +01:00]
Transition[Gap at 1993-03-28T02:00+01:00 to +02:00]
Transition[Overlap at 1993-09-26T03:00+02:00 to +01:00]
Transition[Gap at 1994-03-27T02:00+01:00 to +02:00]
Transition[Overlap at 1994-09-25T03:00+02:00 to +01:00]
Transition[Gap at 1995-03-26T02:00+01:00 to +02:00]
Transition[Overlap at 1995-09-24T03:00+02:00 to +01:00]
Transition[Gap at 1996-03-31T02:00+01:00 to +02:00]
Transition[Overlap at 1996-10-27T03:00+02:00 to +01:00]
Transition[Gap at 1997-03-30T02:00+01:00 to +02:00]
Transition[Overlap at 1997-10-26T03:00+02:00 to +01:00]

We find that Paris region has used four offsets at various times:

  • +00:09:21
  • +00:00
  • +01:00
  • +02:00

Your assumption that Paris time is always using an offset of +01:00 (GMT+1) is incorrect.

We see that time zone tracking for this region is only tracked back to 1911. Before the era of railroads, there was little need to devise a “zone” of time. The only time that mattered was the obvious natural time: Noon is when the sun is directly overhead, wherever you happen to be standing. Noon arrives earlier in the east before it does in the west.

As noted in Wikipedia, in 1891 time was unified in Metropolitan France by way of the Paris Meridian running through the Paris Observatory in Paris, France. That meridian line happens to be ≈ 2°20′ east of the Greenwich meridian used by our contemporary definition of time zones using UTC. That longitudinal distance of 2°20′ means the sun is directly overhead of Parisians about 9 minutes and 21 seconds before the sun arrives directly overhead of Londoners. And so the first French offset for Paris was defined as +00:00 ahead of the Paris Meridian. Nowadays our modern official time zone definition for that era for Europe/Paris is +00:09:21 ahead of UTC meridian to account for the 2°20′ difference. That explains the offset seen in your results.

All of which is a long-winded way of saying that trying to use time zones outside of contemporary times makes little to no sense:

  • Firstly because time zones are a new invention, dating only back to the late 1800s.
  • Secondly, because historically time zones were never tracked! Our record of them is faulty and incomplete.

Shockingly, despite all our many universities and governmental agencies, nobody bothered identifying the time zones, and tracking the changes to their offsets. Only in our (grey-haired) lifetime did Arthur David Olson heroically undertake a formal database of time zones, with responsibility passing to Paul Eggert in 2005. Their efforts have generally been focused on 1970 and later, with edits, additions, corrections, and changes happening multiple times a year to what we now call tzdata.

So your attempt at precisely representing a moment in 1899 is unwise.

OffsetDateTime

Let’s breakdown your code.

ZonedDateTime
.of( 
    LocalDateTime.of( 1899, 12, 31, 23, 9, 20, 0 ), 
    ZoneOffset.UTC 
)
.withZoneSameInstant( 
    ZoneId.of( "Europe/Paris" ) 
)

Though not critical, your use of ZonedDateTime in the first part is misleading. You are assigning a mere offset rather than a time zone. So you should have used OffsetDateTime.

OffsetDateTime
.of( 
    LocalDateTime.of( 1899, 12, 31, 23, 9, 20, 0 ), 
    ZoneOffset.UTC 
)                                   // Returns an `OffsetDateTime` object. 
.atZoneSameInstant( 
    ZoneId.of( "Europe/Paris" )    
)                                   // Returns a `ZonedDateTime` object. 

But, as discussed above, using OffsetDateTime or ZonedDateTime for historical moments is generally unwise. Their use suggests a precision and authoritativeness that is not possible, and likely unnecessary.

Definitions:

  • An offset is merely a number hours-minutes-seconds ahead/behind a temporal meridian. As discussed above, in modern protocols such as ISO 8601 we commonly use the meridian of UTC, while some protocols use the Paris Meridian (or others). And ISO 8601 and other common protocols use + to mean ahead of the meridian, and - to mean behind the meridian, though beware some protocols do the opposite.
  • A time zone is named history of the past, present, and future changes to the offset used by the people of a particular region, as decided by their politicians. Modern time zones are named in a format of Continent/Region such Europe/London, Europe/Paris, America/Edmonton, Pacific/Auckland, etc.
发布评论

评论列表(0)

  1. 暂无评论