21

I have a java application in which I would like the time in UTC. Currently, the code uses a mix of java.util.Date and java.sql.Timestamp. To get the time in UTC, the programmer before me used:

For Date:

 Date.from(ZonedDateTime.now(ZoneOffset.UTC)).toInstant();

For Timestamp:

 Timestamp.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());

However I have run multiple tests myself with this code and both of these lines return the current date/time(in my current timezone). From everything I have read it appears that Date/Timestamp does not have a zoneOffset value, but I cannot find a concrete statement of this.

Is there anyway to keep the timeZone (UTC) within the Date or Timestamp objects, or do I need to do some refactoring and use the actual ZonedDateTime object throughout my application? Also will this ZonedDateTime object be compatible with the current Timestamp object for sql?

Example:

public static void main (String args[])
{
    ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneOffset.UTC);
    Timestamp timestamp = Timestamp.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
    Date date = Date.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
    System.out.println("ZonedDateTime: " + zonedDateTime);
    System.out.println("Timestamp: " + timestamp);
    System.out.println("Date: " + date);
}

Output:

 ZonedDateTime: 2017-04-06T15:46:33.099Z
 Timestamp: 2017-04-06 10:46:33.109
 Date: Thu Apr 06 10:46:33 CDT 2017
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
DevelopingDeveloper
  • 883
  • 1
  • 18
  • 41
  • 1
    "...return the current date/time(in my current timezone)...", maybe you have just used the method `toString()` on `java.util.Date` to conclude that, but this method only uses external default timezone to print itself. The offset or timezone is never part of `java.util.Date`, and its true state is rather reflected by the method `getTime()`. So please clarify what you have really done. – Meno Hochschild Apr 06 '17 at 15:46
  • 3
    Date and Timestamp both store time in GMT. Switching the time zone only changes the display value. – Compass Apr 06 '17 at 15:48
  • 1
    There is no logical difference between `Date.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant())` and `new Date()`. Except former is a lot more confusing. – M. Prokhorov Apr 06 '17 at 16:35
  • 1
    Normally I would not recommend looking at the source, since a class’s behavior is defined by its contract (javadoc), but as of Java 8, [the source of java.util.Date](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/Date.java/) shows it has only two instance fields: a long value and a ‘BaseCalendar’ object used only by now-deprecated methods (which date back to when the Calendar class didn’t exist and the Date class was trying to provide all calendar-related functionality). It just wraps a millisecond value; it retains no timezone information. – VGR Apr 06 '17 at 16:50

2 Answers2

41

tl;dr

Instant.now()  // Capture the current moment in UTC with a resolution up to nanoseconds.

Use only java.time classes. Avoid the troublesome old legacy date-time classes added before Java 8.

Using java.time

The programmer before you was making use of the new modern java.time classes that now supplant the notoriously troublesome old legacy date-time classes such as Date, Calendar, Timestamp.

Instant

The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction). To get the current moment in UTC is utterly simple: Instant.now.

Instant instant = Instant.now();

Converting

You should stick to the java.time classes, and avoid the legacy classes. But if absolutely necessary such as interfacing with old code not yet updated for java.time, you may convert to/from java.time. Look to new methods on old classes. The legacy class java.util.Date equivalent is Instant.

java.util.Date d = java.util.Date.from( myInstant); // To legacy from modern.
Instant instant = myJavaUtilDate.toInstant();  // To modern from legacy.

JDBC

Avoid the legacy date-time classes. Use java.time classes instead.

Your JDBC 4.2 compliant driver may be able to directly address java.time types by calling PreparedStatement::setObject and ResultSet::getObject.

myPreparedStatement.setObject( … , instant ) ;

… and …

Instant instant = myResultSet.getObject( … , Instant.class ) ;

If not, fall back to using the java.sql types, but as briefly as possible. Use new conversion methods added to the old classes.

myPreparedStatement.setTimestamp( … , java.sql.Timestamp.from( instant ) ) ;

… and …

Instant instant = myResultSet.getTimestamp( … ).toInstant() ;

No need for ZonedDateTime

Notice that we had no need for your mentioned ZonedDateTime as you said you were only interested in UTC. The Instant objects are always in UTC. That means that original code you quoted:

Date.from(ZonedDateTime.now(ZoneOffset.UTC)).toInstant();

