Unravel โบ Java concepts โบ Time and dates
Legacy Date, Calendar and SimpleDateFormat
java.util.Date, Calendar and SimpleDateFormat are the pre-Java-8 date classes.
At a glance
A pocket calculator from the 1990s with buttons in odd places: it works if you know the quirks, but a modern one is a better choice.
- 1new Date() is a moment (milliseconds since 1970), not a dateIts toString prints it in the default time zone, which makes it look like a zoned value.
- 2Calendar months start at 0 (January is 0), Calendar.getInstance() uses the default zone, and cal.add(Calendar.DAY_OF_MONTH, 1) mutates the object
- 3SimpleDateFormat("yyyy-MM-dd") formats and parses, but it is not thread safe: one shared static instance corrupts results under loadIt also accepts invalid dates (February 30) by default unless you call setLenient(false).
- 4Bridges: date.toInstant(), Date.from(instant), LocalDate.ofInstant(...), Timestamp.valueOf(localDateTime) for JDBC
- The year 2000 and January numbering bugs of these classes caused so many errors that Java 8 added java.time
- A Date field exposed through a getter lets callers modify your object: copy it (new Date(d.getTime())) or, better, store an Instant
- Date is a single long of epoch milliseconds
import java.text.SimpleDateFormat;import java.time.*;import java.util.*;public class Old { static String format(Date d) { SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd"); f.setLenient(false); return f.format(d); } static Date tomorrow() { Calendar c = Calendar.getInstance(); c.add(Calendar.DAY_OF_MONTH, 1); return c.getTime(); } static LocalDate modern(Date d) { return d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); }}- line 1 uses SimpleDateFormat, the core of Legacy Date, Calendar and SimpleDateFormat
- line 5 uses Date, the core of Legacy Date, Calendar and SimpleDateFormat
- line 6 uses SimpleDateFormat, the core of Legacy Date, Calendar and SimpleDateFormat
- line 10 uses Date, the core of Legacy Date, Calendar and SimpleDateFormat
- line 11 uses Date, the core of Legacy Date, Calendar and SimpleDateFormat
Read the full explanation (about 1 min)
java.util.Date, Calendar and SimpleDateFormat are the pre-Java-8 date classes. They are mutable, not thread safe and full of surprises; java.time replaces them, but old code and some libraries still use them.
A mental model
A pocket calculator from the 1990s with buttons in odd places: it works if you know the quirks, but a modern one is a better choice. The conversion cable between old and new is toInstant() and Date.from().
How it works
new Date() is a moment (milliseconds since 1970), not a date. Its toString prints it in the default time zone, which makes it look like a zoned value.
Calendar months start at 0 (January is 0), Calendar.getInstance() uses the default zone, and cal.add(Calendar.DAY_OF_MONTH, 1) mutates the object.
SimpleDateFormat("yyyy-MM-dd") formats and parses, but it is not thread safe: one shared static instance corrupts results under load. It also accepts invalid dates (February 30) by default unless you call setLenient(false).
Bridges: date.toInstant(), Date.from(instant), LocalDate.ofInstant(...), Timestamp.valueOf(localDateTime) for JDBC.
Going deeper
The year 2000 and January numbering bugs of these classes caused so many errors that Java 8 added java.time. In new code avoid the legacy classes except at the boundary of libraries (JDBC Timestamp, JPA 1 @Temporal).
A Date field exposed through a getter lets callers modify your object: copy it (new Date(d.getTime())) or, better, store an Instant.
java.sql.Date stores only a date but extends java.util.Date; Timestamp has nanoseconds and its equals is not symmetric with Date.
Under the hood
Date is a single long of epoch milliseconds. Calendar is a bag of fields (year, month, day...) that are lazily recomputed from the millisecond value, which is where much of its complexity and mutability comes from.
Common mistakes
- Sharing one
SimpleDateFormatbetween threads. - Using month numbers 1-12 with
Calendar. - Assuming
Dateknows a time zone. - Using
YYYYin the pattern.
How Unravel recognises it
When you paste code, Unravel looks for these signs of legacy date, calendar and simpledateformat. Spotting them yourself is a good habit when you read unfamiliar code:
- Types:
DateCalendarSimpleDateFormatGregorianCalendarTimeZoneTimestampDateFormat - Method calls:
Calendar.getInstance() - Created with new:
DateSimpleDateFormat
Part of a learning path
- I/O, networking and time ยท previous: Time zones, DST and Instant
Related concepts
Frequently asked questions
What is Legacy Date, Calendar and SimpleDateFormat?
java.util.Date, Calendar and SimpleDateFormat are the pre-Java-8 date classes. They are mutable, not thread safe and full of surprises; java.time replaces them, but old code and some libraries still use them.
What are the most common mistakes with Legacy Date, Calendar and SimpleDateFormat?
Sharing one SimpleDateFormat between threads. Using month numbers 1-12 with Calendar. Assuming Date knows a time zone. Using YYYY in the pattern.
How can I see Legacy Date, Calendar and SimpleDateFormat in real code?
Paste your own Java into Unravel and it will point out where legacy date, calendar and simpledateformat appears, explain how it works at the detail level you choose, and draw the flow of the code around it.