Spring- 启动应用程序中未保留时区更改

Timezone changes are not being persisted in Spring-Boot application

当我在“Windows Server 2016 Datacenter”机器上 运行 带有 Tomcat 9 的 Springboot 2.3.8 应用程序时,我遇到了时区问题。 运行 它在本地使用 Eclipse 或 Tomcat 9 不会触发问题。

我在开始时使用以下方式设置时区:

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

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

然后打印 -> 欧洲中部标准时间

但是,稍后当我调用其中一个端点时,我会像以前一样检查时区

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

我得到 --> 协调世界时

我假设时区没有真正设置在 @PostConstruct 上,或者稍后被某些东西覆盖了。

我的问题是,可能会发生什么变化以及如何修复它以使我的时区始终位于“Europe/Berlin”。

我已经尝试如上所述更改应用程序内的时区,使用 tomcat 中的 java 参数并更改机器本身的时区,但在所有情况下更改都会被覆盖,我调用和端点时获取 UTC。

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

你为什么要这样做? “日历”作为 API 已损坏且已过时,请不要使用它。 'Central European Standard Time' 是一个你可能根本不想要的奇怪概念。这是一个你需要摆脱的错误概念。

欧盟已经决定整个欧盟将完全放弃夏令时的概念,但实际上并没有要求每个欧盟国家都使用同一时区。这意味着一些事情:

  1. 这一直是一个愚蠢的标准;有'Central European Standard Time'(UTC+1)和'Central European Summer Time'(UTC+2),都是CEST的缩写,但是通俗的说法,'CEST'就是夏令时(UTC+2), 'Central European Standard Time' 缩写为 CET。捂脸瞬间。

  2. 这两个区域很快就会有完全不同的含义。充其量,我们将只剩下 'Central European Time' (CET),但实际上最终可能是 UTC+2,因此 'CET' 现在指的是 UTC+1,但明年它可能会被重新解释为意思是 UTC+2,这对计算机来说是地狱,所以最好的选择是一开始就不要相信这个 CET/CEST 错误。没有被选中的那个将成为一个过时的遗迹:一个实际上没有国家的区域。

  3. 也许CET/CEST会完全消失:也许西欧国家采用UTC+1,而东方国家采用UTC+2,以匹配他们的经度。在真空中,波兰应该采用UTC+2,荷兰应该采用UTC+1。那就没有什么'european central time'了。

  4. 您已经在代码中找到了正确答案:Europe/Berlin 就是您命名时区的方式。不是含糊不清、过多和不足的 3 个字母或 4 个字母的首字母缩略词。

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

这就是全局默认值的问题。 'Do not use singletons' 是一个常见的格言,这就是为什么:你 运行 陷入了深层次的问题。

是的,有东西正在覆盖它。

最好的解决方法是您不需要关心 'global' 时区 属性 是什么。您现在有什么使用日历的代码?找到它,根据java.time.

替换成代码

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

您使用的日期时间 类 多年前已被现代 java.time 类 取代。 切勿使用 CalendarDateTimeZoneSimpleDateFormat.

你问过:

what could be doing the change

作为 ,您的 JVM 当前默认时区可能被其他一些 Java 代码覆盖。 JVM 中任何应用程序的任何线程中的任何 Java 代码都可以随时更改 JVM 当前的默认时区。这样的更改会立即影响该 JVM 中的所有应用程序和线程 运行。所以这有效地使当前默认时区成为一个不可靠的工具。

你问过:

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

始终通过将 ZoneId 对象传递给 java.time 方法来明确指定您的时区

不要依赖默认时区,而是始终将 desired/expected 时区指定为 java.time 中各种方法的可选参数。

而不是这个:

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

... 这样做:

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

要生成标准 ISO 8601 格式的文本,扩展为在方括号中附加时区名称,请调用 toString

String output = now.toString() ;

要生成其他格式的文本,让 java.time 自动本地化。

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

提示:在服务器上,通常最好将主机 OS 和 JVM 的默认时区都设置​​为 UTC,以实现零小时-分钟-秒的偏移量。

提示:通常最好让您的业务逻辑、调试、日志记录和系统管理都在 UTC 中工作。时区只能用于 (a) 向用户展示,以及 (b) 业务规则需要的地方。


关于java.time

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.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

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

您可以直接与数据库交换 java.time 对象。使用 JDBC driver compliant with JDBC 4.2 或更高版本。不需要字符串,不需要 java.sql.* 类。 Hibernate 5 和 JPA 2.2 支持 java.time.

在哪里获取 java.time 类?