Kotlin日期相关处理

前言

在 Kotlin(基于 JVM)中,日期时间处理主要依赖 Java 8 引入的 java.time(JSR-310),它取代了老旧且问题较多的 java.util.DateCalendar

Kotlin 本身不提供新的日期类,但能以更简洁、安全的方式使用这些 Java 类。

核心类一览

类名 说明
LocalDate 仅日期(年-月-日),无时区
YearMonth 仅年月,无时区
LocalTime 仅时间(时-分-秒-纳秒),无时区
LocalDateTime 日期 + 时间,无时区
ZonedDateTime 日期 + 时间 + 时区(含夏令时规则)
OffsetDateTime 日期 + 时间 + UTC 偏移量
Instant UTC 时间戳(自 1970-01-01T00:00:00Z 起)
Duration 基于秒/纳秒的时间量(如 2 小时)
Period 基于年-月-日的日历量(如 1 年 3 个月)
DateTimeFormatter 线程安全的格式化与解析工具
ZoneId / ZoneOffset 表示时区或偏移量

注意:

遗留类如 java.util.DateCalendarSimpleDateFormat 已不推荐使用。

日期与字符串互转

方式 优点 缺点
SimpleDateFormat 简单直接,兼容老代码 非线程安全,API 设计老旧
java.time + Date.from() / Date.toInstant() 线程安全、功能强大、可读性好 代码稍长,需理解新时间 API

使用 java.time(推荐)

Date 转 LocalDateTime

1
2
3
4
val localDateTime = date
.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime()

LocalDateTime → 字符串

1
2
3
4
5
6
7
fun dateToStringModern(localDateTime: LocalDateTime, pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
return localDateTime.format(DateTimeFormatter.ofPattern(pattern))
}

// 使用
val localDateTime = Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
println(dateToStringModern(localDateTime, "yyyy-MM-dd")) // 2025-12-08

字符串 → LocalDateTime

1
2
3
4
5
6
7
fun stringToDateModern(str: String, pattern: String = "yyyy-MM-dd HH:mm:ss"): LocalDateTime {
val formatter = DateTimeFormatter.ofPattern(pattern)
return LocalDateTime.parse(str, formatter)
}

// 使用
val date = stringToDateModern("2025-12-08 14:30:00")

LocalDateTime → Date

1
Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())

使用 java.util.Date(遗留方式)

Date → 字符串

1
2
3
4
5
6
7
fun dateToString(date: Date, pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
return SimpleDateFormat(pattern, Locale.getDefault()).format(date)
}

// 使用
println(dateToString(Date())) // 2025-12-08 10:30:45
println(dateToString(Date(), "yyyy/MM/dd")) // 2025/12/08

字符串 → Date

1
2
3
4
5
6
7
fun stringToDate(str: String, pattern: String = "yyyy-MM-dd HH:mm:ss"): Date? {
return try {
SimpleDateFormat(pattern, Locale.getDefault()).parse(str)
} catch (e: Exception) {
null
}
}

年月与字符串互转

1
2
3
4
5
6
// 年月 → 字符串
val yearMonthStr = YearMonth.now().format(DateTimeFormatter.ofPattern("yyyy-MM"))
// 2025-12

// 字符串 → 年月
val yearMonth = YearMonth.parse("2025-06", DateTimeFormatter.ofPattern("yyyy-MM"))

获取某日所在周起止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 获取某天所在周周一
*/
fun LocalDate.weekStartMonday(): LocalDate {
val daysSinceMonday = this.dayOfWeek.value - DayOfWeek.MONDAY.value
return this.minusDays(daysSinceMonday.toLong())
}

/**
* 获取某天所在周周日
*/
fun LocalDate.weekEndSunday(): LocalDate {
val daysToAdd = DayOfWeek.SUNDAY.value - this.dayOfWeek.value
return this.plusDays(daysToAdd.toLong())
}

// 使用
val today = LocalDate.now()
println(today.weekStartMonday()) // 本周周一
println(today.weekEndSunday()) // 本周周日

获取某月起止

