Java日期时间用法

基础概念

  • 时区(TimeZone) : 以英国伦敦的时间为标准时间,也称为UTC (Coordinated Universal Time,协调世界时),其他时区进行偏移。比如在同一个时刻:UTC 时间 2025-01-1 12:00, 在中国上海时区为Asia/Shanghai(也记为+08:00 也就是比UTC快8小时),时间就是2025-01-1 20:00, 在纽约时区为merica/New_York (-05:00), 显示的时间就是2025-01-1 07:00。

  • 时间戳(Timestamp): 从1970 年 1 月 1 日 00:00:00 UTC 开始计算的毫秒数。

    long timestamp = System.currentTimeMillis()  // 时间戳 (毫秒数)
    

    时间戳都是对应UTC的,只是同一个时间戳不同的时区会解析为不同的时间。

API使用

如果你的平台支持Java8(当然包括以上), 推荐用java.time包中的API, 如果不支持需要用旧的API,, 参看文章后部分Java8之前的时间API


Java8及以上

Java 8 引入 java.time

  1. LocalDate

    LocalDate 表示日期(没有时间信息), 他是不变类型,不能对它修改,api对它的修改其实会产生一个新的LocalDate对象。

    月:1 ~ 12 (1:1月, 12:12月)

    星期:1 ~ 7 (1:周一, 7:星期天)

    Example

    // 获取当前日期
    LocalDate currentDate = LocalDate.now();
    System.out.println("当前日期: " + currentDate);
    
    // 设定year, month, day
    LocalDate d1 = LocalDate.of(2025, 1, 1);
    int year = d1.getYear();
    int month = d1.getMonthValue();
    int day = d1.getDayOfMonth();
    DayOfWeek weekday = d1.getDayOfWeek();
    // 2025-1-1  星期3
    System.out.println(year + "-" + month + "-" + day + "  " + "星期" + weekday.getValue());
    
    LocalDate nextWeek = d1.plusWeeks(1);
    // 一周后的日期: 2025-01-08
    System.out.println("一周后的日期: " + nextWeek);
    
    
    System.out.println("--------------------------------");
    
    // ==============  plus or minus  ======================
    // 改变到2025-2-31, 这天不存在,变为:2025-2-28
    LocalDate the1 = LocalDate.of(2025, 1, 31).plusMonths(1);
    System.out.println(the1); // 2025-02-28
    LocalDate the2 = LocalDate.of(2025, 3, 31).minusMonths(1);
    System.out.println(the2); // 2025-02-28
    LocalDate the3 = LocalDate.of(2025, 10, 31).withMonth(2);
    System.out.println(the3); // 2025-02-28
    
    LocalDate the4 = LocalDate.of(2025, 1, 30).plusDays(2);
    System.out.println(the4); // 2025-02-01
    
    // 计算生日
    LocalDate birthday = LocalDate.of(2004, 2, 29);
    System.out.println("+0: " + birthday);  // +0: 2004-02-29
    LocalDate birthday1 = birthday.plusYears(1);
    System.out.println("+1: " + birthday1); // +1: 2005-02-28
    LocalDate birthday4 = birthday.plusYears(4);
    System.out.println("+4: " + birthday4); // +4: 2008-02-29
    
    // 其他...
    System.out.println(birthday4.isAfter(birthday1)); // ture  (是否比birthday1大)
    System.out.println(birthday4.isLeapYear());  // true   (是否闰年)
    
  2. LocalTime

    LocalTime表示时间(没有日期信息), 是不可变类型

    Example

    // 获取当前时间
    LocalTime currentTime = LocalTime.now();
    // 02:11:07.280574700    .后面的是纳秒
    System.out.println("当前时间: " + currentTime);
    
    // 创建特定时间
    LocalTime t1 = LocalTime.of(23, 30, 30);
    System.out.println(t1);  // 23:30:30
    
    // 23:30:30
    System.out.println(t1.getHour() + ":" + t1.getMinute() + ":" + t1.getSecond());
    
    // 时间加减操作
    LocalTime t2 = t1.plusSeconds(32);
    System.out.println(t2); // 23:31:02
    LocalTime t3 = t1.plusHours(1);
    System.out.println("一小时后的时间: " + t3); // 一小时后的时间: 00:30:30
    
  3. LocalDateTime
    LocalDateTime 表示一个不可变的日期时间对象

Example:

// 获取当前日期时间
LocalDateTime currentDateTime = LocalDateTime.now();
System.out.println("当前日期时间: " + currentDateTime);

