150

I am trying to set a server agnostic date time in my database and I believe the best practice to do so is to set a UTC DateTime. My db server is Cassandra and the db driver for Java understands only the Date type.

So assuming that in my code I am using the new Java 8 ZonedDateTime to get the UTC now (ZonedDateTime.now(ZoneOffset.UTC)), how can I convert this ZonedDateTime instance to the "legacy" Date class?

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Milen Kovachev
  • 5,111
  • 6
  • 41
  • 58

9 Answers9

238

You can convert ZonedDateTime to an instant, which you can use directly with Date.

Date.from(java.time.ZonedDateTime.now().toInstant());
Jasper
  • 11,590
  • 6
  • 38
  • 55
  • 49
    No, it will be the current Date on your zone system default. –  Aug 28 '15 at 15:53
  • 9
    @MilenKovachev Your question does not make sense - a Date does not have a time zone - it only represents an instant in time. – assylias Aug 28 '15 at 16:29
  • 2
    @assylias Actually, your statement doesn't make sense. The format in which the time is stored implies a timezone. Date is based on UTC, unfortunately, Java does some stupid things and doesn't treat it as such, and on top of it, considers the time to be local TZ instead of UTC. The way Data,LocalDateTime,ZonedDateTime store data implies a timezone. The way they get used is as if TZs don't exist, which is simply flat out wrong. java.util.Date has a TZ of the JVM default, implicitly. The fact that people treat it as anything different (including the java docs for it!) is just bad people. – David Oct 23 '15 at 17:28
  • 10
    @David uh no - a `Date` is a number of milliseconds since the epoch - so it is related to UTC. If you ***print*** it, the default timezone will be used, but the Date class has no knowledge of the user's timezone... See for example the "mapping" section in https://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html And LocalDateTime is explicitly without reference to a timezone - which can be seen as confusing... – assylias Oct 23 '15 at 20:20
  • 4
    Your Answer needlessly involves `ZonedDateTime`. The `java.time.Instant` class is a directly replacement for `java.util.Date`, both representing a moment in UTC though `Instant` uses a finer resolution of nanoseconds instead of milliseconds. `Date.from( Instant.now() )` should have been your solution. Or for that matter, just `new Date()` which has the same effect, capturing the current moment in UTC. – Basil Bourque Jul 16 '18 at 22:02
  • Down a point. Just a simple code will tell that it does not work. – Erick Audet Oct 16 '21 at 13:00
111

tl;dr

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
    Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)

Though there was no point in that code above. Both java.util.Date and Instant represent a moment in UTC, always in UTC. Code above has same effect as:

new java.util.Date()  // Capture current moment in UTC.

No benefit here to using ZonedDateTime. If you already have a ZonedDateTime, adjust to UTC by extracting a Instant.

java.util.Date.from(             // Truncates any micros/nanos.
    myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)

Other Answer Correct

The Answer by ssoltanid correctly addresses your specific question, how to convert a new-school java.time object (ZonedDateTime) to an old-school java.util.Date object. Extract the Instant from the ZonedDateTime and pass to java.util.Date.from().

Data Loss

Note that you will suffer data loss, as Instant tracks nanoseconds since epoch while java.util.Date tracks milliseconds since epoch.

diagram comparing resolutions of millisecond, microsecond, and nanosecond

Your Question and comments raise other issues.

Keep Servers In UTC

Your servers should have their host OS set to UTC as a best practice generally. The JVM picks up on this host OS setting as its default time zone, in the Java implementations that I'm aware of.

Specify Time Zone

But you should never rely on the JVM’s current default time zone. Rather than pick up the host setting, a flag passed when launching a JVM can set another time zone. Even worse: Any code in any thread of any app at any moment can make a call to java.util.TimeZone::setDefault to change that default at runtime!

Cassandra Timestamp Type

Any decent database and driver should automatically handle adjusting a passed date-time to UTC for storage. I do not use Cassandra, but it does seem to have some rudimentary support for date-time. The documentation says its Timestamp type is a count of milliseconds from the same epoch (first moment of 1970 in UTC).

ISO 8601

Furthermore, Cassandra accepts string inputs in the ISO 8601 standard formats. Fortunately, java.time uses ISO 8601 formats as its defaults for parsing/generating strings. The Instant class’ toString implementation will do nicely.

Precision: Millisecond vs Nanosecord

But first we need to reduce the nanosecond precision of ZonedDateTime to milliseconds. One way is to create a fresh Instant using milliseconds. Fortunately, java.time has some handy methods for converting to and from milliseconds.

