4

I want to represent a 'business date', eg a transaction that happened 'on 3 June 2019'. We actively ignore timezones for this purpose, in full knowledge that 'on 3 June 2019' in Japan might be 'on 2 June 2019' in the US - and ordering within the 'day' is equally irrelevant. All dates will be today, or prior dates.

My obvious answer is that this is a LocalDate. However someone else has suggested this would be better represented as an Instant of 2019-06-03T00:00:00.000Z.

Apart from the different calls we'll have to make when converting/formatting this to human-readable dates in a UI, are there actually any differences between the two approaches?

This is a different question to What's the difference between Instant and LocalDateTime? because time is irrelevant to this question, and it only relates to past (or current) dates.

Oli
  • 582
  • 1
  • 6
  • 18
  • 5
    I once worked on a product where we made the mistake of representing a date of birth as an instant. This was one of those epic errors that we were still dealing with years later. Use `LocalDate`. – Andy Turner Jun 03 '19 at 09:36
  • 1
    @AndyTurner That almost sounds like the basis of an answer - what specifically went wrong? – Oli Jun 03 '19 at 09:38
  • @bracco23 Reading, will edit shortly. – Oli Jun 03 '19 at 09:39
  • 2
    Given that a LocalDate is the natural type for a date, does the "someone else" have a particular reason for suggesting an `Instant`? It's like saying "I want to represent an integer" and someone suggesting using `double`... there needs to be a really good reason. – Jon Skeet Jun 03 '19 at 09:44
  • @JonSkeet I think it's because "everything should be UTC", and broadly I agree, except in this 'date only' case! – Oli Jun 03 '19 at 09:45
  • 2
    @Oli check out jonskeet's article [Storing UTC is not a silver bullet](https://codeblog.jonskeet.uk/2019/03/27/storing-utc-is-not-a-silver-bullet/) – Andy Turner Jun 03 '19 at 09:46
  • Edited to add the additional clarification that I only care about past dates (or today). – Oli Jun 03 '19 at 09:55
  • 2
    @JonSkeet Thanks, the int/double is a good analogy. – Oli Jun 03 '19 at 10:02

2 Answers2

9

I once worked on a product where we made the mistake of representing a date of birth as an instant.

This was one of those "tiny" design errors that we were still dealing with years later.

The problem was that you couldn't reliably show it in the UI as the user changed time zone; it was error-prone to convert on the backend, because of developers working in different time zones to where the server was running. It sort-of worked in a single time zone, but as the product was extended to other time zones, it just became a total headache.

An instant is a point on the timeline; a local date is a range of times (and not a well-defined range of times, in the sense that it can represent different ranges of instants in different time zones). They represent different things.

If you want to represent a date, store a date. And I don't mean a java.util.Date, which is really an instant.

Use a LocalDate.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • 1
    As I say, my gut feeling is LocalDate, but if I can't argue the case myself that probably means I don't fully understand it! So maybe one point to make is that "it's too easy to make a mistake when using Instants, and a LocalDate protects you from that"? (As indeed we are both working on and running this in multiple locations around the world.) – Oli Jun 03 '19 at 09:58
  • 2
    With a local date, you've got a year, month, day. That's it. You can't accidentally treat it as something else. An instant can be accidentally interpreted as "a time in some time zone", and then you've converted incorrectly if you got the time zone wrong. – Andy Turner Jun 03 '19 at 10:09
  • 2
    I just wanted to mention that storing a date of birth is merely one of the few edge cases where it might make sense to use `LocalDate`. If I´m born in Germany on 2020/1/1 at 1:00am, it does not make sense to show my birthday as 2019/12/31 for someone in some USA timezone. That being said, in almost all other cases I think it makes total sense to either also record the TZ or have something like `Instant`, where the TZ is defined by default. – stk Apr 29 '20 at 11:57
6

LocalDate can be ambiguous

The Answer by Andy Turner is correct and valuable. In addition, I want to point out the inherent ambiguity of LocalDate.

The LocalDate class represents a date-only value, without time-of-day, without time zone. For any given moment, the date varies around the globe by time zone. At a particular moment, it may be “tomorrow” in Tokyo Japan while simultaneously being “yesterday” in Toledo Ohio US. Two different dates in effect at the very same moment.

So while you may think of a date as containing 24 hours, defining a specific range of moments, it does not. You must place the date in the context of a time zone to be able to determine moments. (And, by the way, days are not always 24 hours long.)

LocalDate localDate = LocalDate.of( 2021 , Month.JANUARY , 24 ) ;
ZoneId zoneTokyo = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime startOfDayTokyo = localDate.atStartOfDay( zoneTokyo ) ;  // Determine a specific moment. 

startOfDayTokyo.toString(): 2021-01-24T00:00+09:00[Asia/Tokyo]

To see that same moment in UTC, extract an Instant object.

Instant instant = startOfDayTokyo.toInstant() ;

Notice the date is 23rd rather than 24th.

instant.toString(): 2021-01-23T15:00:00Z

See that same moment through third wall-clock time, that of Toledo Ohio US in time zone America/New_York.

ZonedDateTime zdtToledo = instant.atZone( ZoneId.of( "America/New_York" ) ) ;

Notice the date is 23rd rather than 24th.

zdtToledo.toString(): 2021-01-23T10:00-05:00[America/New_York]

See this code run live at IdeOne.com.

So when storing a LocalDate, you may want to also store the time zone name as well. In a database table, that would mean two columns.

As an example, think of birthday. If it were crucial to know someone’s age to the very day, then a date-only value is not enough. With only a date, a person appearing to turn 18 years old in Tokyo Japan would still be 17 in Toledo Ohio US. As another example, consider a due date in a contract. If stated as only a date, a stakeholder in Japan will see a task as overdue while another stakeholder in Toledo sees the task as on-time.

In contrast, when you mean a moment, a specific point on the timeline, use Instant (or OffsetDateTime or ZonedDateTime).


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

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154