// 创建特定日期时间
LocalDateTime dt1 = LocalDateTime.of(2025, 1, 31, 12, 30, 30);
System.out.println(dt1); // 2025-01-31T12:30:30

String resultStr = dt1.getYear() + "-" + dt1.getMonthValue() + "-" + dt1.getDayOfMonth() + " " +
    dt1.getHour() + ":" + dt1.getMinute() + ":" + dt1.getSecond();
System.out.println(resultStr); // 2025-1-31 12:30:30

LocalDateTime dt2 = dt1.plusDays(1);
System.out.println(dt2);  // 2025-02-01T12:30:30
LocalDateTime dt3 = dt1.plusMonths(1);
System.out.println(dt3);  // 2025-02-28T12:30:30
  1. DateTimeFormatter

    DateTimeFormatter 用于格式化和解析日期时间。它预定了一些格式,也可自定义格式:

    日期和时间模式字母

    字母含义示例
    y年(Year)yyyy → 2023, yy → 23
    M月(Month)M → 9, MM → 09, MMM → Sep, MMMM → September
    d月中的天数(Day of month)d → 5, dd → 05
    H小时(24小时制,0-23)H → 13, HH → 13
    h小时(12小时制,1-12)h → 1, hh → 01
    m分钟(Minute)m → 30, mm → 30
    s秒(Second)s → 45, ss → 45
    S纳秒(Fraction of second)假设纳秒数为123456789
    S → 1, SS → 12, SSSS → 1234, SSSSSSSSS → 123456789
    n纳秒(Nano of second)n → 123456789
    a上午/下午(AM/PM)a → AM, a → PM
    E星期几(Day of week)E → Mon, EEEE → Monday
    D年中的天数(Day of year)D → 273
    z时区名称(Time zone name)z → CST, zzzz → China Standard Time
    Z时区偏移量(RFC 822)Z → +0800
    X时区偏移量(ISO 8601)X → +08, XX → +0800, XXX → +08:00
    V时区 ID(Zone ID)V → America/New_York


​ Example:

LocalDate d1 = LocalDate.of(2000, 1, 1);

// 格式化日期
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
System.out.println(d1.format(formatter1)); // 2000/01/01

//  解析日期
LocalDate d2 = LocalDate.parse("2000/12/12", formatter1);
System.out.println(d2); // 2000-12-12

LocalDateTime dt2 = LocalDateTime.of(2000, 1, 1, 12, 30, 31);

// 格式化日期时间
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(dt2.format(formatter2)); // 2000-01-01 12:30:31

// 解析日期时间
LocalDateTime dt3 = LocalDateTime.parse("2021-10-15 12:30:45", formatter2);
System.out.println(dt3); // 2021-10-15T12:30:45`
  1. ZonedDateTime

ZonedDateTime 处理带时区的日期时间。 LocalDate, LocaTime, LocalDateTime名字中Local就是不包含时区信息。

ZonedDateTime用法和LocalDateTime类似,只是多了Timezone

//  查看当前时刻在纽约时区的显示时间
ZonedDateTime theTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("纽约当前时间: " + theTime);

// 时区转换
ZonedDateTime londonTime = ZonedDateTime.of(2025, 1, 1, 12, 0, 0,
                                            0, ZoneId.of("Europe/London"));
System.out.println("伦敦时间: " + londonTime); // 伦敦时间: 2025-01-01T12:00Z[Europe/London]

// 转换为纽约时区
ZonedDateTime newYorkTime = londonTime.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("纽约时间: " + newYorkTime); // 纽约时间: 2025-01-01T07:00-05:00[America/New_York]

// 转换为上海时区
ZonedDateTime shanghaiTime = londonTime.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
System.out.println("上海时间: " + shanghaiTime); // 上海时间: 2025-01-01T20:00+08:00[Asia/Shanghai]
  1. DurationPeriod
  • Duration 用于计算时间间隔,精确到纳秒,适用于小时、分钟、秒等。

    Duration 内部使用两个字段来存储数据:

    • seconds:表示间隔的总秒数(long 类型)。
    • nanos:表示不足一秒的纳秒部分(int 类型,范围是 0999,999,999)。

    方法说明:
    假定duration存储的是90061.123456789秒, 即: Duration duration = Duration.ofDays(1).plusHours(1).plusMinutes(1).plusSeconds(1).plusNanos(123456789);

    • getSeconds()返回存储的秒部分,即90061, getNano()返回的是存储的纳秒部分,即123456789
    • toX(): 返回总的秒数转为相应的单位的值, 比如 toDays()返回1, toHours()返回25,toSeconds()返回90061, getSeconds()和toSeconds()结果是一样的
    • 在Java9+ 增加了toXPart(), 这是把总的秒转为x天x时x分x秒x纳秒, 比如toDaysPart()返回1, toHoursPart()返回1, toSecondsPart()返回1

  • Period 用于计算日期间隔,精确到天,适用于年、月、日等。

    Period 内部使用三个字段来存储数据:

    • years:表示间隔的年数(int 类型)。
    • months:表示间隔的月数(int 类型)。
    • days:表示间隔的天数(int 类型)

