简体   繁体   中英

Timezone changes are not being persisted in Spring-Boot application

I am facing a problem with the timezone when I run a Springboot 2.3.8 application with Tomcat 9 on a "Windows Server 2016 Datacenter" machine. Running it locally with Eclipse or Tomcat 9 doesn't trigger the problem.

I set the timezone at the beggining using:

@PostConstruct
    public void init()
    {
        TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin"));

        Calendar now = Calendar.getInstance();
        TimeZone timeZone = now.getTimeZone();
        System.out.println(timeZone.getDisplayName());
    }

And that prints -> Central European Standard Time

However, later on when I call one of the endpoints and I check the timezone the same way as before

Calendar now = Calendar.getInstance();
TimeZone timeZone = now.getTimeZone();
System.out.println(timeZone.getDisplayName());

I get --> Coordinated Universal Time

I assume that either the timezone is not being really set on the @PostConstruct or something is overwritting it later on.

My question is, what could be doing the change and how could I fix it to have my timezone always at "Europe/Berlin".

I have tried changing the timezone inside the app as mentioned above, using java parameters in tomcat and changing the timezone of the machine itself, But in all cases the changes are overwritten and I get UTC when calling and endpoint.

Pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.8.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.test</groupId>
    <artifactId>project</artifactId>
    <name>project</name>
    <version>1.00</version>
    <packaging>war</packaging>

    <url>http://maven.apache.org</url>
    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.test.skip>true</maven.test.skip>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <!-- Master Versions -->
        <spring.boot.version>2.3.8.RELEASE</spring.boot.version>
        <oracle.version>12.2.0.1</oracle.version>
    </properties>

    <!-- Generates the Build Info/Properties -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>build-info</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <!-- Adds local jar to the final WAR -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <webResources>
                        <resource>
                            <directory>${project.basedir}/libraries/</directory>
                            <targetPath>WEB-INF/lib</targetPath>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- Spring-boot-starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-integration</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-ldap</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.1.1.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.5.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.unboundid</groupId>
            <artifactId>unboundid-ldapsdk</artifactId>
        </dependency>

        <!-- javax -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>1.4.7</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>javax.persistence-api</artifactId>
        </dependency>

        <!-- jjwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <!-- Oracle JDBC driver -->
        <dependency>
            <groupId>com.oracle.jdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>${oracle.version}</version>
        </dependency>

        <!-- HikariCP connection pool -->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>

        <!-- Swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.8.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.tika/tika-example -->
        <dependency>
            <groupId>org.apache.tika</groupId>
            <artifactId>tika-core</artifactId>
            <version>1.25</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.tika/tika-parsers -->
        <dependency>
            <groupId>org.apache.tika</groupId>
            <artifactId>tika-parsers</artifactId>
            <version>1.25</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-mock -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-mock</artifactId>
            <version>2.0.8</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>

        <!-- Mockito -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
        </dependency>

        <!-- Powermock -->
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>

    </dependencies>
</project>

And that prints -> Central European Standard Time

Why are you doing this? "Calendar" as an API is broken and obsolete, do not use it. 'Central European Standard Time' is a weird concept that probably you don't want at all. It is a broken concept you need to get rid of.

The EU has already decided that the EU as a whole is going to ditch the concept of daylight savings time entirely, but there is no actual requirement for each EU country to go to the same time zone. This means a few things:

  1. It's always been an idiotic standard; There is 'Central European Standard Time' (UTC+1), and 'Central European Summer Time' (UTC+2), which both shorten to CEST, but in common parlance, 'CEST' means summer time (UTC+2), and 'Central European Standard Time' is shortened to CET. Facepalm moment.

  2. Both of these zones are going to mean something completely different soon. At best, we'll be left with 'Central European Time' (CET), but that may actually end up being UTC+2, so 'CET' now refers to UTC+1, but next year it may be reinterpreted to mean UTC+2, which is hell for computers, so the best option is not to buy into this CET/CEST malarky in the first place. Whichever one isn't chosen will then be an obsolete relic: A zone that no country is actually in.

  3. Maybe CET/CEST will disappear entirely : Maybe western european countries adopt UTC+1, whereas eastern ones adopt UTC+2, to match their longitudes. In a vacuum, Poland should adopt UTC+2, The Netherlands should adopt UTC+1. Then there is no 'european central time' whatsoever.

  4. You already HAVE the right answer in your code: Europe/Berlin . That is how you name time zones. Not with 3-letter or 4-letter acronyms that are nebulous, overloaded, and insufficient.

But in all cases the changes are overwritten and I get UTC when calling and endpoint.

That's the problem with global defaults. 'Do not use singletons' is a common maxim, and this is why: You run into deep problems.

Yes, something is overwriting it.

The best fix is that you shouldn't need to care what the 'global' timezone property is. Whatever code you have now that uses Calendar? Find it, replace it with code based on java.time .

reference: The deprecation notice on TimeZone's javadoc about TLA time zone IDs .

You are using terrible date-time classes that were supplanted years ago by the modern java.time classes. Never use Calendar , Date , TimeZone , or SimpleDateFormat .

You asked:

what could be doing the change

As commented by Ole VV , your JVM's current default time zone is likely being overridden by some other Java code. Any Java code in any thread of any app within the JVM can at any moment change the JVM's current default time zone. Such a change immediately affects all the apps and threads running within that JVM. So this effectively makes the current default time zone an unreliable tool.

You asked:

how could I fix it to have my timezone always at "Europe/Berlin".

Always specify your time zone explicitly by passing a ZoneId object to java.time methods.

Instead of relying on default time zone, always specify the desired/expected time zone as the optional parameter to the various methods in java.time .

Instead of this:

ZonedDateTime now = ZonedDateTime.now() ;  // Do NOT do this. Do not rely implicitly on the JVM’s current default time zone.

… do this:

ZoneId z = ZoneId.of( "Europe/Berlin" ) ;
ZonedDateTime now = ZonedDateTime.now( z ) ;  // Do this. Always pass the desired/expected time zone explicitly. 

To generate text in standard ISO 8601 format extended to append the name of the time zone in square brackets, call toString .

String output = now.toString() ;

To generate text in other formats, let java.time automatically localize.

DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( Locale.CANADA_FRENCH ) ;
String output = now.format( f ) ;

Tip: On a server, it is generally best to set the default time zone of both the host OS and your JVM to UTC, for an offset of zero hours-minutes-seconds.

Tip: Generally best to do you business logic, debugging, logging, and sysadmin work all in UTC. Time zones should only be used (a) for presentation to be user, and (b) where business rules require.

Java 中的日期时间类型表,包括现代和传统


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date , Calendar , & SimpleDateFormat .

To learn more, see the Oracle Tutorial . And search Stack Overflow for many examples and explanations. Specification is JSR 310 .

The Joda-Time project, now in maintenance mode , advises migration to the java.time classes.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes. Hibernate 5 & JPA 2.2 support java.time .

Where to obtain the java.time classes?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM