All about java.util.Date
This post is an attempt to reduce the number of times I need to explain things in Stack Overflow comments. You may well be reading it via a link from Stack Overflow - I intend to refer to this post frequently in comments. Note that this post is mostly not about text handling - see my post on common mistakes in date/time formatting and parsing for more details on that.
There are few classes which cause so many similar questions on Stack Overflow as java.util.Date. There are four causes for this:
- Date and time work is fundamentally quite complicated and full of corner cases. It's manageable, but you do need to put some time into understanding it.
- The java.util.Date class is awful in many ways (details given below).
- It's poorly understood by developers in general.
- It's been badly abused by library authors, adding further to the confusion.
The most important things to know about java.util.Date are:
- You should avoid it if you possibly can. Use java.time.* if possible, or the ThreeTen-Backport (java.time for older versions, basically) or Joda Time if you're not on Java 8 yet.
- If you're forced to use it, avoid the deprecated members. Most of them have been deprecated for nearly 20 years, and for good reason.
- If you really, really feel you have to use the deprecated members, make sure you really understand them.
- A Date instance represents an instant in time, not a date. Importantly, that means:
- It doesn't have a time zone.
- It doesn't have a format.
- It doesn't have a calendar system.
Now, onto the details"
What's wrong with java.util.Date?java.util.Date (just Date from now on) is a terrible type, which explains why so much of it was deprecated in Java 1.1 (but is still being used, unfortunately).
Design flaws include:
- Its name is misleading: it doesn't represent a Date, it represents an instant in time. So it should be called Instant - as its java.time equivalent is.
- It's non-final: that encourages poor uses of inheritance such as java.sql.Date (which is meant to represent a date, and is also confusing due to having the same short-name)
- It's mutable: date/time types are natural values which are usefully modeled by immutable types. The fact that Date is mutable (e.g. via the setTime method) means diligent developers end up creating defensive copies all over the place.
- It implicitly uses the system-local time zone in many places - including toString() - which confuses many developers. More on this in the "What's an instant" section
- Its month numbering is 0-based, copied from C. This has led to many, many off-by-one errors.
- Its year numbering is 1900-based, also copied from C. Surely by the time Java came out we had an idea that this was bad for readability?
- Its methods are unclearly named: getDate() returns the day-of-month, and getDay() returns the day-of-week. How hard would it have been to give those more descriptive names?
- It's ambiguous about whether or not it supports leap seconds: "A second is represented by an integer from 0 to 61; the values 60 and 61 occur only for leap seconds and even then only in Java implementations that actually track leap seconds correctly." I strongly suspect that most developers (including myself) have made plenty of assumptions that the range for getSeconds() is actually in the range 0-59 inclusive.
- It's lenient for no obvious reason: "In all cases, arguments given to methods for these purposes need not fall within the indicated ranges; for example, a date may be specified as January 32 and is interpreted as meaning February 1." How often is that useful?
I could find more problems, but they would be getting pickier. That's a plentiful list to be going on with. On the plus side:
- It unambiguously represents a single value: an instant in time, with no associated calendar system, time zone or text format, to a precision of milliseconds.
Unfortunately even this one "good aspect" is poorly understood by developers. Let's unpack it"
What's an "instant in time"?Note: I'm ignoring relativity and leap seconds for the whole of the rest of this post. They're very important to some people, but for most readers they would just introduce more confusion.
When I talk about an "instant" I'm talking about the sort of concept that could be used to identify when something happened. (It could be in the future, but it's easiest to think about in terms of a past occurrence.) It's independent of time zone and calendar system, so multiple people using their "local" time representations could talk about it in different ways.
Let's use a very concrete example of something that happened somewhere that doesn't use any time zones we're familiar with: Neil Armstrong walking on the moon. The moon walk started at a particular instant in time - if multiple people from around the world were watching at the same time, they'd all (pretty much) say "I can see it happening now" simultaneously.
If you were watching from mission control in Houston, you might have thought of that instant as "July 20th 1969, 9:56:20 pm CDT". If you were watching from London, you might have thought of that instant as "July 21st 1969, 3:26:20 am BST". If you were watching from Riyadh, you might have thought of that instant as "JumAdi 7th 1389, 5:56:20 am (+03)" (using the Umm al-Qura calendar). Even though different observers would see different times on their clocks - and even different years - they would still be considering the same instant. They'd just be applying different time zones and calendar systems to convert from the instant into a more human-centric concept.
So how do computers represent instants? They typically store an amount of time before or after a particular instant which is effectively an origin. Many systems use the Unix epoch, which is the instant represented in the Gregorian calendar in UTC as midnight at the start of January 1st 1970. That doesn't mean the epoch is inherently "in" UTC - the Unix epoch could equally well be defined as "the instant at which it was 7pm on December 31st 1969 in New York".
The Date class uses "milliseconds since the Unix epoch" - that's the value returned by getTime(), and set by either the Date(long) constructor or the setTime() method. As the moon walk occurred before the Unix epoch, the value is negative: it's actually -14159020000.
To demonstrate how Date interacts with the system time zone, let's show the three time zones mentioned before - Houston (America/Chicago), London (Europe/London) and Riyadh (Asia/Riyadh). It doesn't matter what the system time zone is when we construct the date from its epoch-millis value - that doesn't depend on the local time zone at all. But if we use Date.toString(), that converts to the current default time zone to display the result. Changing the default time zone does not change the Date value at all. The internal state of the object is exactly the same. It still represents the same instant, but methods like toString(), getMonth() and getDate() will be affected. Here's sample code to show that:
import java.util.Date;import java.util.TimeZone;public class Test { public static void main(String[] args) { // The default time zone makes no difference when constructing // a Date from a milliseconds-since-Unix-epoch value Date date = new Date(-14159020000L); // Display the instant in three different time zones TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago")); System.out.println(date); TimeZone.setDefault(TimeZone.getTimeZone("Europe/London")); System.out.println(date); TimeZone.setDefault(TimeZone.getTimeZone("Asia/Riyadh")); System.out.println(date); // Prove that the instant hasn't changed... System.out.println(date.getTime()); }}
The output is as follows:
Sun Jul 20 21:56:20 CDT 1969Mon Jul 21 03:56:20 GMT 1969Mon Jul 21 05:56:20 AST 1969-14159020000
The "GMT" and "AST" abbreviations in the output here are highly unfortunate - java.util.TimeZone doesn't have the right names for pre-1970 values in all cases. The times are right though.
Common questionsHow do I convert a Date to a different time zone?You don't - because a Date doesn't have a time zone. It's an instant in time. Don't be fooled by the output of toString(). That's showing you the instant in the default time zone. It's not part of the value.
If your code takes a Date as an input, any conversion from a "local time and time zone" to an instant has already occurred. (Hopefully it was done correctly")
If you start writing a method with a signature like this, you're not helping yourself:
// A method like this is always wrongDate convertTimeZone(Date input, TimeZone fromZone, TimeZone toZone)How do I convert a Date to a different format?
You don't - because a Date doesn't have a format. Don't be fooled by the output of toString(). That always uses the same format, as described by the documentation.
To format a Date in a particular way, use a suitable DateFormat (potentially a SimpleDateFormat) - remembering to set the time zone to the appropriate zone for your use.