Example

LocalTime start = LocalTime.of(10, 0, 0);
LocalTime end = LocalTime.of(12, 30, 20);
Duration duration = Duration.between(start, end);
// Output: 时间间隔: 2 小时  30 分  20秒
System.out.println("时间间隔: " + duration.toHours() + " 小时  " + duration.toMinutes() % 60 + " 分  " 
                   +duration.getSeconds() % 60 + "秒");

// 在java9+中
// Output: 时间间隔: 2 小时  30 分  20秒
System.out.println("时间间隔: " + (duration.toDaysPart() * 24 + duration.toHoursPart()) + " 小时  " + duration.toMinutesPart() + " 分  "
                   + duration.toSecondsPart() + "秒");

// 时间跨天
duration = Duration.between(end, start);
// Output: 时间间隔: -2 小时  -30 分  -20秒
System.out.println("时间间隔: " + duration.toHours() + " 小时  " + duration.toMinutes() % 60 + " 分  "
                   +duration.getSeconds() % 60 + "秒");

LocalDate start1 = LocalDate.of(2023, 1, 1);
LocalDate end1 = LocalDate.of(2025, 3, 10);
Period period = Period.between(start1, end1);
// Output: 间隔: 2 年, 2 个月, 9 天
System.out.println("间隔: " + period.getYears() + " 年, " + period.getMonths() + " 个月, "
                   + period.getDays() + " 天");

// 也可以直接创建
// 创建 2 小时 30 分钟的 Duration
Duration duration1 = Duration.ofHours(2).plusMinutes(30);
System.out.println("Duration: " + duration1); // 输出: PT2H30M  (表示 2 小时 30分钟)

// 创建 3 年 9 个月 14 天的 Period
Period period1 = Period.of(3, 9, 14);
System.out.println("Period: " + period1); // 输出: P3Y9M14D (表示 3 年 9 个月 14 天)

Exmaple: 计算两个日期时间(LocalDateTime)的间隔

// ************* 计算两个 LocalDateTime 之间的间隔 *************
LocalDateTime start = LocalDateTime.of(2020, 1, 1, 23, 59, 59);
LocalDateTime end = LocalDateTime.of(2022, 1, 2, 0, 0, 0);

Period period = Period.between(start.toLocalDate(), end.toLocalDate());
Duration duration = Duration.between(start.toLocalTime(), end.toLocalTime());
// 如果结束时间比开始时间早,需要调整 Duration
if (duration.isNegative()) {
    duration = duration.plusDays(1);
    period = period.minusDays(1);
}

// 提取 Duration 中的小时、分钟、秒
long hours = duration.toHours();
long minutes = duration.toMinutes() % 60;
long seconds = duration.getSeconds() % 60;

// 输出结果
String result = period.getYears() + "年" + period.getDays() + "天" +
    hours + "小时" + minutes + "分钟" + seconds + "秒";
System.out.println(result);  // Output: 2年0天0小时0分钟1秒
  1. 更多

还有一些API及用法, 几乎平常时间相关的所有功都能直接用API实现。

介绍下Instant

Instant 用于记录时间戳(timestamp)表示的时间是基于 UTC 的,与时区无关

Example:

// Instant now = Instant.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(
    LocalDateTime.of(2000, 1, 1, 1, 1),  ZoneId.of("UTC"));
Instant now = Instant.ofEpochSecond(zonedDateTime.toEpochSecond());
System.out.println(now); // 2000-01-01T01:01:00Z

// 增加 1 小时
Instant plus1Hour = now.plus(1, ChronoUnit.HOURS);
System.out.println("增加 1 小时后: " + plus1Hour); // 增加 1 小时后: 2000-01-01T02:01:00Z

// 增加 30 分钟
Instant plus30Minutes = now.plus(30, ChronoUnit.MINUTES);
System.out.println("增加 30 分钟后: " + plus30Minutes); // 增加 30 分钟后: 2000-01-01T01:31:00Z

