基础概念
-
时区(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
包
-
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 (是否闰年)
-
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
-
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
-
DateTimeFormatter
DateTimeFormatter
用于格式化和解析日期时间。它预定了一些格式,也可自定义格式:日期和时间模式字母
字母 含义 示例 y
年(Year) yyyy
→ 2023,yy
→ 23M
月(Month) M
→ 9,MM
→ 09,MMM
→ Sep,MMMM
→ Septemberd
月中的天数(Day of month) d
→ 5,dd
→ 05H
小时(24小时制,0-23) H
→ 13,HH
→ 13h
小时(12小时制,1-12) h
→ 1,hh
→ 01m
分钟(Minute) m
→ 30,mm
→ 30s
秒(Second) s
→ 45,ss
→ 45S
纳秒(Fraction of second) 假设纳秒数为123456789
S
→ 1,SS
→ 12,SSSS
→ 1234,SSSSSSSSS
→ 123456789n
纳秒(Nano of second) n
→ 123456789a
上午/下午(AM/PM) a
→ AM,a
→ PME
星期几(Day of week) E
→ Mon,EEEE
→ MondayD
年中的天数(Day of year) D
→ 273z
时区名称(Time zone name) z
→ CST,zzzz
→ China Standard TimeZ
时区偏移量(RFC 822) Z
→ +0800X
时区偏移量(ISO 8601) X
→ +08,XX
→ +0800,XXX
→ +08:00V
时区 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`
- 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]
- Duration 和 Period
-
Duration
用于计算时间间隔,精确到纳秒,适用于小时、分钟、秒等。Duration
内部使用两个字段来存储数据:seconds
:表示间隔的总秒数(long
类型)。nanos
:表示不足一秒的纳秒部分(int
类型,范围是0
到999,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秒
- 更多
还有一些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
-
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);
-
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
- 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
- 如果要处理日期时间的间隔问题,用
Duration
和Period
不支持Java8 (Java8以下的版本) 的情况:
-
如果只是显示时间,不涉及复杂的操作,用
Date
-
需要对日期时间做处理(比如加几天)用
Calendar
-
显示格式化或解析用
SimpleDateFormat