Problems with java.util.Date and Migration to java.time API
The article explains the design flaws of java.util.Date, such as mutability, misleading naming, and timezone issues, and provides a step‑by‑step guide to replace it and related APIs with java.time classes like Instant, LocalDateTime, and ZonedDateTime, including code examples.
1. What’s wrong with java.util.Date?
java.util.Date is a flawed type; most of its functionality was deprecated in Java 1.1 but it is still widely used.
Misleading name: It represents an instant, not a calendar date, so it should be called Instant , just like its java.time counterpart.
Non‑final: It can be subclassed (e.g., java.sql.Date ), which adds confusion.
Mutable: Methods such as setTime make it mutable, forcing developers to create defensive copies.
Implicit system time‑zone in toString() : The default string representation uses the local time‑zone, confusing many developers.
Month index starts at 0: Copied from C, this causes off‑by‑one errors.
Year based on 1900: Also inherited from C, reducing readability.
Unclear method names: getDate() returns the day of month, while getDay() returns the day of week.
Leap‑second handling is vague: The documentation says seconds can be 0‑61, but most code assumes 0‑59.
Lenient parsing: Dates like “January 32” are accepted and interpreted as February 1, which is rarely useful.
2. Why change it?
Our static‑analysis rule treats usage of java.util.Date as a defect that must be fixed before a release, so the code base needs to be updated.
3. How to migrate?
Replace java.util.Date and java.sql.Date with appropriate java.time classes such as Instant , LocalDateTime , LocalDate , LocalTime , or ZonedDateTime depending on the semantics of the field.
3.1 Update data‑object classes
Identify whether a field represents a date, a time, a date‑time, or a timestamp and change its type accordingly:
If it represents both date and time → LocalDateTime
If it represents only a date → LocalDate
If it represents only a time → LocalTime
If it represents a timestamp with zone → Instant or ZonedDateTime
3.2 Refactor DateUtil methods
Replace usages of new Date() and Calendar.getInstance().getTime() with the modern API.
Date nowDate = new Date();
Date nowCalendarDate = Calendar.getInstance().getTime();After migration:
// Using Instant – represents a point in time, similar to the old Date
Instant nowInstant = Instant.now();
// For a full date‑time without zone information
LocalDateTime nowLocalDateTime = LocalDateTime.now();
// When zone information is required
ZonedDateTime nowZonedDateTime = ZonedDateTime.now();
// Convert back to java.util.Date if legacy APIs still need it
Date nowFromDateInstant = Date.from(nowInstant);
// Convert to java.sql.Timestamp
java.sql.Timestamp nowFromInstant = java.sql.Timestamp.from(nowInstant);3.3 Rewrite utility methods
a. dateFormat
public static String dateFormat(Date date, String dateFormat) {
SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
return formatter.format(date);
}Using java.time :
public static String dateFormat(LocalDateTime date, String dateFormat) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
return date.format(formatter);
}b. addSecond / addMinute / addHour / addDay / addMonth / addYear
public static Date addSecond(Date date, int second) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(13, second);
return calendar.getTime();
}
// ... similar methods for minute, hour, day, month, yearAfter migration:
public static LocalDateTime addSecond(LocalDateTime date, int second) {
return date.plusSeconds(second);
}
public static LocalDateTime addMinute(LocalDateTime date, int minute) {
return date.plusMinutes(minute);
}
public static LocalDateTime addHour(LocalDateTime date, int hour) {
return date.plusHours(hour);
}
public static LocalDateTime addDay(LocalDateTime date, int day) {
return date.plusDays(day);
}
public static LocalDateTime addMonth(LocalDateTime date, int month) {
return date.plusMonths(month);
}
public static LocalDateTime addYear(LocalDateTime date, int year) {
return date.plusYears(year);
}c. dateToWeek
public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"周日","周一","周二","周三","周四","周五","周六"};
public static String dateToWeek(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return WEEK_DAY_OF_CHINESE[cal.get(7) - 1];
}Using java.time :
public static String dateToWeek(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
return WEEK_DAY_OF_CHINESE[dayOfWeek.getValue() % 7];
}d. getStartOfDay / getEndOfDay
public static Date getStartTimeOfDay(Date date) {
if (date == null) return null;
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
}
public static Date getEndTimeOfDay(Date date) {
if (date == null) return null;
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
}After migration:
public static LocalDateTime getStartTimeOfDay(LocalDateTime date) {
if (date == null) return null;
// 00:00 of the given day
return date.toLocalDate().atStartOfDay();
}
public static LocalDateTime getEndTimeOfDay(LocalDateTime date) {
if (date == null) return null;
// 23:59:59.999999999 of the given day
return date.toLocalDate().atTime(LocalTime.MAX);
}e. betweenStartAndEnd
public static Boolean betweenStartAndEnd(Date nowTime, Date beginTime, Date endTime) {
Calendar date = Calendar.getInstance();
date.setTime(nowTime);
Calendar begin = Calendar.getInstance();
begin.setTime(beginTime);
Calendar end = Calendar.getInstance();
end.setTime(endTime);
return date.after(begin) && date.before(end);
}Using java.time :
public static Boolean betweenStartAndEnd(Instant nowTime, Instant beginTime, Instant endTime) {
return nowTime.isAfter(beginTime) && nowTime.isBefore(endTime);
}4. Summary
Although the migration is conceptually simple, it touches many layers of the code base; missing a single change can cause compilation errors or runtime failures, so careful, comprehensive refactoring is required.
5. Call to action
If this guide helped you, consider liking, sharing, or following the author’s technical community for more deep‑dive articles.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.