3

Is it possible to create a java.util.Date object that is guaranteed to be UTC and at a specific time in the past, like an hour ago or a day ago, and is this a good idea?

I have reviewed the Stack Overflow question get date as of 4 hours ago and its answers. But I want to avoid having to add more dependencies, like jodaTime, and the accepted answer uses System.currentTimeMillis() which would be the local timezone, would it not?

Jonny Henly
  • 4,023
  • 4
  • 26
  • 43
Patrick Schaefer
  • 821
  • 10
  • 20
  • 2
    Use the [`java.time` package](https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html), and I'm almost positive this question will be closed as a duplicate. – Jonny Henly Jan 31 '18 at 18:27
  • the question is if this is possible using specifically java.util.Date, but maybe it's not. – Patrick Schaefer Jan 31 '18 at 18:28
  • `System.currentTimeMillis()` does not reflect the time zone. When you start talking about "days" or "months", you have to define what you mean, because that depends on your business requirements. – erickson Jan 31 '18 at 18:29
  • Java 8 or Java 6/7? – Deadron Jan 31 '18 at 18:31
  • 2
    A `Date` has got no time zone or offset. – Ole V.V. Jan 31 '18 at 18:32
  • 1
    @JonnyHenly Provided it’s not a dupe (which I too think it is) I shall be happy to provide a `java.time` answer, you are right. And include conversion to an old-fashioned `java.util.Date` if this is what the OP needs, for example for a legacy API. – Ole V.V. Jan 31 '18 at 18:34
  • 1
    The `Date` class is long outmoded and poorly designed. Only get one if you need one for a legacy API that you cannot change or don’t want to change just now. Under all circumstances I recommend [`java.time`, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Jan 31 '18 at 18:37
  • 2
    @OleV.V.Exactly. Exactly. `Calendar` tried to (unsuccessfully) patch over `Date`'s problems, but it's just better to move to `java.time`. – Powerlord Jan 31 '18 at 18:39
  • 1
    `java.time.Instant` is time zone neutral (just like `Date`), but its `toString` always gives the date and time in UTC. This might be a good choice for you? It’s straightforward to convert it to a `Date`. If you need a date-time that is explicitly in UTC, you need a `java.time.OffsetDateTime`. – Ole V.V. Jan 31 '18 at 18:39

2 Answers2

7

As discussed vividly in the comments, the recommendation is to use the java.time package. The easy solution:

Instant fourHoursAgo = Instant.now().minus(Duration.ofHours(4));
System.out.println(fourHoursAgo);

This just printed

2018-01-31T14:57:44.667255Z

Since UTC time is now 18:58, the output is what you asked for. The Instant itself is offset neutral. Its toString delivers time in UTC, but there was no mention of UTC in producing the Instant, so whether it gives you what you want, I am not sure. I will give you a result that is explicitly in UTC later.

But first, if you do need a java.util.Date, typically for a legacy API that you cannot change, the conversion is easy:

Date oldfashionedDate = Date.from(fourHoursAgo);
System.out.println(oldfashionedDate);

On my computer in Europe/Copenhagen time zone this printed:

Wed Jan 31 15:57:44 CET 2018

Again, this agrees with the time four hours before running the snippet. And again, a Date doesn’t have a UTC offset in it. Only its toString method grabs my JVM’s time zone setting and uses it for generating the string, this does not affect the Date. See the Stack Overflow question, How to set time zone of a java.util.Date?, and its answers.

As promised, if you do need to represent not only the time but also the offset, use an OffsetDateTime:

OffsetDateTime fourHoursAgoInUtc = OffsetDateTime.now(ZoneOffset.UTC).minusHours(4);
System.out.println(fourHoursAgoInUtc);

This printed

2018-01-31T14:57:44.724147Z

Z at the end means offset zero from UTC or “Zulu time zone” (which isn’t a true time zone). The conversion to a Date is not much more complicated than before, but again, you will lose the offset information in the conversion:

Date oldfashionedDate = Date.from(fourHoursAgoInUtc.toInstant());
System.out.println(oldfashionedDate);

This printed:

Wed Jan 31 15:57:44 CET 2018

Link: Oracle tutorial explaining how to use java.time

Jonny Henly
  • 4,023
  • 4
  • 26
  • 43
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
5

You can achieve this using the java.time package, as follows:

LocalDateTime localDateTime = LocalDateTime.now(ZoneOffset.UTC).minusHours(4);
Date date = Date.from(localDateTime.atZone(ZoneOffset.UTC).toInstant());

Gives the following output:

2018-01-31T14:58:28.908
Wed Jan 31 20:28:28 IST 2018     //20:28:28 IST is 14:58:28 UTC

Which is correctly 4+5:30 hours behind my current time - Asia/Kolkata ZoneId.

Jonny Henly
  • 4,023
  • 4
  • 26
  • 43
Pankaj Singhal
  • 15,283
  • 9
  • 47
  • 86
  • It won't work if I use `ZoneOffset.UTC` in both the statements – Pankaj Singhal Jan 31 '18 at 18:46
  • Why not `Date.from(Instant.now().minus(4, ChronoUnit.HOURS))`? – erickson Jan 31 '18 at 18:48
  • `LocalDateTime localDateTime = LocalDateTime.now(ZoneOffset.UTC);` & `Date date = Date.from(localDateTime.atZone(ZoneOffset.UTC).toInstant());` gives the following output: `2018-01-31T18:50:41.519` & `Thu Feb 01 00:20:41 IST 2018` – Pankaj Singhal Jan 31 '18 at 18:51
  • @erickson another nice suggestion – Ole V.V. Jan 31 '18 at 18:52
  • time currently here is the latter one. But the answer which I posted gives same time in both i.e. `2018-01-31T18:50:41.519` – Pankaj Singhal Jan 31 '18 at 18:52
  • 3
    Its the first code in your edited answer that doesn’t work. It gives you 14:56:24 IST, which disagrees with 14:56:24 UTC, which would have been correct. It’s `Date.toString()` fooling you. – Ole V.V. Jan 31 '18 at 19:02
  • 1
    Yup. Now I see it. I missed the `IST` part in the first code's output. Changing my answer to the correct one. – Pankaj Singhal Jan 31 '18 at 19:07