kotlin
复制代码
import kotlinx.datetime.*
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.toDuration
/**
* Kotlin Multiplatform 日期时间工具类
* 基于 kotlinx-datetime,提供时间戳转换、格式化、计算等常用功能
*/
object DateTimeUtils {
/**
* 获取当前时间戳(毫秒)
*/
val currentTimestamp: Long
get() = Clock.System.now().toEpochMilliseconds()
/**
* 获取当前时间的 Instant 对象(UTC 时间)
*/
fun getCurrentInstant(): Instant = Clock.System.now()
/**
* 获取当前系统默认时区的 LocalDateTime
*/
fun getCurrentLocalDateTime(): LocalDateTime =
getCurrentInstant().toLocalDateTime(TimeZone.currentSystemDefault())
/**
* 获取当前日期字符串(格式:yyyy-MM-dd)
*/
fun getCurrentDateString(): String =
getCurrentLocalDateTime().date.toString() // 输出示例: 2025-09-29
/**
* 获取当前时间字符串(格式:HH:mm:ss)
*/
fun getCurrentTimeString(): String =
getCurrentLocalDateTime().time.toString() // 输出示例: 16:26:51
/**
* 将毫秒时间戳转换为指定时区的 LocalDateTime
* @param timestamp 毫秒时间戳
* @param timeZone 目标时区,默认为系统时区
*/
fun timestampToLocalDateTime(timestamp: Long, timeZone: TimeZone = TimeZone.currentSystemDefault()): LocalDateTime =
Instant.fromEpochMilliseconds(timestamp).toLocalDateTime(timeZone)
/**
* 将 LocalDateTime 转换为毫秒时间戳
* @param localDateTime 本地日期时间对象
* @param timeZone 该 LocalDateTime 所对应的时区,默认为系统时区
*/
fun localDateTimeToTimestamp(localDateTime: LocalDateTime, timeZone: TimeZone = TimeZone.currentSystemDefault()): Long =
localDateTime.toInstant(timeZone).toEpochMilliseconds()
/**
* 格式化 LocalDateTime 为字符串
* @param localDateTime 要格式化的日期时间
* @param pattern 格式模式,例如 "yyyy-MM-dd HH:mm:ss"
* 注意:kotlinx-datetime 的格式模式与 Java 的 SimpleDateFormat 略有不同,
* 建议使用基于 Unicode 标准的模式字符串。
*/
fun formatLocalDateTime(localDateTime: LocalDateTime, pattern: String): String {
// 目前 kotlinx-datetime 的格式化功能仍在实验性阶段或需要自己组合。
// 一种简单且跨平台的方式是使用字符串拼接:
// 例如,对于 "yyyy-MM-dd HH:mm:ss" 模式:
return "${localDateTime.year}-${localDateTime.monthNumber.toString().padStart(2, '0')}-${localDateTime.dayOfMonth.toString().padStart(2, '0')} " +
"${localDateTime.hour.toString().padStart(2, '0')}:${localDateTime.minute.toString().padStart(2, '0')}:${localDateTime.second.toString().padStart(2, '0')}"
}
/**
* 计算两个时间点之间的日历差异(年、月、日等)
* @param start 开始时间
* @param end 结束时间
* @param timeZone 计算差异时所基于的时区,默认为 UTC
*/
fun getDateTimePeriod(start: Instant, end: Instant, timeZone: TimeZone = TimeZone.UTC): DateTimePeriod =
start.periodUntil(end, timeZone)
/**
* 计算两个时间点之间的持续时间(总小时、总分钟、总秒等)
* @param start 开始时间
* @param end 结束时间
*/
fun getDurationBetween(start: Instant, end: Instant): Duration =
end - start // Kotlin 运算符重载,直接相减得到 Duration
}
// region 实用扩展函数
/**
* 为 Instant 添加扩展函数,快速转换为指定时区的 LocalDateTime
*/
fun Instant.toLocalDateTimeIn(timeZone: TimeZone = TimeZone.currentSystemDefault()): LocalDateTime =
this.toLocalDateTime(timeZone)
/**
* 为 Long (毫秒时间戳) 添加扩展函数,快速转换为 Instant
*/
fun Long.toInstant(): Instant = Instant.fromEpochMilliseconds(this)
/**
* 为 LocalDateTime 添加扩展函数,快速转换为指定时区的 Instant
*/
fun LocalDateTime.toInstantIn(timeZone: TimeZone = TimeZone.currentSystemDefault()): Instant =
this.toInstant(timeZone)
// endregion
// region 核心转换扩展
/**
* 将毫秒时间戳转换为 Instant 对象
*/
val Long.instant: Instant
get() = Instant.fromEpochMilliseconds(this)
/**
* 将毫秒时间戳转换为系统默认时区的 LocalDateTime
*/
val Long.localDateTime: LocalDateTime
get() = this.instant.toLocalDateTime(TimeZone.currentSystemDefault())
/**
* 将毫秒时间戳转换为指定时区的 LocalDateTime
*/
fun Long.toLocalDateTimeIn(timeZone: TimeZone): LocalDateTime =
this.instant.toLocalDateTime(timeZone)
/**
* 将 LocalDateTime 转换回毫秒时间戳
*/
fun LocalDateTime.toTimestamp(timeZone: TimeZone = TimeZone.currentSystemDefault()): Long =
this.toInstant(timeZone).toEpochMilliseconds()
// endregion
// region 日期时间部件扩展属性
/**
* 从时间戳获取年份
*/
val Long.year: Int
get() = this.localDateTime.year
/**
* 从时间戳获取月份 (1-12)
*/
val Long.month: Int
get() = this.localDateTime.monthNumber
/**
* 从时间戳获取月份名称
*/
val Long.monthName: String
get() = this.localDateTime.month.name.lowercase()
.replaceFirstChar { it.uppercase() }
/**
* 从时间戳获取日期 (1-31)
*/
val Long.day: Int
get() = this.localDateTime.dayOfMonth
/**
* 从时间戳获取星期几
*/
val Long.dayOfWeek: String
get() = this.localDateTime.dayOfWeek.name.lowercase()
.replaceFirstChar { it.uppercase() }
/**
* 从时间戳获取小时 (0-23)
*/
val Long.hour: Int
get() = this.localDateTime.hour
/**
* 从时间戳获取分钟 (0-59)
*/
val Long.minute: Int
get() = this.localDateTime.minute
/**
* 从时间戳获取秒数 (0-59)
*/
val Long.second: Int
get() = this.localDateTime.second
/**
* 从时间戳获取毫秒数
*/
val Long.millisecond: Int
get() = this.localDateTime.nanosecond / 1_000_000
// endregion
// region 格式化输出扩展
/**
* 将时间戳格式化为默认字符串 "yyyy-MM-dd HH:mm:ss"
*/
fun Long.toFormattedString(): String = this.toFormattedString("yyyy-MM-dd HH:mm:ss")
/**
* 将时间戳格式化为指定模式的字符串
*/
fun Long.toFormattedString(pattern: String): String {
val dateTime = this.localDateTime
return when (pattern) {
"yyyy-MM-dd" -> "${dateTime.year}-${dateTime.monthNumber.toString().padStart(2, '0')}-${dateTime.dayOfMonth.toString().padStart(2, '0')}"
"yyyy-MM-dd HH:mm:ss" -> "${dateTime.year}-${dateTime.monthNumber.toString().padStart(2, '0')}-${dateTime.dayOfMonth.toString().padStart(2, '0')} " +
"${dateTime.hour.toString().padStart(2, '0')}:${dateTime.minute.toString().padStart(2, '0')}:${dateTime.second.toString().padStart(2, '0')}"
"HH:mm:ss" -> "${dateTime.hour.toString().padStart(2, '0')}:${dateTime.minute.toString().padStart(2, '0')}:${dateTime.second.toString().padStart(2, '0')}"
"yyyy/MM/dd" -> "${dateTime.year}/${dateTime.monthNumber.toString().padStart(2, '0')}/${dateTime.dayOfMonth.toString().padStart(2, '0')}"
"MM-dd" -> "${dateTime.monthNumber.toString().padStart(2, '0')}-${dateTime.dayOfMonth.toString().padStart(2, '0')}"
"yyyy年MM月dd日" -> "${dateTime.year}年${dateTime.monthNumber.toString().padStart(2, '0')}月${dateTime.dayOfMonth.toString().padStart(2, '0')}日"
"HH:mm" -> "${dateTime.hour.toString().padStart(2, '0')}:${dateTime.minute.toString().padStart(2, '0')}"
"yyyy-MM" -> "${dateTime.year}-${dateTime.monthNumber.toString().padStart(2, '0')}"
"MM/dd" -> "${dateTime.monthNumber.toString().padStart(2, '0')}/${dateTime.dayOfMonth.toString().padStart(2, '0')}"
else -> dateTime.toString()
}
}
/**
* 将时间戳转换为相对时间字符串(例如:"2天前", "刚刚")
*/
fun Long.toRelativeTimeString(): String {
val duration = (DateTimeUtils.currentTimestamp - this).toDuration(DurationUnit.MILLISECONDS)
return when {
duration.isNegative() -> "未来"
duration.inWholeDays > 365 -> "${duration.inWholeDays / 365}年前"
duration.inWholeDays > 30 -> "${duration.inWholeDays / 30}个月前"
duration.inWholeDays > 7 -> "${duration.inWholeDays / 7}周前"
duration.inWholeDays > 0 -> "${duration.inWholeDays}天前"
duration.inWholeHours > 0 -> "${duration.inWholeHours}小时前"
duration.inWholeMinutes > 0 -> "${duration.inWholeMinutes}分钟前"
else -> "刚刚"
}
}
/**
* 将时间戳转换为简洁的时间格式(今天显示时间,昨天显示"昨天",更早显示日期)
*/
fun Long.toSmartTimeString(): String {
return when {
this.isToday() -> this.toFormattedString("HH:mm")
this.isYesterday() -> "昨天 ${this.toFormattedString("HH:mm")}"
this.isThisYear() -> this.toFormattedString("MM-dd HH:mm")
else -> this.toFormattedString("yyyy-MM-dd")
}
}
// endregion
// region 相对时间判断扩展
/**
* 判断时间戳是否代表今天
*/
fun Long.isToday(): Boolean {
val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
val targetDate = this.instant.toLocalDateTime(TimeZone.currentSystemDefault()).date
return today == targetDate
}
/**
* 判断时间戳是否代表明天
*/
fun Long.isTomorrow(): Boolean {
val tomorrow = Clock.System.now().plus(DateTimePeriod(days = 1), TimeZone.UTC)
.toLocalDateTime(TimeZone.currentSystemDefault()).date
val targetDate = this.instant.toLocalDateTime(TimeZone.currentSystemDefault()).date
return tomorrow == targetDate
}
/**
* 判断时间戳是否代表昨天
*/
fun Long.isYesterday(): Boolean {
val yesterday = Clock.System.now().minus(DateTimePeriod(days = 1), TimeZone.UTC)
.toLocalDateTime(TimeZone.currentSystemDefault()).date
val targetDate = this.instant.toLocalDateTime(TimeZone.currentSystemDefault()).date
return yesterday == targetDate
}
/**
* 判断时间戳是否在今年
*/
fun Long.isThisYear(): Boolean {
val currentYear = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).year
return this.year == currentYear
}
/**
* 判断时间戳是否在指定年份
*/
fun Long.isInYear(targetYear: Int): Boolean = this.year == targetYear
/**
* 判断时间戳是否在指定月份
*/
fun Long.isInMonth(targetMonth: Int): Boolean = this.month == targetMonth
// endregion
// region 时间运算扩展
/**
* 为此时间戳添加指定的时间量
*/
fun Long.plusDuration(duration: Duration): Long =
(this.duration + duration).inWholeMilliseconds
fun Long.plusDays(days: Int): Long = this.plusDuration(days.toDuration(DurationUnit.DAYS))
fun Long.plusHours(hours: Int): Long = this.plusDuration(hours.toDuration(DurationUnit.HOURS))
fun Long.plusMinutes(minutes: Int): Long = this.plusDuration(minutes.toDuration(DurationUnit.MINUTES))
fun Long.plusSeconds(seconds: Int): Long = this.plusDuration(seconds.toDuration(DurationUnit.SECONDS))
/**
* 从此时间戳减去指定的时间量
*/
fun Long.minusDuration(duration: Duration): Long =
(this.duration - duration).inWholeMilliseconds
fun Long.minusDays(days: Int): Long = this.minusDuration(days.toDuration(DurationUnit.DAYS))
fun Long.minusHours(hours: Int): Long = this.minusDuration(hours.toDuration(DurationUnit.HOURS))
fun Long.minusMinutes(minutes: Int): Long = this.minusDuration(minutes.toDuration(DurationUnit.MINUTES))
/**
* 计算此时间戳与另一个时间戳之间的时间差
*/
fun Long.timeUntil(otherTimestamp: Long): Duration =
(otherTimestamp - this).toDuration(DurationUnit.MILLISECONDS)
/**
* 计算此时间戳与当前时间的时间差
*/
fun Long.timeFromNow(): Duration =
(DateTimeUtils.currentTimestamp - this).toDuration(DurationUnit.MILLISECONDS)
/**
* 计算两个时间戳之间相隔的天数
*/
fun Long.daysBetween(otherTimestamp: Long): Int {
val startDate = this.instant.toLocalDateTime(TimeZone.currentSystemDefault()).date
val endDate = otherTimestamp.instant.toLocalDateTime(TimeZone.currentSystemDefault()).date
return startDate.daysUntil(endDate)
}
/**
* 计算两个时间戳之间相隔的小时数
*/
fun Long.hoursBetween(otherTimestamp: Long): Long =
this.timeUntil(otherTimestamp).inWholeHours
/**
* 计算两个时间戳之间相隔的分钟数
*/
fun Long.minutesBetween(otherTimestamp: Long): Long =
this.timeUntil(otherTimestamp).inWholeMinutes
// endregion
// region 实用工具扩展
/**
* 获取当天开始的时间戳(00:00:00.000)
*/
fun Long.atStartOfDay(timeZone: TimeZone = TimeZone.currentSystemDefault()): Long {
val localDate = this.instant.toLocalDateTime(timeZone).date
return localDate.atStartOfDayIn(timeZone).toEpochMilliseconds()
}
/**
* 获取当天结束的时间戳(23:59:59.999)
*/
fun Long.atEndOfDay(timeZone: TimeZone = TimeZone.currentSystemDefault()): Long {
return this.atStartOfDay(timeZone).plusDays(1) - 1
}
/**
* 获取本周第一天的开始时间戳
*/
fun Long.atStartOfWeek(timeZone: TimeZone = TimeZone.currentSystemDefault()): Long {
val localDateTime = this.instant.toLocalDateTime(timeZone)
val daysFromMonday = (localDateTime.dayOfWeek.isoDayNumber - 1) % 7
return this.minusDays(daysFromMonday).atStartOfDay(timeZone)
}
/**
* 获取本月第一天的开始时间戳
*/
fun Long.atStartOfMonth(timeZone: TimeZone = TimeZone.currentSystemDefault()): Long {
val localDateTime = this.instant.toLocalDateTime(timeZone)
val firstDayOfMonth = LocalDate(localDateTime.year, localDateTime.month, 1)
return firstDayOfMonth.atStartOfDayIn(timeZone).toEpochMilliseconds()
}
/**
* 判断时间戳是否在某个时间范围内
*/
fun Long.isBetween(startTimestamp: Long, endTimestamp: Long): Boolean =
this in startTimestamp..endTimestamp
/**
* 将时间戳转换为 Duration 对象
*/
val Long.duration: Duration
get() = this.toDuration(DurationUnit.MILLISECONDS)
/**
* 获取时间戳对应的季度 (1-4)
*/
val Long.quarter: Int
get() = (this.month - 1) / 3 + 1
/**
* 获取时间戳对应的星期在年中的周数
*/
val Long.weekOfYear: Int
get() {
val localDate = this.instant.toLocalDateTime(TimeZone.currentSystemDefault()).date
val firstDayOfYear = LocalDate(localDate.year, 1, 1)
val daysBetween = firstDayOfYear.daysUntil(localDate)
return daysBetween / 7 + 1
}
// endregion
// region 常用日期格式模式常量
const val PATTERN_DEFAULT = "yyyy-MM-dd HH:mm:ss"
const val PATTERN_DATE_ONLY = "yyyy-MM-dd"
const val PATTERN_TIME_ONLY = "HH:mm:ss"
const val PATTERN_CHINESE_DATE = "yyyy年MM月dd日"
const val PATTERN_CHINESE_DATETIME = "yyyy年MM月dd日 HH时mm分ss秒"
const val PATTERN_NO_SECONDS = "yyyy-MM-dd HH:mm"
const val PATTERN_FOR_FILENAME = "yyyyMMdd_HHmmss"
const val PATTERN_YEAR_MONTH = "yyyy-MM"
const val PATTERN_MONTH_DAY = "MM-dd"
// endregion