Example Code

Here is some example code in Java 8 Update 60.

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
…
Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z

Or according to this Cassandra Java driver doc, you can pass a java.util.Date instance (not to be confused with java.sqlDate). So you could make a j.u.Date from that instantTruncatedToMilliseconds in the code above.

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

If doing this often, you could make a one-liner.

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

But it would be neater to create a little utility method.

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
    Instant instant = zdt.toInstant();
    // Data-loss, going from nanosecond resolution to milliseconds.
    java.util.Date utilDate = java.util.Date.from( instant ) ;
    return utilDate;
}

Notice the difference in all this code than in the Question. The Question’s code was trying to adjust the time zone of the ZonedDateTime instance to UTC. But that is not necessary. Conceptually:

ZonedDateTime = Instant + ZoneId

We just extract the Instant part, which is already in UTC (basically in UTC, read the class doc for precise details).


Table of date-time types in Java, both modern and legacy


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?

Table of which java.time library to use with which version of Java or Android

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
  • Wonderful explanation, well detailed. Thank you so much! – Gianmarco F. Mar 26 '19 at 08:50
  • Thanks, @Basil Bourque for your complete answer. Is bad or down performance when return list or pagination and loop on this list item by item to convert from zone (assume UTC) to another zone? – java dev Dec 28 '21 at 19:50
  • @devloper152 Are you asking about the performance of *java.time* adjusting a moment by time zone? Try it yourself. I expect you’ll find it quite speedy. I doubt you’ll find *java.time* to be a bottle-neck. – Basil Bourque Dec 28 '21 at 20:01
  • I am very pleased with your replaying I have already tried it. Another question sir what is your opinion when converting from one zone to another in the list in backend or frontend(Angular)? – java dev Dec 28 '21 at 20:12
  • @devloper152 I don’t use Angular. I build web apps entirely in pure Java using [Vaadin Flow](https://en.m.wikipedia.org/wiki/Vaadin#Vaadin_Flow_(Java_API)). – Basil Bourque Dec 28 '21 at 22:44
  • You are da boss of time conversion :) Cheers – Arar Apr 18 '23 at 03:19
5

If you are using the ThreeTen backport for Android and can't use the newer Date.from(Instant instant) (which requires minimum of API 26) you can use:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

or:

Date date = DateTimeUtils.toDate(zdt.toInstant());

Please also read the advice in Basil Bourque's answer