// 转换为 LocalDateTime(需要指定时区) ZoneId.systemDefault()在本机是Asian/Shanghai
LocalDateTime localDateTime = LocalDateTime.ofInstant(now, ZoneId.systemDefault());
System.out.println("LocalDateTime: " + localDateTime); // LocalDateTime: 2000-01-01T09:01

Java8之前API
  1. java.util.Date

    可以把它理解为一个时间戳的包装, 所以只能设置时间戳(setYear等方法已弃用)

    Date d = new Date();
    System.out.println(d);
    
    // d.setDate(30);  // Deprecated
    // int day = d.getDate(); // Deprecated,  不要这样做
    
    // 获取时间戳
    long timestamp = d.getTime();
    System.out.println("Timestamp: " + timestamp);
    
    d.setTime(timestamp - 24 * 60 * 60 * 1000); // 减去一天的毫秒,也就是前一天
    System.out.println(d);
    
  2. java.util.Calendar

Calendar可以设置和处理日期(比如加几天),但有几点要注意

  • 月份是0~11 (0:1月, 11:12月)
  • 星期是1~7 (1:星期日, 7:星期六)
  • setx()方法的延迟生效
  • 改变到一个不存在的日期(比如2025-2-30)的情况

Example:

Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime());

// 2025-01-01 12:00:00
calendar.set(2025, 0, 1, 12, 0, 0);
// calendar.set(Calendar.MONTH, 0);   // 1月
// calendar.set(Calendar.MONTH, Calendar.JANUARY);  // 1月

System.out.println("当前年份: " + calendar.get(Calendar.YEAR));        // 当前年份: 2025
// 月份从 0 开始 即0 ~ 11
System.out.println("当前月份: " + (calendar.get(Calendar.MONTH) + 1));  // 当前月份: 1
System.out.println("当前日期: " + calendar.get(Calendar.DAY_OF_MONTH)); // 当前日期: 1
System.out.println("当前时: " + calendar.get(Calendar.HOUR_OF_DAY));   // 当前时: 12
System.out.println(calendar.get(Calendar.DAY_OF_WEEK));               //  4

calendar.add(Calendar.DAY_OF_MONTH, 7); // 增加 7 天
System.out.println("一周后的日期: " + calendar.get(Calendar.YEAR) + "-" 
                   + (calendar.get(Calendar.MONTH) + 1)
                   + "-" + calendar.get(Calendar.DAY_OF_MONTH));    // 一周后的日期: 2025-01-8

// add方法用于对指定字段进行加减操作,并会自动调整更高位的字段,roll方法也对指定字段进行加减操作,但不会影响更高位的字段
// 如果要减, 用add(Calendar.DAY_OF_MONTH, -32)
// 2025-01-01 12:00:00
calendar.set(2025, 0, 1, 12, 0, 0);
calendar.add(Calendar.DAY_OF_MONTH, 32); // 增加 32 天
System.out.println(calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH) + 1)
                   + "-" + calendar.get(Calendar.DAY_OF_MONTH));  // 2025-2-2
// roll
calendar.set(2025, 0, 1, 12, 0, 0);
calendar.roll(Calendar.DAY_OF_MONTH, 32); // 增加 32 天
System.out.println(calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH) + 1)
                   + "-" + calendar.get(Calendar.DAY_OF_MONTH)); // 2025-1-2

改变到一个不存在的日期的情况 Example

Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

// 2025-01-01 12:00:00
calendar.set(2025, 0, 31, 12, 0, 0);
// 设置为2月, 2月没有31日,实际变为3月3日, 2月只有28天, 设定31日,多了3天,所以向后推3天
calendar.set(Calendar.MONTH, 1);
System.out.println(sdf.format(calendar.getTime()));   // 2025-03-03


// add的情况会变为2月的最后一天
calendar.set(2025, 0, 31, 12, 0, 0);
calendar.add(Calendar.MONTH, 1);
System.out.println(sdf.format(calendar.getTime()));  // 2025-02-28

setx()方法的延迟生效, 直到用calendar.get(), 或add, roll等才生效

Example

//  set的Lazy Computation
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

Calendar c1 = Calendar.getInstance();
c1.set(2025, 0, 31);
c1.set(Calendar.MONTH, 1);
c1.getTime();       // 前面的set生效了
c1.set(Calendar.DAY_OF_MONTH, 1);
System.out.println(sdf.format(c1.getTime()));  // 2025-03-01