…could have simply been shortened to:

Date.from( Instant.now() ) ;

Note that java.util.Date is always in UTC as well. However, its toString unfortunately applies the JVM’ current default time zone implicitly while generating the String. This anti-feature creates no end of confusion as you can see by searching on Stack Overflow.

If you want to see your Instant object’s UTC value through the lens of a region’s wall-clock time, assign a time zone ZoneId to get a ZoneDateTime.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as CDT or EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "America/Chicago" );
ZonedDateTime zdt = instant.atZone( z );

Table of date-time types in Java (both legacy and modern) and in standard SQL


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.

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

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

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.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Why do you recommend using `setObject()` instead of using the `java.sql` type `timestamp`? – Michel Feinstein Jan 24 '18 at 00:47
  • 2
    @mFeinstein Because `Timestamp` is an abomination of poor Class design. The *java.time* framework was designed and adopted by Sun, Oracle, and the JCP to entirely replace the legacy date-time classes. One of the main points of JDBC 4.2 update was this transition to the modern *java.time* classes. – Basil Bourque Jan 24 '18 at 00:55
  • How does `setObject` behaves on this? Will it save my `ZonedDateTime` as a blob? Will I be able to run queries on the data saved on the database? Does it work for all `java.time` values as `Instant` and `Period`? – Michel Feinstein Jan 24 '18 at 01:14
  • I am currently using `Timestamp` from an `Instant` and all `TIMESTAMP` data on my MySQL database is in UTC and I can search it and read it easily, what am I losing then? – Michel Feinstein Jan 24 '18 at 01:16
  • @mFeinstein As of JDBC 4.2, no need to ever touch the `java.sql.Timestamp` class again, nor any other date-time related class outside the *java.time* package. I am a Postgres kind of guy, but apparently the `TIMESTAMP` data type in MySQL represents a moment in UTC, and is akin to the SQL-standard type `TIMESTAMP WITH TIME ZONE`. Therefore, you should use `java.time.Instant` for exchanging values with columns of that type. If you have a `ZonedDateTime` object, simply call its `toInstant` method to converse with your database via your JDBC driver. After retrieving, call `Instant::atZone`. – Basil Bourque Aug 24 '18 at 23:42
  • Is Postgres timestamp different than MySQL? I am planning to learn Postgresql with Postgis shortly – Michel Feinstein Aug 25 '18 at 22:04
  • The only thing I don't like about this "no java.sql.Timestamp" approach is that code becomes less readable and more prone to error since we are passing a generic object to the database and not a strictly typed one.... So I get inclined to use Timestamps just on the database operations, as your recommendations to use them as briefly as possible, so the database operation is known at compile time and very readable... But I am still thinking about it, maybe I won't use it... – Michel Feinstein Aug 25 '18 at 22:05
  • @mFeinstein Postgres and MySQL are two leading open-source relational databases. One is vastly superior to the other, but which one depends on who you ask. ;-) If asking me, the blue elephant is my mascot. – Basil Bourque Aug 25 '18 at 22:42
  • I read a good article about it, my conclusion is that MySQL is better for random access, whereas Postgresql is better for long running queries and GIS.... But I was asking more about the different timestamps since you mentioned it, but no problem, I will have to read it shortly anyways – Michel Feinstein Aug 25 '18 at 22:55
  • FYI: If you are interested in using PostgreSQL and its JDBC driver, then the documentation has some additional useful information on [Using Java 8 Date and Time classes](https://jdbc.postgresql.org/documentation/head/java8-date-time.html) (e.g. the note that ZonedDateTime, Instant and OffsetTime / TIME WITH TIME ZONE are not supported). – jechterhoff Jun 23 '21 at 07:33
6

In Java, Date represents a point in time. It's not related to timestamp. When you call toString() method of a Date object, it converts that time to Platform's default Timestamp, e.g. Following will print date/time in UTC (as it sets default timezone to UTC):

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneOffset.UTC);
Timestamp timestamp = Timestamp.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
Date date = Date.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
System.out.println("ZonedDateTime: " + zonedDateTime);
System.out.println("Timestamp: " + timestamp);
System.out.println("Date: " + date);
Darshan Mehta
  • 30,102
  • 11
  • 68
  • 102