kotlinx-datetime для Kotlin: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

kotlinx-datetime: мультиплатформенная работа с датой и временем в Kotlin

1. Что это и зачем нужна

kotlinx-datetime — это официальная библиотека от JetBrains для работы с датой и временем в экосистеме Kotlin. Её главная особенность — мультиплатформенность: один и тот же код работает на JVM, JS (в браузере и Node.js), Native (iOS, macOS, Windows, Linux) и Wasm. Это избавляет разработчиков от необходимости писать платформо-зависимые обёртки или использовать разные библиотеки для разных целей.

Зачем она нужна? Стандартная библиотека Kotlin/JVM использует java.time, который недоступен на других платформах. Для Kotlin/Native и Kotlin/JS приходилось либо использовать платформенные API (NSDate, Date), либо подключать сторонние библиотеки. kotlinx-datetime решает эту проблему, предоставляя единый API, основанный на спецификации ISO-8601. Она включает классы для представления моментов времени (Instant), дат без времени (LocalDate), времени без даты (LocalTime), даты со временем (LocalDateTime), часовых поясов (TimeZone) и периодов (DateTimePeriod).

Библиотека идеально подходит для проектов, использующих Kotlin Multiplatform (KMP), серверных приложений на Ktor, Android-приложений (как замена java.time) и даже для скриптов и консольных утилит. Она легковесна, не имеет внешних зависимостей и активно поддерживается сообществом.

2. Установка

Библиотека распространяется через Maven Central. Для её подключения необходимо добавить зависимость в файл build.gradle.kts (или build.gradle). Ниже приведён точный код для Gradle Kotlin DSL.

// build.gradle.kts
repositories {
    mavenCentral()
}

dependencies {
    // Основная библиотека
    implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")

    // Если вы используете мультиплатформенный проект, добавляйте в commonMain:
    // commonMain.dependencies {
    //     implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
    // }
}

Важно: Версия 0.6.1 — последняя стабильная на момент написания статьи. Проверяйте актуальную версию на GitHub-релизах. Для Gradle Groovy DSL команда будет аналогичной, но без кавычек у строк.

Если вы используете Maven, добавьте в pom.xml:

<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-datetime</artifactId>
    <version>0.6.1</version>
</dependency>

Для Gradle Kotlin DSL в мультиплатформенном проекте (KMP) добавьте в commonMain:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
        }
    }
}

3. Быстрый старт

Минимальный рабочий пример: создание текущего момента времени, его форматирование и вывод на консоль.

import kotlinx.datetime.*

fun main() {
    // Получаем текущий момент времени в UTC
    val now: Instant = Clock.System.now()
    println("Текущий момент в UTC: $now")

    // Преобразуем в локальную дату и время (используя системный часовой пояс)
    val localDateTime: LocalDateTime = now.toLocalDateTime(TimeZone.currentSystemDefault())
    println("Локальная дата и время: $localDateTime")

    // Создаём конкретную дату
    val specificDate: LocalDate = LocalDate(2025, 3, 15)
    println("Конкретная дата: $specificDate")
}

Вывод (зависит от текущего времени):

Текущий момент в UTC: 2025-03-15T12:30:45.123Z
Локальная дата и время: 2025-03-15T15:30:45.123
Конкретная дата: 2025-03-15

4. Основные методы, функции и классы

Рассмотрим 8 ключевых элементов библиотеки. Все примеры предполагают импорт import kotlinx.datetime.*.

4.1. Clock.System.now()

Сигнатура: fun now(): Instant

Что делает: Возвращает текущий момент времени в виде Instant (момент на временной шкале, обычно в UTC). Это аналог System.currentTimeMillis() в Java, но с наносекундной точностью.

val currentInstant: Instant = Clock.System.now()
println("Сейчас: $currentInstant") // Например: 2025-03-15T12:30:45.123456Z

4.2. Instant.toLocalDateTime()

Сигнатура: fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime

Что делает: Преобразует момент времени (Instant) в локальную дату и время (LocalDateTime) для указанного часового пояса. Учитывает смещение и переход на летнее время.

val instant = Instant.parse("2025-03-15T12:00:00Z")
val tz = TimeZone.of("Europe/Moscow") // UTC+3
val localDT = instant.toLocalDateTime(tz)
println(localDT) // 2025-03-15T15:00:00

4.3. LocalDate.plus() и minus()

Сигнатура: operator fun LocalDate.plus(other: DateTimePeriod): LocalDate (аналогично для minus)

Что делает: Позволяет прибавлять или вычитать временные периоды (DateTimePeriod) из даты. Поддерживает дни, месяцы, годы.

val today = LocalDate(2025, 3, 15)
val nextWeek = today.plus(DateTimePeriod(days = 7))
val lastMonth = today.minus(DateTimePeriod(months = 1))
println("Через неделю: $nextWeek") // 2025-03-22
println("Месяц назад: $lastMonth") // 2025-02-15

4.4. LocalDateTime.toInstant()

Сигнатура: fun LocalDateTime.toInstant(timeZone: TimeZone): Instant