Calendar c2 = Calendar.getInstance();
c2.set(2025, 0, 31);  // set后没有立即生效
c2.set(Calendar.MONTH, 1);
c2.set(Calendar.DAY_OF_MONTH, 1);
// 由于前面的没有立即生效,所以最终前3个set相当于一个set:   c2.set(2025, 1, 1);
System.out.println(sdf.format(c2.getTime()));  // 2025-02-01
  1. java.text.SimpleDateFormat

SimpleDateFormat 用于格式化和解析日期。

日期和时间模式字母

字母含义示例
y年(Year)yyyy → 2023
M月(Month)MM → 09, MMM → Sep
d月中的天数(Day in month)dd → 15
H小时(24小时制,0-23)HH → 13
h小时(12小时制,1-12)hh → 01
a上午/下午(AM/PM)a → AM 或 PM
m分钟(Minute)mm → 30
s秒(Second)ss → 45
S毫秒(Millisecond)SSS → 123
E星期几(Day in week)EEE → Mon, EEEE → Monday
D年中的天数(Day in year)DDD → 273
z时区(Time zone)z → CST, zzzz → China Standard Time
Z时区偏移量(RFC 822)Z → +0800
X时区偏移量(ISO 8601)X → +08, XX → +0800, XXX → +08:00

Example:

// ============= 格式化输出
Calendar c = Calendar.getInstance();
c.set(2025, 0, 1, 12, 0, 0);

// 'T':   ''中的内容可任意写
SimpleDateFormat sdf0 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.'SSSz");
System.out.println(sdf0.format(c.getTime()));       // output: 2025-01-01T12:00:00.585CST

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
// CST: China Standard Time  (中国标准时间,UTC+8, 也就是显示的时间比UTC的时间要大8小时)
System.out.println(sdf.format(c.getTime()));        // 2025-01-01 12:00:00 CST
sdf.applyPattern("yyyy-MM-dd HH:mm:ss Z");
System.out.println(sdf.format(c.getTime()));        // output: 2025-01-01 12:00:00 +0800
sdf.applyPattern("yyyy-MM-dd HH:mm:ss X");
System.out.println(sdf.format(c.getTime()));        // output: 2025-01-01 12:00:00 +08
sdf.applyPattern("yyyy-MM-dd HH:mm:ss XX");
System.out.println(sdf.format(c.getTime()));        // output: 2025-01-01 12:00:00 +0800
sdf.applyPattern("yyyy-MM-dd HH:mm:ss XXX");
System.out.println(sdf.format(c.getTime()));        // output: 2025-01-01 12:00:00 +08:00
System.out.println("-------------------------------------------");

// ============= 解析  ================
String timeStr =  " 2025-01-01 12:00:00.585  +0800";
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
    Date parsed = sdf1.parse(timeStr);
    System.out.println(sdf1.format(parsed));      // output: 2025-01-01 12:00:00
} catch (ParseException e) {
    throw new RuntimeException(e);
}
System.out.println("----------------------------------------------");

// ============= 如果需要考虑时区
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
Calendar calendar2 = Calendar.getInstance();
// 下面设置时间是相对于UTC时区的,否则就是默认(操作系统决定的)的时区
calendar2.setTimeZone(TimeZone.getTimeZone("UTC")); 
calendar2.set(2025, 0, 1, 12, 0, 0);
// sdf2没有指定timezone, 默认是根据系统确定的,当前是Asia/Shanghai
System.out.println(sdf2.format(calendar2.getTime()));        // output: 2025-01-01 20:00:00 +0800
sdf2.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(sdf2.format(calendar2.getTime()));        // output: 2025-01-01 12:00:00 +0000
sdf2.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println(sdf2.format(calendar2.getTime()));        // output: 2025-01-01 07:00:00 -0500

SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssX");
sdf3.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(sdf3.format(calendar2.getTime()));       // output: 2025-01-01 12:00:00Z (Z代表UTC)

总结

支持Java8的情况下,用java.time包中的API,当然也能用Date, Calendar等原来就有的。

  • 如果只操作日期(不含时间)用LocalDate
  • 如果只操作时间(不含日期)用LocalTime
  • 如果要操作日期和时间,用 LocalDateTime
  • 如果需要加入时区(国际化),用ZonedDateTime
  • 如果要显示格式化或解析, 用DateTimeFormatter
  • 如果要处理日期时间的间隔问题,用DurationPeriod

不支持Java8 (Java8以下的版本) 的情况:

  • 如果只是显示时间,不涉及复杂的操作,用Date

  • 需要对日期时间做处理(比如加几天)用 Calendar

  • 显示格式化或解析用 SimpleDateFormat

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大勇学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值