1
2
3
4
5
6
7
fun getFirstAndLastDay(dateStr: String, pattern: String = "yyyy-MM"): Pair<LocalDate, LocalDate> {
val yearMonth = YearMonth.parse(dateStr, DateTimeFormatter.ofPattern(pattern))
return yearMonth.atDay(1) to yearMonth.atEndOfMonth()
}

// 使用
val (firstDay, lastDay) = getFirstAndLastDay("2025-06")

获取两个日期之间的所有日期

1
2
3
4
5
val (firstDay, lastDay) = getFirstAndLastDay("2025-06")

val allDayList = generateSequence(firstDay) { it.plusDays(1) }
.takeWhile { !it.isAfter(lastDay) }
.toList()

LocalDate 详解

LocalDate 是 Java 8 引入的 java.time 包中用于表示不带时间、不带时区的日期(年-月-日) 的不可变类。

创建 LocalDate

1
2
3
4
5
6
7
val today = LocalDate.now()                     // 当前日期
val date1 = LocalDate.of(2025, 12, 8) // 指定年月日
val date2 = LocalDate.parse("2025-12-08") // ISO 格式解析
val date3 = LocalDate.ofYearDay(2025, 342) // 年份 + 第几天

// 自定义格式解析
val date4 = LocalDate.parse("2025年12月08日", DateTimeFormatter.ofPattern("yyyy年MM月dd日"))

格式化为字符串

1
2
3
4
5
6
7
8
val date = LocalDate.of(2025, 12, 8)

date.toString() // "2025-12-08"
date.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")) // "2025年12月08日"
date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) // "08/12/2025"

// 本地化格式
date.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.CHINA))

获取字段

1
2
3
4
5
6
7
8
9
10
val date = LocalDate.of(2025, 12, 8)

date.year // 2025
date.monthValue // 12
date.month // DECEMBER
date.dayOfMonth // 8
date.dayOfWeek // MONDAY
date.dayOfYear // 342
date.lengthOfMonth() // 31
date.isLeapYear // false

日期计算(加减)

所有操作返回新对象(不可变):

1
2
3
4
5
6
7
8
9
10
11
12
13
val date = LocalDate.of(2025, 12, 8)

// 加
date.plusDays(1) // 2025-12-09
date.plusMonths(1) // 2026-01-08
date.plusYears(1) // 2026-12-08

// 减
date.minusDays(1) // 2025-12-07
date.minusMonths(2) // 2025-10-08

// 使用 Period
date.plus(Period.ofWeeks(2)) // +14 天

比较日期

1
2
3
4
5
6
7
8
9
10
11
12
val d1 = LocalDate.of(2025, 12, 8)
val d2 = LocalDate.of(2025, 12, 10)

d1.isBefore(d2) // true
d1.isAfter(d2) // false
d1.isEqual(d2) // false
d1.compareTo(d2) // -1

// 判断是否在范围内
if (d1 in d2.minusDays(5)..d2.plusDays(5)) {
println("d1 在 d2 前后5天内")
}

💡 LocalDate 实现了 Comparable,可直接用于 sorted()min()max() 等。

调整日期(withXXX)

1
2
3
4
5
6
val date = LocalDate.of(2025, 12, 8)

date.withDayOfMonth(1) // 2025-12-01
date.with(TemporalAdjusters.lastDayOfMonth()) // 2025-12-31
date.with(TemporalAdjusters.next(DayOfWeek.MONDAY)) // 下一个周一
date.with(TemporalAdjusters.firstDayOfMonth()) // 本月第一天

需要导入:import java.time.temporal.TemporalAdjusters

与其他类型互转

1
2
3
4
5
6
7
8
// LocalDate → LocalDateTime(默认 00:00)
val ldt = date.atStartOfDay()

// LocalDate → Date
val utilDate = Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant())

// String → LocalDate(非 ISO 格式)
val parsed = LocalDate.parse("08-12-2025", DateTimeFormatter.ofPattern("dd-MM-yyyy"))

小贴士

  • LocalDate 没有时区概念,适合表示生日、节假日、合同起止日等
  • 所有方法都是不可变操作,不会修改原对象
  • 推荐配合 DateTimeFormatterTemporalAdjusters 使用高级功能