Что делает: Преобразует локальную дату и время в момент времени (Instant) в заданном часовом поясе. Обратная операция к toLocalDateTime.

val localDT = LocalDateTime(2025, 3, 15, 15, 0, 0)
val tz = TimeZone.of("Europe/Moscow")
val instant = localDT.toInstant(tz)
println(instant) // 2025-03-15T12:00:00Z (так как Moscow UTC+3)

4.5. DateTimePeriod

Сигнатура: data class DateTimePeriod(val days: Int, val months: Int, val years: Int, val hours: Int, val minutes: Int, val seconds: Int, val nanoseconds: Int)

Что делает: Представляет собой период времени в календарных единицах (дни, месяцы, годы) и точных единицах (часы, минуты, секунды, наносекунды). Используется для арифметики с датами и временем.

val period = DateTimePeriod(days = 30, hours = 12)
val start = LocalDateTime(2025, 1, 1, 0, 0)
val end = start.plus(period)
println(end) // 2025-01-31T12:00

4.6. TimeZone.currentSystemDefault()

Сигнатура: fun currentSystemDefault(): TimeZone

Что делает: Возвращает часовой пояс, установленный в операционной системе. Это удобно для отображения времени в локальном формате.

val systemTz: TimeZone = TimeZone.currentSystemDefault()
println("Системный часовой пояс: ${systemTz.id}") // Например: "Europe/Moscow"

4.7. Instant.periodUntil()

Сигнатура: fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod

Что делает: Вычисляет разницу между двумя моментами времени в виде DateTimePeriod с учётом календаря (дни, месяцы, годы) для заданного часового пояса.

val start = Instant.parse("2025-01-01T00:00:00Z")
val end = Instant.parse("2025-03-15T12:30:00Z")
val diff = start.periodUntil(end, TimeZone.UTC)
println("Разница: ${diff.months} месяцев, ${diff.days} дней, ${diff.hours} часов")
// Разница: 2 месяцев, 14 дней, 12 часов

4.8. LocalDate.dayOfWeek

Сигнатура: val LocalDate.dayOfWeek: DayOfWeek (свойство)

Что делает: Возвращает день недели для даты в виде перечисления DayOfWeek (Monday, Tuesday, ..., Sunday). Полезно для календарных расчётов.

val date = LocalDate(2025, 3, 15) // 15 марта 2025
val dayOfWeek: DayOfWeek = date.dayOfWeek
println("День недели: ${dayOfWeek.name}") // SATURDAY
// Можно получить числовое представление (1 = Monday, 7 = Sunday)
println("Номер дня: ${dayOfWeek.isoDayNumber}") // 6

5. Реальный практический пример

Напишем приложение — планировщик встреч. Оно будет:

  • Принимать список встреч с датой, временем и часовым поясом.
  • Проверять, не пересекаются ли встречи.
  • Вычислять длительность встречи.
  • Выводить расписание в локальном времени пользователя.

import kotlinx.datetime.*

data class Meeting(
    val name: String,
    val startInstant: Instant,
    val endInstant: Instant
)

class MeetingScheduler {
    private val meetings = mutableListOf()

    fun addMeeting(name: String, startLocal: LocalDateTime, endLocal: LocalDateTime, timeZone: TimeZone) {
        val start = startLocal.toInstant(timeZone)
        val end = endLocal.toInstant(timeZone)
        if (start >= end) {
            println("Ошибка: время начала должно быть раньше времени окончания")
            return
        }
        // Проверка на пересечение
        for (existing in meetings) {
            if (start < existing.endInstant && end > existing.startInstant) {
                println("Ошибка: встреча '$name' пересекается с '${existing.name}'")
                return
            }
        }
        meetings.add(Meeting(name, start, end))
        println("Встреча '$name' добавлена")
    }

    fun printSchedule(userTimeZone: TimeZone) {
        if (meetings.isEmpty()) {
            println("Расписание пусто")
            return
        }
        meetings.sortBy { it.startInstant }
        println("Расписание встреч (в часовом поясе ${userTimeZone.id}):")
        for ((index, meeting) in meetings.withIndex()) {
            val startLocal = meeting.startInstant.toLocalDateTime(userTimeZone)
            val endLocal = meeting.endInstant.toLocalDateTime(userTimeZone)
            val duration = meeting.startInstant.periodUntil(meeting.endInstant, userTimeZone)
            println("${index + 1}. ${meeting.name}")
            println("   Начало: $startLocal")
            println("   Конец:  $endLocal")
            println("   Длительность: ${duration.hours} ч ${duration.minutes} мин")
        }
    }

    fun getMeetingsForDate(date: LocalDate, userTimeZone: TimeZone): List {
        val dayStart = date.atStartOfDayIn(userTimeZone)
        val dayEnd = date.plus(DateTimePeriod(days = 1)).atStartOfDayIn(userTimeZone)
        return meetings.filter { it.startInstant >= dayStart && it.startInstant < dayEnd }
    }
}

fun main() {
    val                    

Рекомендаций