David Rawson
  • 20,912
  • 7
  • 88
  • 124
  • 1
    The ThreeTen Backport (and ThreeTenABP) include a `DateTimeUtils` class with the conversion methods, so I’d use Date date = DateTimeUtils.toDate(zdt.toInstant());`. It’s not so low-level. – Ole V.V. Jun 17 '18 at 12:37
5

The accepted answer did not work for me. The Date returned is always the local Date and not the Date for the original Time Zone. I live in UTC+2.

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 

I have come up with two alternative ways to get the correct Date from a ZonedDateTime.

Say you have this ZonedDateTime for Hawaii

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

or for UTC as asked originally

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

Alternative 1

We can use java.sql.Timestamp. It is simple but it will probably also make a dent in your programming integrity

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

Alternative 2

We create the Date from millis (answered here earlier). Note that local ZoneOffset is a must.

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);
Avec
  • 1,626
  • 21
  • 31
  • 1
    While upvoting your answers, I think both your options are a little heavy weight. I like this better: `instant = zdt.toLocalDateTime().toInstant(ZoneOffset.UTC); return Date.from(instant);` I'm using a hardcoded zone offset here for my own purpose, but it's obvious how to use it in general. – Anton Duzenko Nov 22 '22 at 18:14
4

Here is an example converting current system time to UTC. It involves formatting ZonedDateTime as a String and then the String object will be parsed into a date object using java.text DateFormat.

    ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
    final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
    final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    String dateStr = zdt.format(DATETIME_FORMATTER);

    Date utcDate = null;
    try {
        utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
    }catch (ParseException ex){
        ex.printStackTrace();
    }
Asanka Siriwardena
  • 871
  • 13
  • 18
2

For a docker application like beehuang commented you should set your timezone.

Alternatively you can use withZoneSameLocal. For example:

2014-07-01T00:00+02:00[GMT+02:00] is converted by

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

to Tue Jul 01 00:00:00 CEST 2014 and by

Date.from(zonedDateTime.toInstant())

to Mon Jun 30 22:00:00 UTC 2014

dwe
  • 91
  • 3
1

I use this.

public class TimeTools {

    public static Date getTaipeiNowDate() {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Asia/Taipei");
        ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

Because Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); It's not work!!! If u run your application in your computer, it's not problem. But if you run in any region of AWS or Docker or GCP, it will generate problem. Because computer is not your timezone on Cloud. You should set your correctly timezone in Code. For example, Asia/Taipei. Then it will correct in AWS or Docker or GCP.

public class App {
    public static void main(String[] args) {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Australia/Sydney");
        ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
        try {
            Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
            System.out.println("ans="+ans);
        } catch (ParseException e) {
        }
        Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
        System.out.println("wrongAns="+wrongAns);
    }
}
beehuang
  • 339
  • 2
  • 18
  • 1
    This seems to be one of the most complex ways in any of the 7 answers. Does it have any advantages? I would think not. There’s really no need to go through formatting and parsing. – Ole V.V. Jun 16 '18 at 12:46
  • Thanks you ask. Because Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());; It's not work!!!!! – beehuang Jun 17 '18 at 14:05
  • I ran you seconds snippet just before 17:08 in my time zone and got `ans=Mon Jun 18 01:07:56 CEST 2018`, which is incorrect, and then `wrongAns=Sun Jun 17 17:07:56 CEST 2018`, which is correct. – Ole V.V. Jun 17 '18 at 15:10
  • I don't have any problem when run app in my computer. But when use docker, it has problem. Because timezone in docker is not your timezone in computer. You should set your correctly timezone in Code. For example, Asia/Taipei. Then it will correct in AWS, Docker, GCP or any computer. – beehuang Jun 18 '18 at 02:09
  • @OleV.V. Can u understand?? or any questions? – beehuang Jun 20 '18 at 14:04
  • No, let me be honest, I gave up on understanding why you believe that your incorrect code is correct and vice versa. A guess is it might have to do with ignoring the time zone abbreviation that `Date.toString()` prints and which is crucial for interpreting the string. But it’s only a guess. – Ole V.V. Jun 20 '18 at 14:07
  • Ok, not problem. Because I meet the problem, I solve it. Maybe, you can try in different timezone of computer later. You can find that have one comment ```No, it will be the current Date on your zone system default. – Slim Soltani Dridi Aug 28 '15 at 15:53``` on the first answer. – beehuang Jun 20 '18 at 14:38
  • I have seen that comment. But a `Date` hasn’t got a time zone. So if a `Date` holds the current time, then it holds the current time independently of time zone. – Ole V.V. Jun 20 '18 at 14:42
  • Maybe you r right. But I just want get Date from timezone. (This question is ZonedDateTime to Date?). And first answer is incorrect. Because it just get by your zone system default. – beehuang Jun 20 '18 at 14:52
0

You can do this using the java.time classes built into Java 8 and later.

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
-2

If you are interested in now only, then simply use:

Date d = new Date();
Daniel Werner
  • 1,350
  • 16
  • 26
Jacob Eckel
  • 1,633
  • 1
  • 13
  • 22
  • I am interested in now but UTC now. – Milen Kovachev Aug 28 '15 at 15:34
  • 2
    Yes, that will be the UTC now, the Date doesn't know any better. – Jacob Eckel Aug 28 '15 at 15:36
  • 3
    No, it won't. It will be Now in the local system time zone. Date doesn't store any timezone information, but it uses the current system time and ignores the current system timezone, so there for never converts it back to UTC – David Oct 23 '15 at 17:34
  • 2
    @David Date keeps time relative to UTC epoch, so if you take a new Date() from a system in the US and another object form a computer in Japan they will be identical (look into the long they both keep internally). – Jacob Eckel Oct 24 '15 at 09:28
  • 1
    @JacobEckel - Yes, but if you put 9am into a date while your tz is a US tz and then change to a JP tz and create a new date with 9am, the internal value in Date will be different. As soon as you throw DST into the mix you can not reliably use Date unless your app ALWAYS runs in UTC specifically, which could change for any number of reasons most of which revolve around bad code. – David Nov 18 '15 at 18:52
  • I think Milen want UTC+0 aka GMT aka Zulu time. I have added an answer that will give the correct Date object if I have understand this correctly. new Date() will match UTC+? for whatever timezone/local settings Java runs on. If you live in London new Date() will work fine but it will not work for other time zones. – Avec Sep 25 '19 at 12:32