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>
ZonedDateTime.now ( ZoneId.of ( "Europe/Berlin" ) )
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 V.V., 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.
#} Specify desired time zone
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.
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?