1. 核心时间日期API发展历程
1.1 各版本主要API
- Java 8之前:
Date、Calendar、SimpleDateFormat - Java 8+:
java.time包 (JSR-310) - 推荐: 新项目统一使用
java.time包
2. Java 8+ 时间日期API (java.time)
2.1 核心类概览
| 类名 | 描述 | 示例 |
|---|---|---|
LocalDate |
日期 (年月日) | 2023-10-25 |
LocalTime |
时间 (时分秒纳秒) | 14:30:15.123 |
LocalDateTime |
日期时间 | 2023-10-25T14:30:15 |
ZonedDateTime |
带时区的日期时间 | 2023-10-25T14:30:15+08:00[Asia/Shanghai] |
Instant |
时间戳 (Unix时间) | 1698215415 |
Duration |
时间间隔 (秒、纳秒) | PT1H30M |
Period |
日期间隔 (年、月、日) | P1Y2M3D |
2.2 基础使用示例
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;public class DateTimeExamples {public static void main(String[] args) {// 1. 获取当前时间LocalDate currentDate = LocalDate.now();LocalTime currentTime = LocalTime.now();LocalDateTime currentDateTime = LocalDateTime.now();System.out.println("当前日期: " + currentDate);System.out.println("当前时间: " + currentTime);System.out.println("当前日期时间: " + currentDateTime);// 2. 创建特定时间LocalDate specificDate = LocalDate.of(2023, 10, 25);LocalTime specificTime = LocalTime.of(14, 30, 15);LocalDateTime specificDateTime = LocalDateTime.of(2023, 10, 25, 14, 30, 15);// 3. 日期计算LocalDate tomorrow = currentDate.plusDays(1);LocalDate lastWeek = currentDate.minusWeeks(1);LocalDate nextMonth = currentDate.plusMonths(1);// 4. 日期比较boolean isAfter = specificDate.isAfter(currentDate);boolean isBefore = specificDate.isBefore(currentDate);boolean isEqual = specificDate.isEqual(currentDate);// 5. 获取日期组成部分int year = currentDate.getYear();Month month = currentDate.getMonth();int dayOfMonth = currentDate.getDayOfMonth();DayOfWeek dayOfWeek = currentDate.getDayOfWeek();}
}
2.3 时区处理
public class TimeZoneExamples {public static void main(String[] args) {// 1. 时区日期时间ZonedDateTime beijingTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));ZonedDateTime newYorkTime = ZonedDateTime.now(ZoneId.of("America/New_York"));System.out.println("北京时间: " + beijingTime);System.out.println("纽约时间: " + newYorkTime);// 2. 时区转换ZonedDateTime convertedTime = beijingTime.withZoneSameInstant(ZoneId.of("UTC"));System.out.println("UTC时间: " + convertedTime);// 3. 获取所有可用时区// ZoneId.getAvailableZoneIds().forEach(System.out::println);// 4. Instant (时间戳)Instant instant = Instant.now();System.out.println("当前时间戳: " + instant.toEpochMilli());// 从时间戳创建Instant fromTimestamp = Instant.ofEpochMilli(1698215415000L);}
}
2.4 时间间隔计算
public class DurationPeriodExamples {public static void main(String[] args) {// 1. Duration - 时间间隔 (精确到纳秒)LocalTime startTime = LocalTime.of(9, 0, 0);LocalTime endTime = LocalTime.of(17, 30, 0);Duration duration = Duration.between(startTime, endTime);System.out.println("工作时间: " + duration.toHours() + "小时");System.out.println("总分钟数: " + duration.toMinutes());// 2. Period - 日期间隔 (年、月、日)LocalDate birthDate = LocalDate.of(1990, 5, 15);LocalDate currentDate = LocalDate.now();Period age = Period.between(birthDate, currentDate);System.out.println("年龄: " + age.getYears() + "年" + age.getMonths() + "月" + age.getDays() + "天");// 3. 使用ChronoUnit计算差值long daysBetween = ChronoUnit.DAYS.between(birthDate, currentDate);long monthsBetween = ChronoUnit.MONTHS.between(birthDate, currentDate);System.out.println("总天数: " + daysBetween);System.out.println("总月数: " + monthsBetween);}
}
2.5 格式化与解析
public class FormattingExamples {public static void main(String[] args) {// 1. 预定义格式器LocalDateTime now = LocalDateTime.now();String isoFormat = now.format(DateTimeFormatter.ISO_DATE_TIME);String basicFormat = now.format(DateTimeFormatter.BASIC_ISO_DATE);System.out.println("ISO格式: " + isoFormat);System.out.println("基本ISO格式: " + basicFormat);// 2. 自定义格式DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String customFormat = now.format(customFormatter);System.out.println("自定义格式: " + customFormat);// 3. 解析字符串为日期String dateString = "2023-10-25 14:30:15";LocalDateTime parsedDateTime = LocalDateTime.parse(dateString, customFormatter);System.out.println("解析结果: " + parsedDateTime);// 4. 本地化格式DateTimeFormatter germanFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.GERMAN);String germanFormat = now.format(germanFormatter);System.out.println("德语格式: " + germanFormat);}
}
3. 传统日期API (Java 8之前)
3.1 Date 和 Calendar
import java.util.*;
import java.text.SimpleDateFormat;public class LegacyDateTime {public static void main(String[] args) throws Exception {// 1. Date 类Date now = new Date();System.out.println("当前时间: " + now);// 2. Calendar 类Calendar calendar = Calendar.getInstance();calendar.set(2023, Calendar.OCTOBER, 25, 14, 30, 15);Date specificDate = calendar.getTime();// 日历计算calendar.add(Calendar.DAY_OF_MONTH, 7); // 加7天calendar.add(Calendar.MONTH, -1); // 减1个月// 3. SimpleDateFormatSimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String formatted = sdf.format(now);System.out.println("格式化: " + formatted);// 解析Date parsedDate = sdf.parse("2023-10-25 14:30:15");// 注意: SimpleDateFormat 非线程安全!}
}
3.2 线程安全问题
// 错误的用法 - 多线程环境下会有问题
public class UnsafeDateFormat {private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");// 多线程调用时会抛出异常或返回错误结果
}// 正确的用法 - 使用ThreadLocal
public class SafeDateFormat {private static final ThreadLocal<SimpleDateFormat> threadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));public static String format(Date date) {return threadLocal.get().format(date);}
}
4. 新旧API转换
4.1 互相转换方法
public class ConversionExamples {public static void main(String[] args) {// 1. Date -> Instant -> LocalDateTimeDate oldDate = new Date();Instant instant = oldDate.toInstant();LocalDateTime newDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());// 2. LocalDateTime -> Instant -> DateLocalDateTime localDateTime = LocalDateTime.now();Instant instant2 = localDateTime.atZone(ZoneId.systemDefault()).toInstant();Date newDate = Date.from(instant2);// 3. Calendar -> ZonedDateTimeCalendar calendar = Calendar.getInstance();ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());// 4. 时间戳转换long timestamp = System.currentTimeMillis();Instant instantFromTimestamp = Instant.ofEpochMilli(timestamp);LocalDateTime dateTimeFromTimestamp = LocalDateTime.ofInstant(instantFromTimestamp, ZoneId.systemDefault());}
}
5. 最佳实践和注意事项
5.1 推荐做法
public class BestPractices {// 1. 使用不可变对象public void processOrder(LocalDateTime orderTime) {LocalDateTime processedTime = orderTime.plusMinutes(30); // 返回新对象// orderTime 保持不变}// 2. 使用常量格式器private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// 3. 明确处理时区public ZonedDateTime convertToTimezone(LocalDateTime localDateTime, String zoneId) {return localDateTime.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of(zoneId));}// 4. 使用合适的类public void demonstrateAppropriateUse() {// 只需要日期LocalDate birthday = LocalDate.of(1990, 5, 15);// 只需要时间LocalTime meetingTime = LocalTime.of(14, 30);// 需要日期时间但不需要时区LocalDateTime createdTime = LocalDateTime.now();// 需要时区信息ZonedDateTime publishTime = ZonedDateTime.now(ZoneId.of("UTC"));// 时间戳存储Instant timestamp = Instant.now();}
}
5.2 常见陷阱
public class CommonPitfalls {public static void main(String[] args) {// 1. 不要使用已废弃的构造方法// Date date = new Date(123, 9, 25); // 已废弃!// 2. Calendar 的月份从0开始Calendar cal = Calendar.getInstance();cal.set(2023, 9, 25); // 10月 (0=1月, 9=10月)// 3. 时区处理要一致LocalDateTime local = LocalDateTime.now();// 错误的时区假设// ZonedDateTime zoned = local.atZone(ZoneId.of("UTC")); // 正确的做法ZonedDateTime zoned = local.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC"));// 4. 格式化解析要匹配String input = "2023/10/25";// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // 错误!DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); // 正确}
}
总结
- 新项目优先使用
java.time包 - 根据需求选择合适的类:
LocalDate、LocalTime、LocalDateTime、ZonedDateTime - 时区处理要明确, 避免隐式时区转换
- 格式化器尽量重用, 避免重复创建
- 注意线程安全问题, 特别是传统API
- 利用不可变特性, 避免意外的状态修改
这套新的日期时间API设计更加合理,解决了传统API的很多问题,是Java日期时间处理的现代解决方案。